about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/librustc/back/archive.rs45
-rw-r--r--src/librustc/back/link.rs284
-rw-r--r--src/librustc/back/lto.rs96
-rw-r--r--src/librustc/driver/driver.rs23
-rw-r--r--src/librustc/driver/session.rs30
-rw-r--r--src/librustc/lib.rs1
-rw-r--r--src/librustc/lib/llvm.rs11
-rw-r--r--src/librustc/metadata/encoder.rs14
-rw-r--r--src/librustc/metadata/loader.rs36
-rw-r--r--src/librustc/middle/trans/base.rs58
-rw-r--r--src/librustc/middle/trans/context.rs18
-rw-r--r--src/librustc/util/common.rs11
-rw-r--r--src/rustllvm/PassWrapper.cpp8
-rw-r--r--src/rustllvm/RustWrapper.cpp19
-rw-r--r--src/rustllvm/rustllvm.def.in3
-rw-r--r--src/rustllvm/rustllvm.h2
-rw-r--r--src/test/run-make/lto-smoke-c/Makefile11
-rw-r--r--src/test/run-make/lto-smoke-c/bar.c6
-rw-r--r--src/test/run-make/lto-smoke-c/foo.rs4
-rw-r--r--src/test/run-make/lto-smoke/Makefile6
-rw-r--r--src/test/run-make/lto-smoke/lib.rs1
-rw-r--r--src/test/run-make/lto-smoke/main.rs3
22 files changed, 538 insertions, 152 deletions
diff --git a/src/librustc/back/archive.rs b/src/librustc/back/archive.rs
index 2dd53f7d514..eec15f79827 100644
--- a/src/librustc/back/archive.rs
+++ b/src/librustc/back/archive.rs
@@ -20,6 +20,8 @@ use std::str;
 use extra::tempfile::TempDir;
 use syntax::abi;
 
