about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2016-05-20 00:34:50 -0700
committerbors <bors@rust-lang.org>2016-05-20 00:34:50 -0700
commitd27bdafc3eaab2729d664f82b7d650782640f31a (patch)
tree2940c1e6c24c8df00ff08e397042a5033f3b05c2
parentcde0fa5f673c99e8d534123187ee554452513dc3 (diff)
parent0d2c26c261671f71002af13431fbd3c9720feff2 (diff)
downloadrust-d27bdafc3eaab2729d664f82b7d650782640f31a.tar.gz
rust-d27bdafc3eaab2729d664f82b7d650782640f31a.zip
Auto merge of #33553 - alexcrichton:cdylibs, r=brson
rustc: Add a new crate type, cdylib

This commit is an implementation of [RFC 1510] which adds a new crate type,
`cdylib`, to the compiler. This new crate type differs from the existing `dylib`
crate type in a few key ways:

* No metadata is present in the final artifact
* Symbol visibility rules are the same as executables, that is only reachable
  `extern` functions are visible symbols
* LTO is allowed
* All libraries are always linked statically

This commit is relatively simple by just plubming the compiler with another
crate type which takes different branches here and there. The only major change
is an implementation of the `Linker::export_symbols` function on Unix which now
actually does something. This helps restrict the public symbols from a cdylib on
Unix.

With this PR a "hello world" `cdylib` is 7.2K while the same `dylib` is 2.4MB,
which is some nice size savings!

[RFC 1510]: https://github.com/rust-lang/rfcs/pull/1510

