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); - }); + } + 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 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 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 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, 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(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 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(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() {}