+pub static METADATA_FILENAME: &'static str = "metadata";
+
 pub struct Archive {
     priv sess: Session,
     priv dst: Path,
@@ -40,7 +42,8 @@ fn run_ar(sess: Session, args: &str, cwd: Option<&Path>,
     }
     let o = Process::new(ar, args.as_slice(), opts).finish_with_output();
     if !o.status.success() {
-        sess.err(format!("{} failed with: {}", ar, o.status));
+        sess.err(format!("{} {} failed with: {}", ar, args.connect(" "),
+                         o.status));
         sess.note(format!("stdout ---\n{}", str::from_utf8(o.output)));
         sess.note(format!("stderr ---\n{}", str::from_utf8(o.error)));
         sess.abort_if_errors();
@@ -81,17 +84,40 @@ impl Archive {
     /// search in the relevant locations for a library named `name`.
     pub fn add_native_library(&mut self, name: &str) {
         let location = self.find_library(name);
-        self.add_archive(&location, name);
+        self.add_archive(&location, name, []);
     }
 
     /// Adds all of the contents of the rlib at the specified path to this
     /// archive.
-    pub fn add_rlib(&mut self, rlib: &Path) {
-        let name = rlib.filename_str().unwrap().split('-').next().unwrap();
-        self.add_archive(rlib, name);
+    ///
+    /// This ignores adding the bytecode from the rlib, and if LTO is enabled
+    /// then the object file also isn't added.
+    pub fn add_rlib(&mut self, rlib: &Path, name: &str, lto: bool) {
+        let object = format!("{}.o", name);
+        let bytecode = format!("{}.bc", name);
+        let mut ignore = ~[METADATA_FILENAME, bytecode.as_slice()];
+        if lto {
+            ignore.push(object.as_slice());
+        }
+        self.add_archive(rlib, name, ignore);
     }
 
-    fn add_archive(&mut self, archive: &Path, name: &str) {
+    /// Adds an arbitrary file to this archive
+    pub fn add_file(&mut self, file: &Path) {
+        run_ar(self.sess, "r", None, [&self.dst, file]);
+    }
+
+    /// Removes a file from this archive
+    pub fn remove_file(&mut self, file: &str) {
+        run_ar(self.sess, "d", None, [&self.dst, &Path::new(file)]);
+    }
+
+    pub fn files(&self) -> ~[~str] {
+        let output = run_ar(self.sess, "t", None, [&self.dst]);
+        str::from_utf8(output.output).lines().map(|s| s.to_owned()).collect()
+    }
+
+    fn add_archive(&mut self, archive: &Path, name: &str, skip: &[&str]) {
         let loc = TempDir::new("rsar").unwrap();
 
         // First, extract the contents of the archive to a temporary directory
@@ -102,10 +128,17 @@ impl Archive {
         // The reason for this is that archives are keyed off the name of the
         // files, so if two files have the same name they will override one
         // another in the archive (bad).
+        //
+        // We skip any files explicitly desired for skipping, and we also skip
+        // all SYMDEF files as these are just magical placeholders which get
+        // re-created when we make a new archive anyway.
         let files = fs::readdir(loc.path());
         let mut inputs = ~[];
         for file in files.iter() {
             let filename = file.filename_str().unwrap();
+            if skip.iter().any(|s| *s == filename) { continue }
+            if filename.contains(".SYMDEF") { continue }
+
             let filename = format!("r-{}-{}", name, filename);
             let new_filename = file.with_filename(filename);
             fs::rename(file, &new_filename);
diff --git a/src/librustc/back/link.rs b/src/librustc/back/link.rs
index 8ec0674f74f..451213ab694 100644
--- a/src/librustc/back/link.rs
+++ b/src/librustc/back/link.rs
@@ -9,8 +9,9 @@
 // except according to those terms.
 
 
-use back::archive::Archive;
+use back::archive::{Archive, METADATA_FILENAME};
 use back::rpath;
+use driver::driver::CrateTranslation;
 use driver::session::Session;
 use driver::session;
 use lib::llvm::llvm;
@@ -21,6 +22,7 @@ use metadata::{encoder, cstore, filesearch, csearch};
 use middle::trans::context::CrateContext;
 use middle::trans::common::gensym_name;
 use middle::ty;
+use util::common::time;
 use util::ppaux;
 
 use std::c_str::ToCStr;
@@ -32,6 +34,7 @@ use std::ptr;
 use std::run;
 use std::str;
 use std::io::fs;
+use extra::tempfile::TempDir;
 use syntax::abi;
 use syntax::ast;
 use syntax::ast_map::{path, path_mod, path_name, path_pretty_name};
@@ -84,15 +87,18 @@ pub fn WriteOutputFile(
 
 pub mod write {
 
+    use back::lto;
     use back::link::{WriteOutputFile, output_type};
     use back::link::{output_type_assembly, output_type_bitcode};
     use back::link::{output_type_exe, output_type_llvm_assembly};
     use back::link::{output_type_object};
+    use driver::driver::CrateTranslation;
     use driver::session::Session;
     use driver::session;
     use lib::llvm::llvm;
-    use lib::llvm::{ModuleRef, ContextRef};
+    use lib::llvm::{ModuleRef, TargetMachineRef, PassManagerRef};
     use lib;
+    use util::common::time;
 
     use std::c_str::ToCStr;
     use std::libc::{c_uint, c_int};
@@ -101,10 +107,11 @@ pub mod write {
     use std::str;
 
     pub fn run_passes(sess: Session,
-                      llcx: ContextRef,
-                      llmod: ModuleRef,
+                      trans: &CrateTranslation,
                       output_type: output_type,
                       output: &Path) {
+        let llmod = trans.module;
+        let llcx = trans.context;
         unsafe {
             llvm::LLVMInitializePasses();
 
@@ -191,49 +198,106 @@ pub mod write {
             }
 
             // Finally, run the actual optimization passes
-            llvm::LLVMRustRunFunctionPassManager(fpm, llmod);
-            llvm::LLVMRunPassManager(mpm, llmod);
+            time(sess.time_passes(), "llvm function passes", (), |()|
+                 llvm::LLVMRustRunFunctionPassManager(fpm, llmod));
+            time(sess.time_passes(), "llvm module passes", (), |()|
+                 llvm::LLVMRunPassManager(mpm, llmod));
 
             // Deallocate managers that we're now done with
             llvm::LLVMDisposePassManager(fpm);
             llvm::LLVMDisposePassManager(mpm);
 
-            if sess.opts.save_temps {
+            // Emit the bytecode if we're either saving our temporaries or
+            // emitting an rlib. Whenever an rlib is create, the bytecode is
+            // inserted into the archive in order to allow LTO against it.
+            if sess.opts.save_temps ||
+               sess.outputs.iter().any(|&o| o == session::OutputRlib) {
                 output.with_extension("bc").with_c_str(|buf| {
                     llvm::LLVMWriteBitcodeToFile(llmod, buf);
                 })
             }
 
-            // Create a codegen-specific pass manager to emit the actual
-            // assembly or object files. This may not end up getting used,
-            // but we make it anyway for good measure.
-            let cpm = llvm::LLVMCreatePassManager();
-            llvm::LLVMRustAddAnalysisPasses(tm, cpm, llmod);
-            llvm::LLVMRustAddLibraryInfo(cpm, llmod);
-
-            match output_type {
-                output_type_none => {}
-                output_type_bitcode => {
-                    output.with_c_str(|buf| {
+            if sess.lto() {
+                time(sess.time_passes(), "all lto passes", (), |()|
+                     lto::run(sess, llmod, tm, trans.reachable));
+
+                if sess.opts.save_temps {
+                    output.with_extension("lto.bc").with_c_str(|buf| {
                         llvm::LLVMWriteBitcodeToFile(llmod, buf);
                     })
                 }
-                output_type_llvm_assembly => {
-                    output.with_c_str(|output| {
-                        llvm::LLVMRustPrintModule(cpm, llmod, output)
-                    })
-                }
-                output_type_assembly => {
-                    WriteOutputFile(sess, tm, cpm, llmod, output, lib::llvm::AssemblyFile);
-                }
-                output_type_exe | output_type_object => {
-                    WriteOutputFile(sess, tm, cpm, llmod, output, lib::llvm::ObjectFile);
+            }
+
+            // A codegen-specific pass manager is used to generate object
+            // files for an LLVM module.
+            //
+            // Apparently each of these pass managers is a one-shot kind of
+            // thing, so we create a new one for each type of output. The
+            // pass manager passed to the closure should be ensured to not
+            // escape the closure itself, and the manager should only be
+            // used once.
+            fn with_codegen(tm: TargetMachineRef, llmod: ModuleRef,
+                            f: |PassManagerRef|) {
+                unsafe {
+                    let cpm = llvm::LLVMCreatePassManager();
+                    llvm::LLVMRustAddAnalysisPasses(tm, cpm, llmod);
+                    llvm::LLVMRustAddLibraryInfo(cpm, llmod);
+                    f(cpm);
+                    llvm::LLVMDisposePassManager(cpm);
                 }
             }
 
-            llvm::LLVMDisposePassManager(cpm);
+            time(sess.time_passes(), "codegen passes", (), |()| {
+                match output_type {
+                    output_type_none => {}
+                    output_type_bitcode => {
+                        output.with_c_str(|buf| {
+                            llvm::LLVMWriteBitcodeToFile(llmod, buf);
+                        })
+                    }
+                    output_type_llvm_assembly => {
+                        output.with_c_str(|output| {
+                            with_codegen(tm, llmod, |cpm| {
+                                llvm::LLVMRustPrintModule(cpm, llmod, output);
+                            })
+                        })
+                    }
+                    output_type_assembly => {
+                        with_codegen(tm, llmod, |cpm| {
+                            WriteOutputFile(sess, tm, cpm, llmod, output,
+                                            lib::llvm::AssemblyFile);
+                        });
+
+                        // If we're not using the LLVM assembler, this function
+                        // could be invoked specially with output_type_assembly,
+                        // so in this case we still want the metadata object
+                        // file.
+                        if sess.opts.output_type != output_type_assembly {
+                            with_codegen(tm, trans.metadata_module, |cpm| {
+                                let out = output.with_extension("metadata.o");
+                                WriteOutputFile(sess, tm, cpm,
+                                                trans.metadata_module, &out,
+                                                lib::llvm::ObjectFile);
+                            })
+                        }
+                    }
+                    output_type_exe | output_type_object => {
+                        with_codegen(tm, llmod, |cpm| {
+                            WriteOutputFile(sess, tm, cpm, llmod, output,
+                                            lib::llvm::ObjectFile);
+                        });
+                        with_codegen(tm, trans.metadata_module, |cpm| {
+                            let out = output.with_extension("metadata.o");
+                            WriteOutputFile(sess, tm, cpm,
+                                            trans.metadata_module, &out,
+                                            lib::llvm::ObjectFile);
+                        })
+                    }
+                }
+            });
 
             llvm::LLVMRustDisposeTargetMachine(tm);
+            llvm::LLVMDisposeModule(trans.metadata_module);
             llvm::LLVMDisposeModule(llmod);
             llvm::LLVMContextDispose(llcx);
             if sess.time_llvm_passes() { llvm::LLVMRustPrintPassTimings(); }
@@ -782,43 +846,25 @@ pub fn get_cc_prog(sess: Session) -> ~str {
 /// Perform the linkage portion of the compilation phase. This will generate all
 /// of the requested outputs for this compilation session.
 pub fn link_binary(sess: Session,
-                   crate_types: &[~str],
+                   trans: &CrateTranslation,
                    obj_filename: &Path,
-                   out_filename: &Path,
-                   lm: LinkMeta) {
+                   out_filename: &Path) {
+    // If we're generating a test executable, then ignore all other output
+    // styles at all other locations
     let outputs = if sess.opts.test {
-        // If we're generating a test executable, then ignore all other output
-        // styles at all other locations
         ~[session::OutputExecutable]
     } else {
-        // Always generate whatever was specified on the command line, but also
-        // look at what was in the crate file itself for generating output
-        // formats.
-        let mut outputs = sess.opts.outputs.clone();
-        for ty in crate_types.iter() {
-            if "bin" == *ty {
-                outputs.push(session::OutputExecutable);
-            } else if "dylib" == *ty || "lib" == *ty {
-                outputs.push(session::OutputDylib);
-            } else if "rlib" == *ty {
-                outputs.push(session::OutputRlib);
-            } else if "staticlib" == *ty {
-                outputs.push(session::OutputStaticlib);
-            }
-        }
-        if outputs.len() == 0 {
-            outputs.push(session::OutputExecutable);
-        }
-        outputs
+        (*sess.outputs).clone()
     };
 
     for output in outputs.move_iter() {
-        link_binary_output(sess, output, obj_filename, out_filename, lm);
+        link_binary_output(sess, trans, output, obj_filename, out_filename);
     }
 
-    // Remove the temporary object file if we aren't saving temps
+    // Remove the temporary object file and metadata if we aren't saving temps
     if !sess.opts.save_temps {
         fs::unlink(obj_filename);
+        fs::unlink(&obj_filename.with_extension("metadata.o"));
     }
 }
 
@@ -832,11 +878,11 @@ fn is_writeable(p: &Path) -> bool {
 }
 
 fn link_binary_output(sess: Session,
+                      trans: &CrateTranslation,
                       output: session::OutputStyle,
                       obj_filename: &Path,
-                      out_filename: &Path,
-                      lm: LinkMeta) {
-    let libname = output_lib_filename(lm);
+                      out_filename: &Path) {
+    let libname = output_lib_filename(trans.link);
     let out_filename = match output {
         session::OutputRlib => {
             out_filename.with_filename(format!("lib{}.rlib", libname))
@@ -874,7 +920,7 @@ fn link_binary_output(sess: Session,
 
     match output {
         session::OutputRlib => {
-            link_rlib(sess, obj_filename, &out_filename);
+            link_rlib(sess, Some(trans), obj_filename, &out_filename);
         }
         session::OutputStaticlib => {
             link_staticlib(sess, obj_filename, &out_filename);
@@ -894,9 +940,12 @@ fn link_binary_output(sess: Session,
 // rlib primarily contains the object file of the crate, but it also contains
 // all of the object files from native libraries. This is done by unzipping
 // native libraries and inserting all of the contents into this archive.
-fn link_rlib(sess: Session, obj_filename: &Path,
+fn link_rlib(sess: Session,
+             trans: Option<&CrateTranslation>, // None == no metadata/bytecode
+             obj_filename: &Path,
              out_filename: &Path) -> Archive {
     let mut a = Archive::create(sess, out_filename, obj_filename);
+
     for &(ref l, kind) in cstore::get_used_libraries(sess.cstore).iter() {
         match kind {
             cstore::NativeStatic => {
@@ -905,6 +954,48 @@ fn link_rlib(sess: Session, obj_filename: &Path,
             cstore::NativeFramework | cstore::NativeUnknown => {}
         }
     }
+
+    // Note that it is important that we add all of our non-object "magical
+    // files" *after* all of the object files in the archive. The reason for
+    // this is as follows:
+    //
+    // * When performing LTO, this archive will be modified to remove
+    //   obj_filename from above. The reason for this is described below.
+    //
+    // * When the system linker looks at an archive, it will attempt to
+    //   determine the architecture of the archive in order to see whether its
+    //   linkable.
+    //
+    //   The algorithm for this detections is: iterate over the files in the
+    //   archive. Skip magical SYMDEF names. Interpret the first file as an
+    //   object file. Read architecture from the object file.
+    //
+    // * As one can probably see, if "metadata" and "foo.bc" were placed
+    //   before all of the objects, then the architecture of this archive would
+    //   not be correctly inferred once 'foo.o' is removed.
+    //
+    // Basically, all this means is that this code should not move above the
+    // code above.
+    match trans {
+        Some(trans) => {
+            // Instead of putting the metadata in an object file section, rlibs
+            // contain the metadata in a separate file.
+            let metadata = obj_filename.with_filename(METADATA_FILENAME);
+            fs::File::create(&metadata).write(trans.metadata);
+            a.add_file(&metadata);
+            fs::unlink(&metadata);
+
+            // For LTO purposes, the bytecode of this library is also inserted
+            // into the archive.
+            let bc = obj_filename.with_extension("bc");
+            a.add_file(&bc);
+            if !sess.opts.save_temps {
+                fs::unlink(&bc);
+            }
+        }
+
+        None => {}
+    }
     return a;
 }
 
@@ -916,20 +1007,24 @@ fn link_rlib(sess: Session, obj_filename: &Path,
 //
 // Additionally, there's no way for us to link dynamic libraries, so we warn
 // about all dynamic library dependencies that they're not linked in.
+//
+// There's no need to include metadata in a static archive, so ensure to not
+// link in the metadata object file (and also don't prepare the archive with a
+// metadata file).
 fn link_staticlib(sess: Session, obj_filename: &Path, out_filename: &Path) {
-    let mut a = link_rlib(sess, obj_filename, out_filename);
+    let mut a = link_rlib(sess, None, obj_filename, out_filename);
     a.add_native_library("morestack");
 
     let crates = cstore::get_used_crates(sess.cstore, cstore::RequireStatic);
     for &(cnum, ref path) in crates.iter() {
+        let name = cstore::get_crate_data(sess.cstore, cnum).name;
         let p = match *path {
             Some(ref p) => p.clone(), None => {
-                sess.err(format!("could not find rlib for: `{}`",
-                                 cstore::get_crate_data(sess.cstore, cnum).name));
+                sess.err(format!("could not find rlib for: `{}`", name));
                 continue
             }
         };
-        a.add_rlib(&p);
+        a.add_rlib(&p, name, sess.lto());
         let native_libs = csearch::get_native_libraries(sess.cstore, cnum);
         for &(kind, ref lib) in native_libs.iter() {
             let name = match kind {
@@ -948,10 +1043,12 @@ fn link_staticlib(sess: Session, obj_filename: &Path, out_filename: &Path) {
 // links to all upstream files as well.
 fn link_natively(sess: Session, dylib: bool, obj_filename: &Path,
                  out_filename: &Path) {
+    let tmpdir = TempDir::new("rustc").expect("needs a temp dir");
     // The invocations of cc share some flags across platforms
     let cc_prog = get_cc_prog(sess);
     let mut cc_args = sess.targ_cfg.target_strs.cc_args.clone();
-    cc_args.push_all_move(link_args(sess, dylib, obj_filename, out_filename));
+    cc_args.push_all_move(link_args(sess, dylib, tmpdir.path(),
+                                    obj_filename, out_filename));
     if (sess.opts.debugging_opts & session::print_link_args) != 0 {
         println!("{} link args: '{}'", cc_prog, cc_args.connect("' '"));
     }
@@ -961,7 +1058,8 @@ fn link_natively(sess: Session, dylib: bool, obj_filename: &Path,
 
     // Invoke the system linker
     debug!("{} {}", cc_prog, cc_args.connect(" "));
-    let prog = run::process_output(cc_prog, cc_args);
+    let prog = time(sess.time_passes(), "running linker", (), |()|
+                    run::process_output(cc_prog, cc_args));
 
     if !prog.status.success() {
         sess.err(format!("linking with `{}` failed: {}", cc_prog, prog.status));
@@ -982,6 +1080,7 @@ fn link_natively(sess: Session, dylib: bool, obj_filename: &Path,
 
 fn link_args(sess: Session,
              dylib: bool,
+             tmpdir: &Path,
              obj_filename: &Path,
              out_filename: &Path) -> ~[~str] {
 
@@ -998,6 +1097,14 @@ fn link_args(sess: Session,
         ~"-o", out_filename.as_str().unwrap().to_owned(),
         obj_filename.as_str().unwrap().to_owned()]);
 
+    // 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 {
+        let metadata = obj_filename.with_extension("metadata.o");
+        args.push(metadata.as_str().unwrap().to_owned());
+    }
+
     if sess.targ_cfg.os == abi::OsLinux {
         // GNU-style linkers will use this to omit linking to libraries which
         // don't actually fulfill any relocations, but only for libraries which
@@ -1015,7 +1122,7 @@ fn link_args(sess: Session,
     }
 
     add_local_native_libraries(&mut args, sess);
-    add_upstream_rust_crates(&mut args, sess, dylib);
+    add_upstream_rust_crates(&mut args, sess, dylib, tmpdir);
     add_upstream_native_libraries(&mut args, sess);
 
     // # Telling the linker what we're doing
@@ -1098,7 +1205,7 @@ fn add_local_native_libraries(args: &mut ~[~str], sess: Session) {
 // dependencies will be linked when producing the final output (instead of
 // the intermediate rlib version)
 fn add_upstream_rust_crates(args: &mut ~[~str], sess: Session,
-                            dylib: bool) {
+                            dylib: bool, tmpdir: &Path) {
     // Converts a library file-stem into a cc -l argument
     fn unlib(config: @session::config, stem: &str) -> ~str {
         if stem.starts_with("lib") &&
@@ -1123,14 +1230,49 @@ fn add_upstream_rust_crates(args: &mut ~[~str], sess: Session,
         // dynamic libraries.
         let crates = cstore::get_used_crates(cstore, cstore::RequireStatic);
         if crates.iter().all(|&(_, ref p)| p.is_some()) {
-            for (_, path) in crates.move_iter() {
-                let path = path.unwrap();
-                args.push(path.as_str().unwrap().to_owned());
+            for (cnum, path) in crates.move_iter() {
+                let cratepath = path.unwrap();
+
+                // When performing LTO on an executable output, all of the
+                // bytecode from the upstream libraries has already been
+                // included in our object file output. We need to modify all of
+                // the upstream archives to remove their corresponding object
+                // file to make sure we don't pull the same code in twice.
+                //
+                // We must continue to link to the upstream archives to be sure
+                // to pull in native static dependencies. As the final caveat,
+                // on linux it is apparently illegal to link to a blank archive,
+                // so if an archive no longer has any object files in it after
+                // we remove `lib.o`, then don't link against it at all.
+                //
+                // If we're not doing LTO, then our job is simply to just link
+                // against the archive.
+                if sess.lto() {
+                    let name = cstore::get_crate_data(sess.cstore, cnum).name;
+                    time(sess.time_passes(), format!("altering {}.rlib", name),
+                         (), |()| {
+                        let dst = tmpdir.join(cratepath.filename().unwrap());
+                        fs::copy(&cratepath, &dst);
+                        let dst_str = dst.as_str().unwrap().to_owned();
+                        let mut archive = Archive::open(sess, dst);
+                        archive.remove_file(format!("{}.o", name));
+                        let files = archive.files();
+                        if files.iter().any(|s| s.ends_with(".o")) {
+                            args.push(dst_str);
+                        }
+                    });
+                } else {
+                    args.push(cratepath.as_str().unwrap().to_owned());
+                }
             }
             return;
         }
     }
 
+    // If we're performing LTO, then it should have been previously required
+    // that all upstream rust depenencies were available in an rlib format.
+    assert!(!sess.lto());
+
     // This is a fallback of three different  cases of linking:
     //
     // * When creating a dynamic library, all inputs are required to be dynamic
diff --git a/src/librustc/back/lto.rs b/src/librustc/back/lto.rs
new file mode 100644
index 00000000000..7c8c6aabd7e
--- /dev/null
+++ b/src/librustc/back/lto.rs
@@ -0,0 +1,96 @@
+// Copyright 2013 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 back::archive::Archive;
+use back::link;
+use driver::session;
+use lib::llvm::{ModuleRef, TargetMachineRef, llvm, True, False};
+use metadata::cstore;
+use util::common::time;
+
+use std::libc;
+use std::vec;
+
+pub fn run(sess: session::Session, llmod: ModuleRef,
+           tm: TargetMachineRef, reachable: &[~str]) {
+    // Make sure we actually can run LTO
+    for output in sess.outputs.iter() {
+        match *output {
+            session::OutputExecutable | session::OutputStaticlib => {}
+            _ => {
+                sess.fatal("lto can only be run for executables and \
+                            static library outputs");
+            }
+        }
+    }
+
+    // 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.
+    let crates = cstore::get_used_crates(sess.cstore, cstore::RequireStatic);
+    for (cnum, path) in crates.move_iter() {
+        let name = cstore::get_crate_data(sess.cstore, cnum).name;
+        let path = match path {
+            Some(p) => p,
+            None => {
+                sess.fatal(format!("could not find rlib for: `{}`", name));
+            }
+        };
+
+        let archive = Archive::open(sess, path);
+        debug!("reading {}", name);
+        let bc = time(sess.time_passes(), format!("read {}.bc", name), (), |_|
+                      archive.read(format!("{}.bc", name)));
+        let ptr = vec::raw::to_ptr(bc);
+        debug!("linking {}", name);
+        time(sess.time_passes(), format!("ll link {}", name), (), |()| unsafe {
+            if !llvm::LLVMRustLinkInExternalBitcode(llmod,
+                                                    ptr as *libc::c_char,
+                                                    bc.len() as libc::size_t) {
+                link::llvm_err(sess, format!("failed to load bc of `{}`", name));
+            }
+        });
+    }
+
+    // Internalize everything but the reachable symbols of the current module
+    let cstrs = reachable.map(|s| s.to_c_str());
+    let arr = cstrs.map(|c| c.with_ref(|p| p));
+    let ptr = vec::raw::to_ptr(arr);
+    unsafe {
+        llvm::LLVMRustRunRestrictionPass(llmod, ptr as **libc::c_char,
+                                         arr.len() as libc::size_t);
+    }
+
+    // Now we have one massive module inside of llmod. Time to run the
+    // LTO-specific optimization passes that LLVM provides.
+    //
+    // This code is based off the code found in llvm's LTO code generator:
+    //      tools/lto/LTOCodeGenerator.cpp
+    debug!("running the pass manager");
+    unsafe {
+        let pm = llvm::LLVMCreatePassManager();
+        llvm::LLVMRustAddAnalysisPasses(tm, pm, llmod);
+        "verify".with_c_str(|s| llvm::LLVMRustAddPass(pm, s));
+
+        let builder = llvm::LLVMPassManagerBuilderCreate();
+        llvm::LLVMPassManagerBuilderPopulateLTOPassManager(builder, pm,
+            /* Internalize = */ False,
+            /* RunInliner = */ True);
+        llvm::LLVMPassManagerBuilderDispose(builder);
+
+        "verify".with_c_str(|s| llvm::LLVMRustAddPass(pm, s));
+
+        time(sess.time_passes(), "LTO pases", (), |()|
+             llvm::LLVMRunPassManager(pm, llmod));
+
+        llvm::LLVMDisposePassManager(pm);
+    }
+    debug!("lto done");
+}
diff --git a/src/librustc/driver/driver.rs b/src/librustc/driver/driver.rs
index 6b76fdc52f8..c0ee53da970 100644
--- a/src/librustc/driver/driver.rs
+++ b/src/librustc/driver/driver.rs
@@ -165,10 +165,7 @@ pub fn phase_2_configure_and_expand(sess: Session,
     let time_passes = sess.time_passes();
 
     *sess.building_library = session::building_library(sess.opts, &crate);
-    let want_exe = sess.opts.outputs.iter().any(|&o| o == OutputExecutable);
-    if *sess.building_library && want_exe {
-        sess.err("cannot build both a library and an executable");
-    }
+    *sess.outputs = session::collect_outputs(sess.opts, &crate);
 
     time(time_passes, "gated feature checking", (), |_|
          front::feature_gate::check_crate(sess, &crate));
@@ -335,8 +332,10 @@ pub fn phase_3_run_analysis_passes(sess: Session,
 pub struct CrateTranslation {
     context: ContextRef,
     module: ModuleRef,
+    metadata_module: ModuleRef,
     link: LinkMeta,
-    crate_types: ~[~str],
+    metadata: ~[u8],
+    reachable: ~[~str],
 }
 
 /// Run the translation phase to LLVM, after which the AST and analysis can
@@ -362,8 +361,7 @@ pub fn phase_5_run_llvm_passes(sess: Session,
 
         time(sess.time_passes(), "LLVM passes", (), |_|
             link::write::run_passes(sess,
-                                    trans.context,
-                                    trans.module,
+                                    trans,
                                     output_type,
                                     &asm_filename));
 
@@ -376,8 +374,7 @@ pub fn phase_5_run_llvm_passes(sess: Session,
     } else {
         time(sess.time_passes(), "LLVM passes", (), |_|
             link::write::run_passes(sess,
-                                    trans.context,
-                                    trans.module,
+                                    trans,
                                     sess.opts.output_type,
                                     &outputs.obj_filename));
     }
@@ -390,10 +387,9 @@ pub fn phase_6_link_output(sess: Session,
                            outputs: &OutputFilenames) {
     time(sess.time_passes(), "linking", (), |_|
          link::link_binary(sess,
-                           trans.crate_types,
+                           trans,
                            &outputs.obj_filename,
-                           &outputs.out_filename,
-                           trans.link));
+                           &outputs.out_filename));
 }
 
 pub fn stop_after_phase_3(sess: Session) -> bool {
@@ -838,7 +834,8 @@ pub fn build_session_(sopts: @session::options,
         building_library: @mut false,
         working_dir: os::getcwd(),
         lints: @mut HashMap::new(),
-        node_id: @mut 1
+        node_id: @mut 1,
+        outputs: @mut ~[],
     }
 }
 
diff --git a/src/librustc/driver/session.rs b/src/librustc/driver/session.rs
index 2d1d7033300..30d5b7780cf 100644
--- a/src/librustc/driver/session.rs
+++ b/src/librustc/driver/session.rs
@@ -17,6 +17,7 @@ use metadata::filesearch;
 use metadata;
 use middle::lint;
 
+use syntax::attr::AttrMetaMethods;
 use syntax::ast::NodeId;
 use syntax::ast::{int_ty, uint_ty};
 use syntax::codemap::Span;
@@ -67,6 +68,7 @@ pub static use_softfp:              uint = 1 << 26;
 pub static gen_crate_map:           uint = 1 << 27;
 pub static prefer_dynamic:          uint = 1 << 28;
 pub static no_integrated_as:        uint = 1 << 29;
+pub static lto:                     uint = 1 << 30;
 
 pub fn debugging_opts_map() -> ~[(&'static str, &'static str, uint)] {
     ~[("verbose", "in general, enable more debug printouts", verbose),
@@ -120,6 +122,7 @@ pub fn debugging_opts_map() -> ~[(&'static str, &'static str, uint)] {
      ("prefer-dynamic", "Prefer dynamic linking to static linking", prefer_dynamic),
      ("no-integrated-as",
       "Use external assembler rather than LLVM's integrated one", no_integrated_as),
+     ("lto", "Perform LLVM link-time optimizations", lto),
     ]
 }
 
@@ -208,6 +211,7 @@ pub struct Session_ {
     working_dir: Path,
     lints: @mut HashMap<ast::NodeId, ~[(lint::lint, codemap::Span, ~str)]>,
     node_id: @mut ast::NodeId,
+    outputs: @mut ~[OutputStyle],
 }
 
 pub type Session = @Session_;
@@ -341,6 +345,9 @@ impl Session_ {
     pub fn no_integrated_as(&self) -> bool {
         self.debugging_opt(no_integrated_as)
     }
+    pub fn lto(&self) -> bool {
+        self.debugging_opt(lto)
+    }
 
     // pointless function, now...
     pub fn str_of(&self, id: ast::Ident) -> @str {
@@ -408,6 +415,29 @@ pub fn building_library(options: &options, crate: &ast::Crate) -> bool {
     }
 }
 
+pub fn collect_outputs(options: &options, crate: &ast::Crate) -> ~[OutputStyle] {
+    let mut base = options.outputs.clone();
+    let mut iter = crate.attrs.iter().filter_map(|a| {
+        if "crate_type" == a.name() {
+            match a.value_str() {
+                Some(n) if "rlib" == n => Some(OutputRlib),
+                Some(n) if "dylib" == n => Some(OutputDylib),
+                Some(n) if "lib" == n => Some(OutputDylib),
+                Some(n) if "staticlib" == n => Some(OutputStaticlib),
+                Some(n) if "bin" == n => Some(OutputExecutable),
+                _ => None
+            }
+        } else {
+            None
+        }
+    });
+    base.extend(&mut iter);
+    if base.len() == 0 {
+        base.push(OutputExecutable);
+    }
+    return base;
+}
+
 pub fn sess_os_to_meta_os(os: abi::Os) -> metadata::loader::Os {
     use metadata::loader;
 
diff --git a/src/librustc/lib.rs b/src/librustc/lib.rs
index 2185617c79f..3de33e13ecc 100644
--- a/src/librustc/lib.rs
+++ b/src/librustc/lib.rs
@@ -99,6 +99,7 @@ pub mod back {
     pub mod x86_64;
     pub mod rpath;
     pub mod target_strs;
+    pub mod lto;
 }
 
 pub mod metadata;
diff --git a/src/librustc/lib/llvm.rs b/src/librustc/lib/llvm.rs
index 3b0925164c9..7039eced976 100644
--- a/src/librustc/lib/llvm.rs
+++ b/src/librustc/lib/llvm.rs
@@ -1403,6 +1403,11 @@ pub mod llvm {
         pub fn LLVMPassManagerBuilderPopulateFunctionPassManager(
             PMB: PassManagerBuilderRef,
             PM: PassManagerRef);
+        pub fn LLVMPassManagerBuilderPopulateLTOPassManager(
+            PMB: PassManagerBuilderRef,
+            PM: PassManagerRef,
+            Internalize: Bool,
+            RunInliner: Bool);
 
         /** Destroys a memory buffer. */
         pub fn LLVMDisposeMemoryBuffer(MemBuf: MemoryBufferRef);
@@ -1736,6 +1741,12 @@ pub mod llvm {
         pub fn LLVMRustSetNormalizedTarget(M: ModuleRef, triple: *c_char);
         pub fn LLVMRustAddAlwaysInlinePass(P: PassManagerBuilderRef,
                                            AddLifetimes: bool);
+        pub fn LLVMRustLinkInExternalBitcode(M: ModuleRef,
+                                             bc: *c_char,
+                                             len: size_t) -> bool;
+        pub fn LLVMRustRunRestrictionPass(M: ModuleRef,
+                                          syms: **c_char,
+                                          len: size_t);
     }
 }
 
diff --git a/src/librustc/metadata/encoder.rs b/src/librustc/metadata/encoder.rs
index e0f63a92987..bf50da3d789 100644
--- a/src/librustc/metadata/encoder.rs
+++ b/src/librustc/metadata/encoder.rs
@@ -21,13 +21,14 @@ use middle::ty;
 use middle::typeck;
 use middle;
 
+use std::cast;
 use std::hashmap::{HashMap, HashSet};
-use std::io::{Writer, Seek, Decorator};
 use std::io::mem::MemWriter;
+use std::io::{Writer, Seek, Decorator};
 use std::str;
+use std::util;
 use std::vec;
 
-use extra::flate;
 use extra::serialize::Encodable;
 use extra;
 
@@ -47,8 +48,6 @@ use syntax::parse::token;
 use syntax;
 use writer = extra::ebml::writer;
 
-use std::cast;
-
 // used by astencode:
 type abbrev_map = @mut HashMap<ty::t, tyencode::ty_abbrev>;
 
@@ -1871,10 +1870,9 @@ pub fn encode_metadata(parms: EncodeParams, crate: &Crate) -> ~[u8] {
     // remaining % 4 bytes.
     wr.write(&[0u8, 0u8, 0u8, 0u8]);
 
-    let writer_bytes: &mut ~[u8] = wr.inner_mut_ref();
-
-    metadata_encoding_version.to_owned() +
-        flate::deflate_bytes(*writer_bytes)
+    // This is a horrible thing to do to the outer MemWriter, but thankfully we
+    // don't use it again so... it's ok right?
+    return util::replace(wr.inner_mut_ref(), ~[]);
 }
 
 // Get the encoded string for a type
diff --git a/src/librustc/metadata/loader.rs b/src/librustc/metadata/loader.rs
index 40fca0f42f1..5b1385c7579 100644
--- a/src/librustc/metadata/loader.rs
+++ b/src/librustc/metadata/loader.rs
@@ -10,7 +10,7 @@
 
 //! Finds crate binaries and loads their metadata
 
-use back::archive::Archive;
+use back::archive::{Archive, METADATA_FILENAME};
 use driver::session::Session;
 use lib::llvm::{False, llvm, ObjectFile, mk_section_iter};
 use metadata::decoder;
@@ -27,7 +27,6 @@ use syntax::attr::AttrMetaMethods;
 use std::c_str::ToCStr;
 use std::cast;
 use std::io;
-use std::libc;
 use std::num;
 use std::option;
 use std::os::consts::{macos, freebsd, linux, android, win32};
@@ -102,8 +101,7 @@ impl Context {
                     if candidate && existing {
                         FileMatches
                     } else if candidate {
-                        match get_metadata_section(self.sess, self.os, path,
-                                                   crate_name) {
+                        match get_metadata_section(self.sess, self.os, path) {
                             Some(cvec) =>
                                 if crate_matches(cvec, self.metas, self.hash) {
                                     debug!("found {} with matching metadata",
@@ -271,22 +269,15 @@ pub fn metadata_matches(extern_metas: &[@ast::MetaItem],
     local_metas.iter().all(|needed| attr::contains(extern_metas, *needed))
 }
 
-fn get_metadata_section(sess: Session, os: Os, filename: &Path,
-                        crate_name: &str) -> Option<@~[u8]> {
+fn get_metadata_section(sess: Session, os: Os, filename: &Path) -> Option<@~[u8]> {
+    if filename.filename_str().unwrap().ends_with(".rlib") {
+        let archive = Archive::open(sess, filename.clone());
+        return Some(@archive.read(METADATA_FILENAME));
+    }
     unsafe {
-        let mb = if filename.filename_str().unwrap().ends_with(".rlib") {
-            let archive = Archive::open(sess, filename.clone());
-            let contents = archive.read(crate_name + ".o");
-            let ptr = vec::raw::to_ptr(contents);
-            crate_name.with_c_str(|name| {
-                llvm::LLVMCreateMemoryBufferWithMemoryRangeCopy(
-                    ptr as *i8, contents.len() as libc::size_t, name)
-            })
-        } else {
-            filename.with_c_str(|buf| {
-                llvm::LLVMRustCreateMemoryBufferWithContentsOfFile(buf)
-            })
-        };
+        let mb = filename.with_c_str(|buf| {
+            llvm::LLVMRustCreateMemoryBufferWithContentsOfFile(buf)
+        });
         if mb as int == 0 { return None }
         let of = match ObjectFile::new(mb) {
             Some(of) => of,
@@ -356,12 +347,7 @@ pub fn list_file_metadata(sess: Session,
                           os: Os,
                           path: &Path,
                           out: @mut io::Writer) {
-    // guess the crate name from the pathname
-    let crate_name = path.filename_str().unwrap();
-    let crate_name = if crate_name.starts_with("lib") {
-        crate_name.slice_from(3) } else { crate_name };
-    let crate_name = crate_name.split('-').next().unwrap();
-    match get_metadata_section(sess, os, path, crate_name) {
+    match get_metadata_section(sess, os, path) {
       option::Some(bytes) => decoder::list_crate_metadata(intr, bytes, out),
       option::None => {
         write!(out, "could not find metadata in {}.\n", path.display())
diff --git a/src/librustc/middle/trans/base.rs b/src/librustc/middle/trans/base.rs
index 72b9fc83c4a..8a89cd35d0e 100644
--- a/src/librustc/middle/trans/base.rs
+++ b/src/librustc/middle/trans/base.rs
@@ -2929,7 +2929,7 @@ pub fn symname(sess: session::Session, name: &str,
 }
 
 pub fn decl_crate_map(sess: session::Session, mapmeta: LinkMeta,
-                      llmod: ModuleRef) -> ValueRef {
+                      llmod: ModuleRef) -> (~str, ValueRef) {
     let targ_cfg = sess.targ_cfg;
     let int_type = Type::int(targ_cfg.arch);
     let mut n_subcrates = 1;
@@ -2963,7 +2963,7 @@ pub fn decl_crate_map(sess: session::Session, mapmeta: LinkMeta,
         lib::llvm::SetLinkage(map, lib::llvm::ExternalLinkage);
     }
 
-    return map;
+    return (sym_name, map);
 }
 
 pub fn fill_crate_map(ccx: @mut CrateContext, map: ValueRef) {
@@ -3044,19 +3044,26 @@ pub fn crate_ctxt_to_encode_parms<'r>(cx: &'r CrateContext, ie: encoder::encode_
         }
 }
 
-pub fn write_metadata(cx: &CrateContext, crate: &ast::Crate) {
-    if !*cx.sess.building_library { return; }
+pub fn write_metadata(cx: &CrateContext, crate: &ast::Crate) -> ~[u8] {
+    use extra::flate;
+
+    if !*cx.sess.building_library { return ~[]; }
 
     let encode_inlined_item: encoder::encode_inlined_item =
         |ecx, ebml_w, path, ii|
         astencode::encode_inlined_item(ecx, ebml_w, path, ii, cx.maps);
 
     let encode_parms = crate_ctxt_to_encode_parms(cx, encode_inlined_item);
-    let llmeta = C_bytes(encoder::encode_metadata(encode_parms, crate));
+    let metadata = encoder::encode_metadata(encode_parms, crate);
+    let compressed = encoder::metadata_encoding_version +
+                        flate::deflate_bytes(metadata);
+    let llmeta = C_bytes(compressed);
     let llconst = C_struct([llmeta], false);
-    let mut llglobal = "rust_metadata".with_c_str(|buf| {
+    let name = format!("rust_metadata_{}_{}_{}", cx.link_meta.name,
+                       cx.link_meta.vers, cx.link_meta.extras_hash);
+    let llglobal = name.with_c_str(|buf| {
         unsafe {
-            llvm::LLVMAddGlobal(cx.llmod, val_ty(llconst).to_ref(), buf)
+            llvm::LLVMAddGlobal(cx.metadata_llmod, val_ty(llconst).to_ref(), buf)
         }
     });
     unsafe {
@@ -3064,16 +3071,8 @@ pub fn write_metadata(cx: &CrateContext, crate: &ast::Crate) {
         cx.sess.targ_cfg.target_strs.meta_sect_name.with_c_str(|buf| {
             llvm::LLVMSetSection(llglobal, buf)
         });
-        lib::llvm::SetLinkage(llglobal, lib::llvm::InternalLinkage);
-
-        let t_ptr_i8 = Type::i8p();
-        llglobal = llvm::LLVMConstBitCast(llglobal, t_ptr_i8.to_ref());
-        let llvm_used = "llvm.used".with_c_str(|buf| {
-            llvm::LLVMAddGlobal(cx.llmod, Type::array(&t_ptr_i8, 1).to_ref(), buf)
-        });
-        lib::llvm::SetLinkage(llvm_used, lib::llvm::AppendingLinkage);
-        llvm::LLVMSetInitializer(llvm_used, C_array(t_ptr_i8, [llglobal]));
     }
+    return metadata;
 }
 
 pub fn trans_crate(sess: session::Session,
@@ -3140,7 +3139,7 @@ pub fn trans_crate(sess: session::Session,
     }
 
     // Translate the metadata.
-    write_metadata(ccx, &crate);
+    let metadata = write_metadata(ccx, &crate);
     if ccx.sess.trans_stats() {
         println("--- trans stats ---");
         println!("n_static_tydescs: {}", ccx.stats.n_static_tydescs);
@@ -3174,18 +3173,27 @@ pub fn trans_crate(sess: session::Session,
     let llcx = ccx.llcx;
     let link_meta = ccx.link_meta;
     let llmod = ccx.llmod;
-    let crate_types = crate.attrs.iter().filter_map(|a| {
-        if "crate_type" == a.name() {
-            a.value_str()
-        } else {
-            None
-        }
-    }).map(|a| a.to_owned()).collect();
+    let mut reachable = ccx.reachable.iter().filter_map(|id| {
+        ccx.item_symbols.find(id).map(|s| s.to_owned())
+    }).to_owned_vec();
+
+    // Make sure that some other crucial symbols are not eliminated from the
+    // module. This includes the main function (main/amain elsewhere), the crate
+    // map (used for debug log settings and I/O), and finally the curious
+    // rust_stack_exhausted symbol. This symbol is required for use by the
+    // libmorestack library that we link in, so we must ensure that this symbol
+    // is not internalized (if defined in the crate).
+    reachable.push(ccx.crate_map_name.to_owned());
+    reachable.push(~"main");
+    reachable.push(~"amain");
+    reachable.push(~"rust_stack_exhausted");
 
     return CrateTranslation {
         context: llcx,
         module: llmod,
         link: link_meta,
-        crate_types: crate_types,
+        metadata_module: ccx.metadata_llmod,
+        metadata: metadata,
+        reachable: reachable,
     };
 }
diff --git a/src/librustc/middle/trans/context.rs b/src/librustc/middle/trans/context.rs
index 851a1233dcc..c483a0f48f8 100644
--- a/src/librustc/middle/trans/context.rs
+++ b/src/librustc/middle/trans/context.rs
@@ -42,6 +42,7 @@ pub struct CrateContext {
      sess: session::Session,
      llmod: ModuleRef,
      llcx: ContextRef,
+     metadata_llmod: ModuleRef,
      td: TargetData,
      tn: TypeNames,
      externs: ExternMap,
@@ -110,6 +111,7 @@ pub struct CrateContext {
      opaque_vec_type: Type,
      builder: BuilderRef_res,
      crate_map: ValueRef,
+     crate_map_name: ~str,
      // Set when at least one function uses GC. Needed so that
      // decl_gc_metadata knows whether to link to the module metadata, which
      // is not emitted by LLVM's GC pass when no functions use GC.
@@ -134,11 +136,18 @@ impl CrateContext {
             let llmod = name.with_c_str(|buf| {
                 llvm::LLVMModuleCreateWithNameInContext(buf, llcx)
             });
+            let metadata_llmod = format!("{}_metadata", name).with_c_str(|buf| {
+                llvm::LLVMModuleCreateWithNameInContext(buf, llcx)
+            });
             let data_layout: &str = sess.targ_cfg.target_strs.data_layout;
             let targ_triple: &str = sess.targ_cfg.target_strs.target_triple;
-            data_layout.with_c_str(|buf| llvm::LLVMSetDataLayout(llmod, buf));
+            data_layout.with_c_str(|buf| {
+                llvm::LLVMSetDataLayout(llmod, buf);
+                llvm::LLVMSetDataLayout(metadata_llmod, buf);
+            });
             targ_triple.with_c_str(|buf| {
-                llvm::LLVMRustSetNormalizedTarget(llmod, buf)
+                llvm::LLVMRustSetNormalizedTarget(llmod, buf);
+                llvm::LLVMRustSetNormalizedTarget(metadata_llmod, buf);
             });
             let targ_cfg = sess.targ_cfg;
 
@@ -159,7 +168,8 @@ impl CrateContext {
             tn.associate_type("tydesc", &tydesc_type);
             tn.associate_type("str_slice", &str_slice_ty);
 
-            let crate_map = decl_crate_map(sess, link_meta, llmod);
+            let (crate_map_name, crate_map) = decl_crate_map(sess, link_meta,
+                                                             llmod);
             let dbg_cx = if sess.opts.debuginfo {
                 Some(debuginfo::CrateDebugContext::new(llmod, name.to_owned()))
             } else {
@@ -174,6 +184,7 @@ impl CrateContext {
                   sess: sess,
                   llmod: llmod,
                   llcx: llcx,
+                  metadata_llmod: metadata_llmod,
                   td: td,
                   tn: tn,
                   externs: HashMap::new(),
@@ -229,6 +240,7 @@ impl CrateContext {
                   opaque_vec_type: opaque_vec_type,
                   builder: BuilderRef_res(llvm::LLVMCreateBuilderInContext(llcx)),
                   crate_map: crate_map,
+                  crate_map_name: crate_map_name,
                   uses_gc: false,
                   dbg_cx: dbg_cx,
                   do_not_commit_warning_issued: false
diff --git a/src/librustc/util/common.rs b/src/librustc/util/common.rs
index 643e0440860..ee068d7e6a1 100644
--- a/src/librustc/util/common.rs
+++ b/src/librustc/util/common.rs
@@ -15,14 +15,23 @@ use syntax::visit;
 use syntax::visit::Visitor;
 
 use std::hashmap::HashSet;
+use std::local_data;
 use extra;
 
 pub fn time<T, U>(do_it: bool, what: &str, u: U, f: |U| -> T) -> T {
+    local_data_key!(depth: uint);
     if !do_it { return f(u); }
+
+    let old = local_data::get(depth, |d| d.map(|a| *a).unwrap_or(0));
+    local_data::set(depth, old + 1);
+
     let start = extra::time::precise_time_s();
     let rv = f(u);
     let end = extra::time::precise_time_s();
-    println!("time: {:3.3f} s\t{}", end - start, what);
+
+    println!("{}time: {:3.3f} s\t{}", "  ".repeat(old), end - start, what);
+    local_data::set(depth, old);
+
     rv
 }
 
diff --git a/src/rustllvm/PassWrapper.cpp b/src/rustllvm/PassWrapper.cpp
index 0ae8991b2e7..76e24faebd9 100644
--- a/src/rustllvm/PassWrapper.cpp
+++ b/src/rustllvm/PassWrapper.cpp
@@ -211,3 +211,11 @@ extern "C" void
 LLVMRustAddAlwaysInlinePass(LLVMPassManagerBuilderRef PMB, bool AddLifetimes) {
     unwrap(PMB)->Inliner = createAlwaysInlinerPass(AddLifetimes);
 }
+
+extern "C" void
+LLVMRustRunRestrictionPass(LLVMModuleRef M, char **symbols, size_t len) {
+    PassManager passes;
+    ArrayRef<const char*> ref(symbols, len);
+    passes.add(llvm::createInternalizePass(ref));
+    passes.run(*unwrap(M));
+}
diff --git a/src/rustllvm/RustWrapper.cpp b/src/rustllvm/RustWrapper.cpp
index 484cded3147..fb611dd15c2 100644
--- a/src/rustllvm/RustWrapper.cpp
+++ b/src/rustllvm/RustWrapper.cpp
@@ -539,3 +539,22 @@ extern "C" char *LLVMTypeToString(LLVMTypeRef Type) {
     unwrap<llvm::Type>(Type)->print(os);
     return strdup(os.str().data());
 }
+
+extern "C" bool
+LLVMRustLinkInExternalBitcode(LLVMModuleRef dst, char *bc, size_t len) {
+    Module *Dst = unwrap(dst);
+    MemoryBuffer* buf = MemoryBuffer::getMemBufferCopy(StringRef(bc, len));
+    std::string Err;
+    Module *Src = llvm::getLazyBitcodeModule(buf, Dst->getContext(), &Err);
+    if (Src == NULL) {
+        LLVMRustError = Err.c_str();
+        delete buf;
+        return false;
+    }
+
+    if (Linker::LinkModules(Dst, Src, Linker::DestroySource, &Err)) {
+        LLVMRustError = Err.c_str();
+        return false;
+    }
+    return true;
+}
diff --git a/src/rustllvm/rustllvm.def.in b/src/rustllvm/rustllvm.def.in
index d8ec1c86840..ee82fa80f87 100644
--- a/src/rustllvm/rustllvm.def.in
+++ b/src/rustllvm/rustllvm.def.in
@@ -629,3 +629,6 @@ LLVMTypeToString
 LLVMAddColdAttribute
 LLVMCreateMemoryBufferWithMemoryRange
 LLVMCreateMemoryBufferWithMemoryRangeCopy
+LLVMPassManagerBuilderPopulateLTOPassManager
+LLVMRustLinkInExternalBitcode
+LLVMRustRunRestrictionPass
diff --git a/src/rustllvm/rustllvm.h b/src/rustllvm/rustllvm.h
index 94bb00aab77..ef7199a6ca8 100644
--- a/src/rustllvm/rustllvm.h
+++ b/src/rustllvm/rustllvm.h
@@ -19,6 +19,7 @@
 #include "llvm/Analysis/Verifier.h"
 #include "llvm/Analysis/Passes.h"
 #include "llvm/Analysis/Lint.h"
+#include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/Triple.h"
 #include "llvm/ADT/DenseSet.h"
 #include "llvm/Assembly/Parser.h"
@@ -47,6 +48,7 @@
 #include "llvm/Transforms/Vectorize.h"
 #include "llvm/DebugInfo.h"
 #include "llvm/DIBuilder.h"
+#include "llvm/Bitcode/ReaderWriter.h"
 #include "llvm-c/Core.h"
 #include "llvm-c/BitReader.h"
 #include "llvm-c/ExecutionEngine.h"
diff --git a/src/test/run-make/lto-smoke-c/Makefile b/src/test/run-make/lto-smoke-c/Makefile
new file mode 100644
index 00000000000..a491fda7dc2
--- /dev/null
+++ b/src/test/run-make/lto-smoke-c/Makefile
@@ -0,0 +1,11 @@
+-include ../tools.mk
+
+ifneq ($(shell uname),Darwin)
+	EXTRAFLAGS := -lm -lrt -ldl -lpthread
+endif
+
+all:
+	$(RUSTC) foo.rs -Z gen-crate-map -Z lto
+	ln -s $(call STATICLIB,foo-*) $(call STATICLIB,foo)
+	$(CC) bar.c -lfoo -o $(call RUN,bar) $(EXTRAFLAGS) -lstdc++
+	$(call RUN,bar)
diff --git a/src/test/run-make/lto-smoke-c/bar.c b/src/test/run-make/lto-smoke-c/bar.c
new file mode 100644
index 00000000000..bb4036b06e1
--- /dev/null
+++ b/src/test/run-make/lto-smoke-c/bar.c
@@ -0,0 +1,6 @@
+void foo();
+
+int main() {
+    foo();
+    return 0;
+}
diff --git a/src/test/run-make/lto-smoke-c/foo.rs b/src/test/run-make/lto-smoke-c/foo.rs
new file mode 100644
index 00000000000..3da09eb6bb6
--- /dev/null
+++ b/src/test/run-make/lto-smoke-c/foo.rs
@@ -0,0 +1,4 @@
+#[crate_type = "staticlib"];
+
+#[no_mangle]
+pub extern "C" fn foo() {}
diff --git a/src/test/run-make/lto-smoke/Makefile b/src/test/run-make/lto-smoke/Makefile
new file mode 100644
index 00000000000..4652556d344
--- /dev/null
+++ b/src/test/run-make/lto-smoke/Makefile
@@ -0,0 +1,6 @@
+-include ../tools.mk
+
+all:
+	$(RUSTC) lib.rs
+	$(RUSTC) main.rs -Z lto
+	$(call RUN,main)
diff --git a/src/test/run-make/lto-smoke/lib.rs b/src/test/run-make/lto-smoke/lib.rs
new file mode 100644
index 00000000000..3cdacc96ee9
--- /dev/null
+++ b/src/test/run-make/lto-smoke/lib.rs
@@ -0,0 +1 @@
+#[crate_type = "rlib"];
diff --git a/src/test/run-make/lto-smoke/main.rs b/src/test/run-make/lto-smoke/main.rs
new file mode 100644
index 00000000000..a3ed6772926
--- /dev/null
+++ b/src/test/run-make/lto-smoke/main.rs
@@ -0,0 +1,3 @@
+extern mod lib;
+
+fn main() {}