Closes #33132
-rw-r--r--src/librustc/middle/dependency_format.rs7
-rw-r--r--src/librustc/middle/reachable.rs2
-rw-r--r--src/librustc/middle/weak_lang_items.rs1
-rw-r--r--src/librustc/session/config.rs5
-rw-r--r--src/librustc_driver/driver.rs3
-rw-r--r--src/librustc_metadata/creader.rs1
-rw-r--r--src/librustc_trans/back/link.rs80
-rw-r--r--src/librustc_trans/back/linker.rs125
-rw-r--r--src/librustc_trans/back/lto.rs7
-rw-r--r--src/librustc_trans/base.rs35
-rw-r--r--src/librustc_trans/context.rs6
-rw-r--r--src/test/compile-fail/auxiliary/cdylib-dep.rs11
-rw-r--r--src/test/compile-fail/cdylib-deps-must-be-static.rs17
-rw-r--r--src/test/run-make/cdylib/Makefile19
-rw-r--r--src/test/run-make/cdylib/bar.rs15
-rw-r--r--src/test/run-make/cdylib/foo.c20
-rw-r--r--src/test/run-make/cdylib/foo.rs23
17 files changed, 283 insertions, 94 deletions
diff --git a/src/librustc/middle/dependency_format.rs b/src/librustc/middle/dependency_format.rs
index fe22cfdb43f..0b398fd0d47 100644
--- a/src/librustc/middle/dependency_format.rs
+++ b/src/librustc/middle/dependency_format.rs
@@ -115,9 +115,10 @@ fn calculate_type(sess: &session::Session,
         // got long ago), so don't bother with anything.
         config::CrateTypeRlib => return Vec::new(),
 
-        // Staticlibs must have all static dependencies. If any fail to be
-        // found, we generate some nice pretty errors.
-        config::CrateTypeStaticlib => {
+        // Staticlibs and cdylibs must have all static dependencies. If any fail
+        // to be found, we generate some nice pretty errors.
+        config::CrateTypeStaticlib |
+        config::CrateTypeCdylib => {
             match attempt_static(sess) {
                 Some(v) => return v,
                 None => {}
diff --git a/src/librustc/middle/reachable.rs b/src/librustc/middle/reachable.rs
index bca5af69edf..55d75ace081 100644
--- a/src/librustc/middle/reachable.rs
+++ b/src/librustc/middle/reachable.rs
@@ -145,7 +145,7 @@ impl<'a, 'tcx> ReachableContext<'a, 'tcx> {
     // Creates a new reachability computation context.
     fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>) -> ReachableContext<'a, 'tcx> {
         let any_library = tcx.sess.crate_types.borrow().iter().any(|ty| {
-            *ty != config::CrateTypeExecutable
+            *ty == config::CrateTypeRlib || *ty == config::CrateTypeDylib
         });
         ReachableContext {
             tcx: tcx,
diff --git a/src/librustc/middle/weak_lang_items.rs b/src/librustc/middle/weak_lang_items.rs
index b7dfc867204..32588768491 100644
--- a/src/librustc/middle/weak_lang_items.rs
+++ b/src/librustc/middle/weak_lang_items.rs
@@ -70,6 +70,7 @@ fn verify(sess: &Session, items: &lang_items::LanguageItems) {
     let needs_check = sess.crate_types.borrow().iter().any(|kind| {
         match *kind {
             config::CrateTypeDylib |
+            config::CrateTypeCdylib |
             config::CrateTypeExecutable |
             config::CrateTypeStaticlib => true,
             config::CrateTypeRlib => false,
diff --git a/src/librustc/session/config.rs b/src/librustc/session/config.rs
index 7bb96c5ab2b..da5555dbd64 100644
--- a/src/librustc/session/config.rs
+++ b/src/librustc/session/config.rs
@@ -300,6 +300,7 @@ pub enum CrateType {
     CrateTypeDylib,
     CrateTypeRlib,
     CrateTypeStaticlib,
+    CrateTypeCdylib,
 }
 
 #[derive(Clone)]
@@ -1326,6 +1327,7 @@ pub fn parse_crate_types_from_list(list_list: Vec<String>) -> Result<Vec<CrateTy
                 "rlib"      => CrateTypeRlib,
                 "staticlib" => CrateTypeStaticlib,
                 "dylib"     => CrateTypeDylib,
+                "cdylib"    => CrateTypeCdylib,
                 "bin"       => CrateTypeExecutable,
                 _ => {
                     return Err(format!("unknown crate type: `{}`",
@@ -1413,7 +1415,8 @@ impl fmt::Display for CrateType {
             CrateTypeExecutable => "bin".fmt(f),
             CrateTypeDylib => "dylib".fmt(f),
             CrateTypeRlib => "rlib".fmt(f),
-            CrateTypeStaticlib => "staticlib".fmt(f)
+            CrateTypeStaticlib => "staticlib".fmt(f),
+            CrateTypeCdylib => "cdylib".fmt(f),
         }
     }
 }
diff --git a/src/librustc_driver/driver.rs b/src/librustc_driver/driver.rs
index 1d60c2eb437..1f3df1ff6f2 100644
--- a/src/librustc_driver/driver.rs
+++ b/src/librustc_driver/driver.rs
@@ -1174,6 +1174,9 @@ pub fn collect_crate_types(session: &Session, attrs: &[ast::Attribute]) -> Vec<c
                          Some(ref n) if *n == "dylib" => {
                              Some(config::CrateTypeDylib)
                          }
+                         Some(ref n) if *n == "cdylib" => {
+                             Some(config::CrateTypeCdylib)
+                         }
                          Some(ref n) if *n == "lib" => {
                              Some(config::default_lib_output())
                          }
diff --git a/src/librustc_metadata/creader.rs b/src/librustc_metadata/creader.rs
index 0eacc0907bc..aac68cc09bc 100644
--- a/src/librustc_metadata/creader.rs
+++ b/src/librustc_metadata/creader.rs
@@ -744,6 +744,7 @@ impl<'a> CrateReader<'a> {
             match *ct {
                 config::CrateTypeExecutable => need_exe_alloc = true,
                 config::CrateTypeDylib |
+                config::CrateTypeCdylib |
                 config::CrateTypeStaticlib => need_lib_alloc = true,
                 config::CrateTypeRlib => {}
             }
diff --git a/src/librustc_trans/back/link.rs b/src/librustc_trans/back/link.rs
index b5248c209a3..53cc0319829 100644
--- a/src/librustc_trans/back/link.rs
+++ b/src/librustc_trans/back/link.rs
@@ -231,6 +231,7 @@ pub fn invalid_output_for_target(sess: &Session,
                                  crate_type: config::CrateType) -> bool {
     match (sess.target.target.options.dynamic_linking,
            sess.target.target.options.executables, crate_type) {
+        (false, _, config::CrateTypeCdylib) |
         (false, _, config::CrateTypeDylib) => true,
         (_, false, config::CrateTypeExecutable) => true,
         _ => false
@@ -253,6 +254,7 @@ pub fn filename_for_input(sess: &Session,
         config::CrateTypeRlib => {
             outputs.out_directory.join(&format!("lib{}.rlib", libname))
         }
+        config::CrateTypeCdylib |
         config::CrateTypeDylib => {
             let (prefix, suffix) = (&sess.target.target.options.dll_prefix,
                                     &sess.target.target.options.dll_suffix);
@@ -281,9 +283,10 @@ pub fn each_linked_rlib(sess: &Session,
                         f: &mut FnMut(ast::CrateNum, &Path)) {
     let crates = sess.cstore.used_crates(LinkagePreference::RequireStatic).into_iter();
     let fmts = sess.dependency_formats.borrow();
-    let fmts = fmts.get(&config::CrateTypeExecutable).or_else(|| {
-        fmts.get(&config::CrateTypeStaticlib)
-    }).unwrap_or_else(|| {
+    let fmts = fmts.get(&config::CrateTypeExecutable)
+                   .or_else(|| fmts.get(&config::CrateTypeStaticlib))
+                   .or_else(|| fmts.get(&config::CrateTypeCdylib));
+    let fmts = fmts.unwrap_or_else(|| {
         bug!("could not find formats for rlibs")
     });
     for (cnum, path) in crates {
@@ -338,13 +341,9 @@ fn link_binary_output(sess: &Session,
         config::CrateTypeStaticlib => {
             link_staticlib(sess, &objects, &out_filename, tmpdir.path());
         }
-        config::CrateTypeExecutable => {
-            link_natively(sess, false, &objects, &out_filename, trans, outputs,
-                          tmpdir.path());
-        }
-        config::CrateTypeDylib => {
-            link_natively(sess, true, &objects, &out_filename, trans, outputs,
-                          tmpdir.path());
+        _ => {
+            link_natively(sess, crate_type, &objects, &out_filename, trans,
+                          outputs, tmpdir.path());
         }
     }
 
@@ -612,13 +611,14 @@ fn link_staticlib(sess: &Session, objects: &[PathBuf], out_filename: &Path,
 //
 // This will invoke the system linker/cc to create the resulting file. This
 // links to all upstream files as well.
-fn link_natively(sess: &Session, dylib: bool,
-                 objects: &[PathBuf], out_filename: &Path,
+fn link_natively(sess: &Session,
+                 crate_type: config::CrateType,
+                 objects: &[PathBuf],
+                 out_filename: &Path,
                  trans: &CrateTranslation,
                  outputs: &OutputFilenames,
                  tmpdir: &Path) {
-    info!("preparing dylib? ({}) from {:?} to {:?}", dylib, objects,
-          out_filename);
+    info!("preparing {:?} from {:?} to {:?}", crate_type, objects, out_filename);
 
     // The invocations of cc share some flags across platforms
     let (pname, mut cmd) = get_linker(sess);
@@ -627,10 +627,10 @@ fn link_natively(sess: &Session, dylib: bool,
     let root = sess.target_filesearch(PathKind::Native).get_lib_path();
     cmd.args(&sess.target.target.options.pre_link_args);
 
-    let pre_link_objects = if dylib {
-        &sess.target.target.options.pre_link_objects_dll
-    } else {
+    let pre_link_objects = if crate_type == config::CrateTypeExecutable {
         &sess.target.target.options.pre_link_objects_exe
+    } else {
+        &sess.target.target.options.pre_link_objects_dll
     };
     for obj in pre_link_objects {
         cmd.arg(root.join(obj));
@@ -642,7 +642,7 @@ fn link_natively(sess: &Session, dylib: bool,
         } else {
             Box::new(GnuLinker { cmd: &mut cmd, sess: &sess }) as Box<Linker>
         };
-        link_args(&mut *linker, sess, dylib, tmpdir,
+        link_args(&mut *linker, sess, crate_type, tmpdir,
                   objects, out_filename, trans, outputs);
         if !sess.target.target.options.no_compiler_rt {
             linker.link_staticlib("compiler-rt");
@@ -708,7 +708,7 @@ fn link_natively(sess: &Session, dylib: bool,
 
 fn link_args(cmd: &mut Linker,
              sess: &Session,
-             dylib: bool,
+             crate_type: config::CrateType,
              tmpdir: &Path,
              objects: &[PathBuf],
              out_filename: &Path,
@@ -730,26 +730,28 @@ fn link_args(cmd: &mut Linker,
 
     // 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);
+    if crate_type != config::CrateTypeExecutable {
+        cmd.export_symbols(sess, trans, tmpdir, crate_type);
     }
 
     // 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.
-    if dylib {
+    if crate_type == config::CrateTypeDylib {
         cmd.add_object(&outputs.with_extension("metadata.o"));
     }
 
     // Try to strip as much out of the generated object by removing unused
     // sections if possible. See more comments in linker.rs
     if !sess.opts.cg.link_dead_code {
-        cmd.gc_sections(dylib);
+        let keep_metadata = crate_type == config::CrateTypeDylib;
+        cmd.gc_sections(keep_metadata);
     }
 
     let used_link_args = sess.cstore.used_link_args();
 
-    if !dylib && t.options.position_independent_executables {
+    if crate_type == config::CrateTypeExecutable &&
+       t.options.position_independent_executables {
         let empty_vec = Vec::new();
         let empty_str = String::new();
         let args = sess.opts.cg.link_args.as_ref().unwrap_or(&empty_vec);
@@ -804,12 +806,12 @@ fn link_args(cmd: &mut Linker,
     // in this DAG so far because they're only dylibs and dylibs can only depend
     // on other dylibs (e.g. other native deps).
     add_local_native_libraries(cmd, sess);
-    add_upstream_rust_crates(cmd, sess, dylib, tmpdir);
+    add_upstream_rust_crates(cmd, sess, crate_type, tmpdir);
     add_upstream_native_libraries(cmd, sess);
 
     // # Telling the linker what we're doing
 
-    if dylib {
+    if crate_type != config::CrateTypeExecutable {
         cmd.build_dylib(out_filename);
     }
 
@@ -907,8 +909,10 @@ fn add_local_native_libraries(cmd: &mut Linker, sess: &Session) {
 // Rust crates are not considered at all when creating an rlib output. All
 // dependencies will be linked when producing the final output (instead of
 // the intermediate rlib version)
-fn add_upstream_rust_crates(cmd: &mut Linker, sess: &Session,
-                            dylib: bool, tmpdir: &Path) {
+fn add_upstream_rust_crates(cmd: &mut Linker,
+                            sess: &Session,
+                            crate_type: config::CrateType,
+                            tmpdir: &Path) {
     // All of the heavy lifting has previously been accomplished by the
     // dependency_format module of the compiler. This is just crawling the
     // output of that module, adding crates as necessary.
@@ -918,11 +922,7 @@ fn add_upstream_rust_crates(cmd: &mut Linker, sess: &Session,
     // involves just passing the right -l flag.
 
     let formats = sess.dependency_formats.borrow();
-    let data = if dylib {
-        formats.get(&config::CrateTypeDylib).unwrap()
-    } else {
-        formats.get(&config::CrateTypeExecutable).unwrap()
-    };
+    let data = formats.get(&crate_type).unwrap();
 
     // Invoke get_used_crates to ensure that we get a topological sorting of
     // crates.
@@ -937,7 +937,8 @@ fn add_upstream_rust_crates(cmd: &mut Linker, sess: &Session,
             Linkage::NotLinked |
             Linkage::IncludedFromDylib => {}
             Linkage::Static => {
-                add_static_crate(cmd, sess, tmpdir, dylib, &src.rlib.unwrap().0)
+                add_static_crate(cmd, sess, tmpdir, crate_type,
+                                 &src.rlib.unwrap().0)
             }
             Linkage::Dynamic => {
                 add_dynamic_crate(cmd, sess, &src.dylib.unwrap().0)
@@ -982,9 +983,12 @@ fn add_upstream_rust_crates(cmd: &mut Linker, sess: &Session,
     // (aka we're making an executable), we can just pass the rlib blindly to
     // the linker (fast) because it's fine if it's not actually included as
     // we're at the end of the dependency chain.
-    fn add_static_crate(cmd: &mut Linker, sess: &Session, tmpdir: &Path,
-                        dylib: bool, cratepath: &Path) {
-        if !sess.lto() && !dylib {
+    fn add_static_crate(cmd: &mut Linker,
+                        sess: &Session,
+                        tmpdir: &Path,
+                        crate_type: config::CrateType,
+                        cratepath: &Path) {
+        if !sess.lto() && crate_type != config::CrateTypeDylib {
             cmd.link_rlib(&fix_windows_verbatim_for_gcc(cratepath));
             return
         }
@@ -1020,7 +1024,7 @@ fn add_upstream_rust_crates(cmd: &mut Linker, sess: &Session,
 
             if any_objects {
                 archive.build();
-                if dylib {
+                if crate_type == config::CrateTypeDylib {
                     cmd.link_whole_rlib(&fix_windows_verbatim_for_gcc(&dst));
                 } else {
                     cmd.link_rlib(&fix_windows_verbatim_for_gcc(&dst));
diff --git a/src/librustc_trans/back/linker.rs b/src/librustc_trans/back/linker.rs
index 8055e97034e..50f6366e85c 100644
--- a/src/librustc_trans/back/linker.rs
+++ b/src/librustc_trans/back/linker.rs
@@ -18,7 +18,7 @@ use std::process::Command;
 use back::archive;
 use middle::dependency_format::Linkage;
 use session::Session;
-use session::config::CrateTypeDylib;
+use session::config::CrateType;
 use session::config;
 use syntax::ast;
 use CrateTranslation;
@@ -42,7 +42,7 @@ pub trait Linker {
     fn framework_path(&mut self, path: &Path);
     fn output_filename(&mut self, path: &Path);
     fn add_object(&mut self, path: &Path);
-    fn gc_sections(&mut self, is_dylib: bool);
+    fn gc_sections(&mut self, keep_metadata: bool);
     fn position_independent_executable(&mut self);
     fn optimize(&mut self);
     fn debuginfo(&mut self);
@@ -53,8 +53,11 @@ 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);
+    fn export_symbols(&mut self,
+                      sess: &Session,
+                      trans: &CrateTranslation,
+                      tmpdir: &Path,
+                      crate_type: CrateType);
 }
 
 pub struct GnuLinker<'a> {
@@ -113,7 +116,7 @@ impl<'a> Linker for GnuLinker<'a> {
         }
     }
 
-    fn gc_sections(&mut self, is_dylib: bool) {
+    fn gc_sections(&mut self, keep_metadata: bool) {
         // The dead_strip option to the linker specifies that functions and data
         // unreachable by the entry point will be removed. This is quite useful
         // with Rust's compilation model of compiling libraries at a time into
@@ -139,7 +142,7 @@ impl<'a> Linker for GnuLinker<'a> {
         // eliminate the metadata. If we're building an executable, however,
         // --gc-sections drops the size of hello world from 1.8MB to 597K, a 67%
         // reduction.
-        } else if !is_dylib {
+        } else if !keep_metadata {
             self.cmd.arg("-Wl,--gc-sections");
         }
     }
@@ -198,8 +201,46 @@ impl<'a> Linker for GnuLinker<'a> {
         self.cmd.arg("-Wl,-Bdynamic");
     }
 
-    fn export_symbols(&mut self, _: &Session, _: &CrateTranslation, _: &Path) {
-        // noop, visibility in object files takes care of this
+    fn export_symbols(&mut self,
+                      sess: &Session,
+                      trans: &CrateTranslation,
+                      tmpdir: &Path,
+                      crate_type: CrateType) {
+        // If we're compiling a dylib, then we let symbol visibility in object
+        // files to take care of whether they're exported or not.
+        //
+        // If we're compiling a cdylib, however, we manually create a list of
+        // exported symbols to ensure we don't expose any more. The object files
+        // have far more public symbols than we actually want to export, so we
+        // hide them all here.
+        if crate_type == CrateType::CrateTypeDylib {
+            return
+        }
+
+        let path = tmpdir.join("list");
+        let prefix = if self.sess.target.target.options.is_like_osx {
+            "_"
+        } else {
+            ""
+        };
+        let res = (|| -> io::Result<()> {
+            let mut f = BufWriter::new(File::create(&path)?);
+            for sym in exported_symbols(sess, trans, crate_type) {
+                writeln!(f, "{}{}", prefix, sym)?;
+            }
+            Ok(())
+        })();
+        if let Err(e) = res {
+            sess.fatal(&format!("failed to write lib.def file: {}", e));
+        }
+        let mut arg = OsString::new();
+        if self.sess.target.target.options.is_like_osx {
+            arg.push("-Wl,-exported_symbols_list,");
+        } else {
+            arg.push("-Wl,--retain-symbols-file=");
+        }
+        arg.push(&path);
+        self.cmd.arg(arg);
     }
 }
 
@@ -220,7 +261,9 @@ impl<'a> Linker for MsvcLinker<'a> {
         self.cmd.arg(arg);
     }
 
-    fn gc_sections(&mut self, _is_dylib: bool) { self.cmd.arg("/OPT:REF,ICF"); }
+    fn gc_sections(&mut self, _keep_metadata: bool) {
+        self.cmd.arg("/OPT:REF,ICF");
+    }
 
     fn link_dylib(&mut self, lib: &str) {
         self.cmd.arg(&format!("{}.lib", lib));
@@ -322,8 +365,11 @@ impl<'a> Linker for MsvcLinker<'a> {
     // 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) {
+    fn export_symbols(&mut self,
+                      sess: &Session,
+                      trans: &CrateTranslation,
+                      tmpdir: &Path,
+                      crate_type: CrateType) {
         let path = tmpdir.join("lib.def");
         let res = (|| -> io::Result<()> {
             let mut f = BufWriter::new(File::create(&path)?);
@@ -333,32 +379,10 @@ impl<'a> Linker for MsvcLinker<'a> {
             writeln!(f, "LIBRARY")?;
             writeln!(f, "EXPORTS")?;
 
-            // Write out all our local symbols
-            for sym in trans.reachable.iter() {
+            for sym in exported_symbols(sess, trans, crate_type) {
                 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 formats = sess.dependency_formats.borrow();
-            let symbols = formats[&CrateTypeDylib].iter();
-            let symbols = symbols.enumerate().filter_map(|(i, f)| {
-                if *f == Linkage::Static {
-                    Some((i + 1) as ast::CrateNum)
-                } else {
-                    None
-                }
-            }).flat_map(|cnum| {
-                cstore.reachable_ids(cnum)
-            }).map(|did| {
-                cstore.item_symbol(did)
-            });
-            for symbol in symbols {
-                writeln!(f, "  {}", symbol)?;
-            }
-
             Ok(())
         })();
         if let Err(e) = res {
@@ -369,3 +393,36 @@ impl<'a> Linker for MsvcLinker<'a> {
         self.cmd.arg(&arg);
     }
 }
+
+fn exported_symbols(sess: &Session,
+                    trans: &CrateTranslation,
+                    crate_type: CrateType) -> Vec<String> {
+    let mut symbols = trans.reachable.iter().cloned().collect::<Vec<_>>();
+
+    // 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 = &sess.cstore;
+    let formats = sess.dependency_formats.borrow();
+    let upstream_symbols = formats[&crate_type].iter();
+    symbols.extend(upstream_symbols.enumerate().filter_map(|(i, f)| {
+        if *f == Linkage::Static {
+            Some((i + 1) as ast::CrateNum)
+        } else {
+            None
+        }
+    }).flat_map(|cnum| {
+        cstore.reachable_ids(cnum)
+    }).map(|did| {
+        cstore.item_symbol(did)
+    }));
+
+    return symbols
+}
diff --git a/src/librustc_trans/back/lto.rs b/src/librustc_trans/back/lto.rs
index 649d37e802d..31bc11fb215 100644
--- a/src/librustc_trans/back/lto.rs
+++ b/src/librustc_trans/back/lto.rs
@@ -30,7 +30,8 @@ pub fn run(sess: &session::Session, llmod: ModuleRef,
            output_names: &config::OutputFilenames) {
     if sess.opts.cg.prefer_dynamic {
         sess.struct_err("cannot prefer dynamic linking when performing LTO")
-            .note("only 'staticlib' and 'bin' outputs are supported with LTO")
+            .note("only 'staticlib', 'bin', and 'cdylib' outputs are \
+                   supported with LTO")
             .emit();
         sess.abort_if_errors();
     }
@@ -38,7 +39,9 @@ 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::CrateTypeStaticlib => {}
+            config::CrateTypeExecutable |
+            config::CrateTypeCdylib |
+            config::CrateTypeStaticlib => {}
             _ => {
                 sess.fatal("lto can only be run for executables and \
                             static library outputs");
diff --git a/src/librustc_trans/base.rs b/src/librustc_trans/base.rs
index 481154ba29f..d68998927da 100644
--- a/src/librustc_trans/base.rs
+++ b/src/librustc_trans/base.rs
@@ -2509,9 +2509,7 @@ pub fn write_metadata<'a, 'tcx>(cx: &SharedCrateContext<'a, 'tcx>,
 
     let llmeta = C_bytes_in_context(cx.metadata_llcx(), &compressed[..]);
     let llconst = C_struct_in_context(cx.metadata_llcx(), &[llmeta], false);
-    let name = format!("rust_metadata_{}_{}",
-                       cx.link_meta().crate_name,
-                       cx.link_meta().crate_hash);
+    let name = cx.metadata_symbol_name();
     let buf = CString::new(name).unwrap();
     let llglobal = unsafe {
         llvm::LLVMAddGlobal(cx.metadata_llmod(), val_ty(llconst).to_ref(), buf.as_ptr())
@@ -2813,18 +2811,25 @@ pub fn trans_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
         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.
-    if sess.lto() {
-        for cnum in sess.cstore.crates() {
-            let syms = sess.cstore.reachable_ids(cnum);
-            reachable_symbols.extend(syms.into_iter().filter(|did| {
-                sess.cstore.is_extern_item(shared_ccx.tcx(), *did)
-            }).map(|did| {
-                sess.cstore.item_symbol(did)
-            }));
-        }
+    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(|did| {
+            sess.cstore.is_extern_item(shared_ccx.tcx(), *did)
+        }).map(|did| {
+            sess.cstore.item_symbol(did)
+        }));
     }
 
     if codegen_unit_count > 1 {
diff --git a/src/librustc_trans/context.rs b/src/librustc_trans/context.rs
index 60c6af84ebb..4d6c4cdcc6b 100644
--- a/src/librustc_trans/context.rs
+++ b/src/librustc_trans/context.rs
@@ -503,6 +503,12 @@ impl<'b, 'tcx> SharedCrateContext<'b, 'tcx> {
             Substs::new(VecPerParamSpace::empty(),
                         scheme.generics.regions.map(|_| ty::ReStatic)))
     }
+
+    pub fn metadata_symbol_name(&self) -> String {
+        format!("rust_metadata_{}_{}",
+                self.link_meta().crate_name,
+                self.link_meta().crate_hash)
+    }
 }
 
 impl<'tcx> LocalCrateContext<'tcx> {
diff --git a/src/test/compile-fail/auxiliary/cdylib-dep.rs b/src/test/compile-fail/auxiliary/cdylib-dep.rs
new file mode 100644
index 00000000000..a3d0222a14c
--- /dev/null
+++ b/src/test/compile-fail/auxiliary/cdylib-dep.rs
@@ -0,0 +1,11 @@
+// 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"]
diff --git a/src/test/compile-fail/cdylib-deps-must-be-static.rs b/src/test/compile-fail/cdylib-deps-must-be-static.rs
new file mode 100644
index 00000000000..4b160f26e92
--- /dev/null
+++ b/src/test/compile-fail/cdylib-deps-must-be-static.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.
+
+// error-pattern: dependency `cdylib_dep` not found in rlib format
+// aux-build:cdylib-dep.rs
+// ignore-musl
+
+#![crate_type = "cdylib"]
+
+extern crate cdylib_dep;
diff --git a/src/test/run-make/cdylib/Makefile b/src/test/run-make/cdylib/Makefile
new file mode 100644
index 00000000000..ae3b82537db
--- /dev/null
+++ b/src/test/run-make/cdylib/Makefile
@@ -0,0 +1,19 @@
+include ../tools.mk
+
+all: $(call RUN_BINFILE,foo)
+	$(call RUN,foo)
+	rm $(call DYLIB,foo)
+	$(RUSTC) foo.rs -C lto
+	$(call RUN,foo)
+
+ifdef IS_MSVC
+$(call RUN_BINFILE,foo): $(call DYLIB,foo)
+	$(CC) $(CFLAGS) foo.c $(TMPDIR)/foo.dll.lib -Fe:`cygpath -w $@`
+else
+$(call RUN_BINFILE,foo): $(call DYLIB,foo)
+	$(CC) $(CFLAGS) foo.c -lfoo -o $(call RUN_BINFILE,foo) -L $(TMPDIR)
+endif
+
+$(call DYLIB,foo):
+	$(RUSTC) bar.rs
+	$(RUSTC) foo.rs
diff --git a/src/test/run-make/cdylib/bar.rs b/src/test/run-make/cdylib/bar.rs
new file mode 100644
index 00000000000..2c97298604c
--- /dev/null
+++ b/src/test/run-make/cdylib/bar.rs
@@ -0,0 +1,15 @@
+// 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 bar() {
+    println!("hello!");
+}
diff --git a/src/test/run-make/cdylib/foo.c b/src/test/run-make/cdylib/foo.c
new file mode 100644
index 00000000000..1c950427c65
--- /dev/null
+++ b/src/test/run-make/cdylib/foo.c
@@ -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.
+
+#include <assert.h>
+
+extern void foo();
+extern unsigned bar(unsigned a, unsigned b);
+
+int main() {
+  foo();
+  assert(bar(1, 2) == 3);
+  return 0;
+}
diff --git a/src/test/run-make/cdylib/foo.rs b/src/test/run-make/cdylib/foo.rs
new file mode 100644
index 00000000000..cdac6d19035
--- /dev/null
+++ b/src/test/run-make/cdylib/foo.rs
@@ -0,0 +1,23 @@
+// 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 bar;
+
+#[no_mangle]
+pub extern fn foo() {
+    bar::bar();
+}
+
+#[no_mangle]
+pub extern fn bar(a: u32, b: u32) -> u32 {
+    a + b
+}