about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/librustc/back/archive.rs33
-rw-r--r--src/librustc/back/link.rs255
-rw-r--r--src/librustc/back/lto.rs96
-rw-r--r--src/librustc/driver/driver.rs10
-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/middle/trans/base.rs41
-rw-r--r--src/librustc/middle/trans/context.rs5
-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
20 files changed, 432 insertions, 124 deletions
diff --git a/src/librustc/back/archive.rs b/src/librustc/back/archive.rs
index d0225866cbe..eec15f79827 100644
--- a/src/librustc/back/archive.rs
+++ b/src/librustc/back/archive.rs
@@ -42,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();
@@ -88,9 +89,17 @@ impl Archive {
 
     /// 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, [METADATA_FILENAME]);
+    ///
+    /// 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);
     }
 
     /// Adds an arbitrary file to this archive
@@ -98,6 +107,16 @@ impl Archive {
         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();
 
@@ -109,11 +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 cbe2dbebc06..fdf47e2db73 100644
--- a/src/librustc/back/link.rs
+++ b/src/librustc/back/link.rs
@@ -22,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;
@@ -33,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};
@@ -85,6 +87,7 @@ 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};
@@ -93,8 +96,9 @@ pub mod write {
     use driver::session::Session;
     use driver::session;
     use lib::llvm::llvm;
-    use lib::llvm::ModuleRef;
+    use lib::llvm::{ModuleRef, TargetMachineRef, PassManagerRef};
     use lib;
+    use util::common::time;
 
     use std::c_str::ToCStr;
     use std::libc::{c_uint, c_int};
@@ -194,19 +198,36 @@ 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);
                 })
             }
 
+            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);
+                    })
+                }
+            }
+
             // A codegen-specific pass manager is used to generate object
             // files for an LLVM module.
             //
@@ -217,41 +238,54 @@ pub mod write {
             // used once.
             fn with_codegen(tm: TargetMachineRef, llmod: ModuleRef,
                             f: |PassManagerRef|) {
-                let cpm = llvm::LLVMCreatePassManager();
-                llvm::LLVMRustAddAnalysisPasses(tm, cpm, llmod);
-                llvm::LLVMRustAddLibraryInfo(cpm, llmod);
-                f(cpm);
-                llvm::LLVMDisposePassManager(cpm);
-
+                unsafe {
+                    let cpm = llvm::LLVMCreatePassManager();
+                    llvm::LLVMRustAddAnalysisPasses(tm, cpm, llmod);
+                    llvm::LLVMRustAddLibraryInfo(cpm, llmod);
+                    f(cpm);
+                    llvm::LLVMDisposePassManager(cpm);
+                }
             }
 
-            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);
+            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_assembly => {
-                    with_codegen(tm, llmod, |cpm| {
-                        WriteOutputFile(sess, tm, cpm, llmod, output,
-                                        lib::llvm::AssemblyFile);
-                    });
-
-                    // windows will invoke this function with an assembly output
-                    // type when it's actually generating an object file. This
-                    // is because g++ is used to compile the assembly instead of
-                    // having LLVM directly output an object file. Regardless,
-                    // in this case, we're going to possibly need a metadata
-                    // file.
-                    if sess.opts.output_type != output_type_assembly {
+                    }
+                    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,
@@ -260,18 +294,7 @@ pub mod write {
                         })
                     }
                 }
-                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| {
-                        WriteOutputFile(sess, tm, cpm, trans.metadata_module,
-                                        &output.with_extension("metadata.o"),
-                                        lib::llvm::ObjectFile);
-                    })
-                }
-            }
+            });
 
             llvm::LLVMRustDisposeTargetMachine(tm);
             llvm::LLVMDisposeModule(trans.metadata_module);
@@ -826,30 +849,12 @@ pub fn link_binary(sess: Session,
                    trans: &CrateTranslation,
                    obj_filename: &Path,
                    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 trans.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() {
@@ -935,32 +940,61 @@ 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.
-//
-// Instead of putting the metadata in an object file section, instead rlibs
-// contain the metadata in a separate file.
 fn link_rlib(sess: Session,
-             trans: Option<&CrateTranslation>, // None == no metadata
+             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 => {
+                a.add_native_library(l.as_slice());
+            }
+            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);
-        }
-        None => {}
-    }
 
-    for &(ref l, kind) in cstore::get_used_libraries(sess.cstore).iter() {
-        match kind {
-            cstore::NativeStatic => {
-                a.add_native_library(l.as_slice());
+            // 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);
             }
-            cstore::NativeFramework | cstore::NativeUnknown => {}
         }
+
+        None => {}
     }
     return a;
 }
@@ -983,14 +1017,14 @@ fn link_staticlib(sess: Session, obj_filename: &Path, out_filename: &Path) {
 
     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 {
@@ -1009,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("' '"));
     }
@@ -1022,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));
@@ -1043,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] {
 
@@ -1084,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
@@ -1167,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") &&
@@ -1192,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 737bffeea02..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));
@@ -337,8 +334,8 @@ pub struct CrateTranslation {
     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
@@ -837,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/middle/trans/base.rs b/src/librustc/middle/trans/base.rs
index a0bfe402c24..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) {
@@ -3059,7 +3059,9 @@ pub fn write_metadata(cx: &CrateContext, crate: &ast::Crate) -> ~[u8] {
                         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.metadata_llmod, val_ty(llconst).to_ref(), buf)
         }
@@ -3069,16 +3071,6 @@ pub fn write_metadata(cx: &CrateContext, crate: &ast::Crate) -> ~[u8] {
         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.metadata_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;
 }
@@ -3181,20 +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 86cbcd48e2c..c483a0f48f8 100644
--- a/src/librustc/middle/trans/context.rs
+++ b/src/librustc/middle/trans/context.rs
@@ -111,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.
@@ -167,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 {
@@ -238,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() {}