From 52b835c5e7a68b32a8f0532f178c150d09be200d Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 3 Dec 2013 17:41:01 -0800 Subject: [PATCH 1/2] Store metadata separately in rlib files Right now whenever an rlib file is linked against, all of the metadata from the rlib is pulled in to the final staticlib or binary. The reason for this is that the metadata is currently stored in a section of the object file. Note that this is intentional for dynamic libraries in order to distribute metadata bundled with static libraries. This commit alters the situation for rlib libraries to instead store the metadata in a separate file in the archive. In doing so, when the archive is passed to the linker, none of the metadata will get pulled into the result executable. Furthermore, the metadata file is skipped when assembling rlibs into an archive. The snag in this implementation comes with multiple output formats. When generating a dylib, the metadata needs to be in the object file, but when generating an rlib this needs to be separate. In order to accomplish this, the metadata variable is inserted into an entirely separate LLVM Module which is then codegen'd into a different location (foo.metadata.o). This is then linked into dynamic libraries and silently ignored for rlib files. While changing how metadata is inserted into archives, I have also stopped compressing metadata when inserted into rlib files. We have wanted to stop compressing metadata, but the sections it creates in object file sections are apparently too large. Thankfully if it's just an arbitrary file it doesn't matter how large it is. I have seen massive reductions in executable sizes, as well as staticlib output sizes (to confirm that this is all working). --- src/librustc/back/archive.rs | 14 ++- src/librustc/back/link.rs | 123 +++++++++++++++++++++------ src/librustc/driver/driver.rs | 13 ++- src/librustc/metadata/encoder.rs | 14 ++- src/librustc/metadata/loader.rs | 36 +++----- src/librustc/middle/trans/base.rs | 21 +++-- src/librustc/middle/trans/context.rs | 13 ++- 7 files changed, 156 insertions(+), 78 deletions(-) diff --git a/src/librustc/back/archive.rs b/src/librustc/back/archive.rs index 2dd53f7d514a..d0225866cbeb 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, @@ -81,17 +83,22 @@ 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); + self.add_archive(rlib, name, [METADATA_FILENAME]); } - 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]); + } + + 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 @@ -106,6 +113,7 @@ impl Archive { let mut inputs = ~[]; for file in files.iter() { let filename = file.filename_str().unwrap(); + if skip.iter().any(|s| *s == filename) { 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 1cb10d59e59c..cbe2dbebc061 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; @@ -88,10 +89,11 @@ pub mod write { 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; use lib; use std::c_str::ToCStr; @@ -101,10 +103,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(); @@ -204,12 +207,23 @@ pub mod write { }) } - // 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); + // 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|) { + let cpm = llvm::LLVMCreatePassManager(); + llvm::LLVMRustAddAnalysisPasses(tm, cpm, llmod); + llvm::LLVMRustAddLibraryInfo(cpm, llmod); + f(cpm); + llvm::LLVMDisposePassManager(cpm); + + } match output_type { output_type_none => {} @@ -220,20 +234,47 @@ pub mod write { } output_type_llvm_assembly => { output.with_c_str(|output| { - llvm::LLVMRustPrintModule(cpm, llmod, output) + with_codegen(tm, llmod, |cpm| { + llvm::LLVMRustPrintModule(cpm, llmod, output); + }) }) } output_type_assembly => { - WriteOutputFile(sess, tm, cpm, llmod, output, lib::llvm::AssemblyFile); + 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 { + 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 => { - WriteOutputFile(sess, tm, cpm, llmod, output, lib::llvm::ObjectFile); + 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::LLVMDisposePassManager(cpm); - llvm::LLVMRustDisposeTargetMachine(tm); + llvm::LLVMDisposeModule(trans.metadata_module); llvm::LLVMDisposeModule(llmod); llvm::LLVMContextDispose(llcx); if sess.time_llvm_passes() { llvm::LLVMRustPrintPassTimings(); } @@ -782,10 +823,9 @@ 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) { let outputs = if sess.opts.test { // If we're generating a test executable, then ignore all other output // styles at all other locations @@ -795,7 +835,7 @@ pub fn link_binary(sess: Session, // 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() { + for ty in trans.crate_types.iter() { if "bin" == *ty { outputs.push(session::OutputExecutable); } else if "dylib" == *ty || "lib" == *ty { @@ -813,12 +853,13 @@ pub fn link_binary(sess: Session, }; 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 +873,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 +915,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 +935,25 @@ 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, +// +// 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 + obj_filename: &Path, out_filename: &Path) -> Archive { let mut a = Archive::create(sess, out_filename, obj_filename); + + match trans { + Some(trans) => { + 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 => { @@ -916,8 +973,12 @@ 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); @@ -998,6 +1059,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 diff --git a/src/librustc/driver/driver.rs b/src/librustc/driver/driver.rs index 6b76fdc52f8e..737bffeea02b 100644 --- a/src/librustc/driver/driver.rs +++ b/src/librustc/driver/driver.rs @@ -335,8 +335,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], } /// Run the translation phase to LLVM, after which the AST and analysis can @@ -362,8 +364,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 +377,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 +390,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 { diff --git a/src/librustc/metadata/encoder.rs b/src/librustc/metadata/encoder.rs index e0f63a92987b..bf50da3d7892 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; @@ -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 40fca0f42f1a..5b1385c75797 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 72b9fc83c4ab..a0bfe402c246 100644 --- a/src/librustc/middle/trans/base.rs +++ b/src/librustc/middle/trans/base.rs @@ -3044,19 +3044,24 @@ 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| { unsafe { - llvm::LLVMAddGlobal(cx.llmod, val_ty(llconst).to_ref(), buf) + llvm::LLVMAddGlobal(cx.metadata_llmod, val_ty(llconst).to_ref(), buf) } }); unsafe { @@ -3069,11 +3074,13 @@ pub fn write_metadata(cx: &CrateContext, crate: &ast::Crate) { 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) + 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; } pub fn trans_crate(sess: session::Session, @@ -3140,7 +3147,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); @@ -3187,5 +3194,7 @@ pub fn trans_crate(sess: session::Session, module: llmod, link: link_meta, crate_types: crate_types, + metadata_module: ccx.metadata_llmod, + metadata: metadata, }; } diff --git a/src/librustc/middle/trans/context.rs b/src/librustc/middle/trans/context.rs index 851a1233dcca..86cbcd48e2c2 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, @@ -134,11 +135,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; @@ -174,6 +182,7 @@ impl CrateContext { sess: sess, llmod: llmod, llcx: llcx, + metadata_llmod: metadata_llmod, td: td, tn: tn, externs: HashMap::new(), From fce4a174b9ffff71a66feecd9f4960f17fc9c331 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 2 Dec 2013 23:19:29 -0800 Subject: [PATCH 2/2] Implement LTO This commit implements LTO for rust leveraging LLVM's passes. What this means is: * When compiling an rlib, in addition to insdering foo.o into the archive, also insert foo.bc (the LLVM bytecode) of the optimized module. * When the compiler detects the -Z lto option, it will attempt to perform LTO on a staticlib or binary output. The compiler will emit an error if a dylib or rlib output is being generated. * The actual act of performing LTO is as follows: 1. Force all upstream libraries to have an rlib version available. 2. Load the bytecode of each upstream library from the rlib. 3. Link all this bytecode into the current LLVM module (just using llvm apis) 4. Run an internalization pass which internalizes all symbols except those found reachable for the local crate of compilation. 5. Run the LLVM LTO pass manager over this entire module 6a. If assembling an archive, then add all upstream rlibs into the output archive. This ignores all of the object/bitcode/metadata files rust generated and placed inside the rlibs. 6b. If linking a binary, create copies of all upstream rlibs, remove the rust-generated object-file, and then link everything as usual. As I have explained in #10741, this process is excruciatingly slow, so this is *not* turned on by default, and it is also why I have decided to hide it behind a -Z flag for now. The good news is that the binary sizes are about as small as they can be as a result of LTO, so it's definitely working. Closes #10741 Closes #10740 --- src/librustc/back/archive.rs | 33 +++- src/librustc/back/link.rs | 257 ++++++++++++++++--------- src/librustc/back/lto.rs | 96 +++++++++ src/librustc/driver/driver.rs | 10 +- src/librustc/driver/session.rs | 30 +++ src/librustc/lib.rs | 1 + src/librustc/lib/llvm.rs | 11 ++ src/librustc/middle/trans/base.rs | 41 ++-- src/librustc/middle/trans/context.rs | 5 +- src/librustc/util/common.rs | 11 +- src/rustllvm/PassWrapper.cpp | 8 + src/rustllvm/RustWrapper.cpp | 19 ++ src/rustllvm/rustllvm.def.in | 3 + src/rustllvm/rustllvm.h | 2 + src/test/run-make/lto-smoke-c/Makefile | 11 ++ src/test/run-make/lto-smoke-c/bar.c | 6 + src/test/run-make/lto-smoke-c/foo.rs | 4 + src/test/run-make/lto-smoke/Makefile | 6 + src/test/run-make/lto-smoke/lib.rs | 1 + src/test/run-make/lto-smoke/main.rs | 3 + 20 files changed, 433 insertions(+), 125 deletions(-) create mode 100644 src/librustc/back/lto.rs create mode 100644 src/test/run-make/lto-smoke-c/Makefile create mode 100644 src/test/run-make/lto-smoke-c/bar.c create mode 100644 src/test/run-make/lto-smoke-c/foo.rs create mode 100644 src/test/run-make/lto-smoke/Makefile create mode 100644 src/test/run-make/lto-smoke/lib.rs create mode 100644 src/test/run-make/lto-smoke/main.rs diff --git a/src/librustc/back/archive.rs b/src/librustc/back/archive.rs index d0225866cbeb..eec15f798278 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 cbe2dbebc061..fdf47e2db737 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); - }); + } + 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); + }); - // 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 { + // 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,25 +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. -// -// 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); - match trans { - Some(trans) => { - 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 => { @@ -962,6 +954,48 @@ fn link_rlib(sess: Session, 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; } @@ -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 000000000000..7c8c6aabd7ea --- /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 or the MIT license +// , 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 737bffeea02b..c0ee53da9705 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 2d1d7033300b..30d5b7780cf9 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, 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 2185617c79f0..3de33e13ecc1 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 3b0925164c98..7039eced9769 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 a0bfe402c246..8a89cd35d0e4 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 86cbcd48e2c2..c483a0f48f8c 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 643e0440860e..ee068d7e6a14 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(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 0ae8991b2e74..76e24faebd93 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 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 484cded31471..fb611dd15c22 100644 --- a/src/rustllvm/RustWrapper.cpp +++ b/src/rustllvm/RustWrapper.cpp @@ -539,3 +539,22 @@ extern "C" char *LLVMTypeToString(LLVMTypeRef Type) { unwrap(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 d8ec1c868408..ee82fa80f874 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 94bb00aab775..ef7199a6ca8c 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 000000000000..a491fda7dc23 --- /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 000000000000..bb4036b06e13 --- /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 000000000000..3da09eb6bb6a --- /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 000000000000..4652556d3440 --- /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 000000000000..3cdacc96ee9e --- /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 000000000000..a3ed67729262 --- /dev/null +++ b/src/test/run-make/lto-smoke/main.rs @@ -0,0 +1,3 @@ +extern mod lib; + +fn main() {}