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
This commit is contained in:
Alex Crichton 2013-12-02 23:19:29 -08:00
parent 52b835c5e7
commit fce4a174b9
20 changed files with 433 additions and 125 deletions

View file

@ -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);

View file

@ -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

96
src/librustc/back/lto.rs Normal file
View file

@ -0,0 +1,96 @@
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use back::archive::Archive;
use back::link;
use driver::session;
use lib::llvm::{ModuleRef, TargetMachineRef, llvm, True, False};
use metadata::cstore;
use util::common::time;
use std::libc;
use std::vec;
pub fn run(sess: session::Session, llmod: ModuleRef,
tm: TargetMachineRef, reachable: &[~str]) {
// Make sure we actually can run LTO
for output in sess.outputs.iter() {
match *output {
session::OutputExecutable | session::OutputStaticlib => {}
_ => {
sess.fatal("lto can only be run for executables and \
static library outputs");
}
}
}
// For each of our upstream dependencies, find the corresponding rlib and
// load the bitcode from the archive. Then merge it into the current LLVM
// module that we've got.
let crates = cstore::get_used_crates(sess.cstore, cstore::RequireStatic);
for (cnum, path) in crates.move_iter() {
let name = cstore::get_crate_data(sess.cstore, cnum).name;
let path = match path {
Some(p) => p,
None => {
sess.fatal(format!("could not find rlib for: `{}`", name));
}
};
let archive = Archive::open(sess, path);
debug!("reading {}", name);
let bc = time(sess.time_passes(), format!("read {}.bc", name), (), |_|
archive.read(format!("{}.bc", name)));
let ptr = vec::raw::to_ptr(bc);
debug!("linking {}", name);
time(sess.time_passes(), format!("ll link {}", name), (), |()| unsafe {
if !llvm::LLVMRustLinkInExternalBitcode(llmod,
ptr as *libc::c_char,
bc.len() as libc::size_t) {
link::llvm_err(sess, format!("failed to load bc of `{}`", name));
}
});
}
// Internalize everything but the reachable symbols of the current module
let cstrs = reachable.map(|s| s.to_c_str());
let arr = cstrs.map(|c| c.with_ref(|p| p));
let ptr = vec::raw::to_ptr(arr);
unsafe {
llvm::LLVMRustRunRestrictionPass(llmod, ptr as **libc::c_char,
arr.len() as libc::size_t);
}
// Now we have one massive module inside of llmod. Time to run the
// LTO-specific optimization passes that LLVM provides.
//
// This code is based off the code found in llvm's LTO code generator:
// tools/lto/LTOCodeGenerator.cpp
debug!("running the pass manager");
unsafe {
let pm = llvm::LLVMCreatePassManager();
llvm::LLVMRustAddAnalysisPasses(tm, pm, llmod);
"verify".with_c_str(|s| llvm::LLVMRustAddPass(pm, s));
let builder = llvm::LLVMPassManagerBuilderCreate();
llvm::LLVMPassManagerBuilderPopulateLTOPassManager(builder, pm,
/* Internalize = */ False,
/* RunInliner = */ True);
llvm::LLVMPassManagerBuilderDispose(builder);
"verify".with_c_str(|s| llvm::LLVMRustAddPass(pm, s));
time(sess.time_passes(), "LTO pases", (), |()|
llvm::LLVMRunPassManager(pm, llmod));
llvm::LLVMDisposePassManager(pm);
}
debug!("lto done");
}

View file

@ -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 ~[],
}
}

View file

@ -17,6 +17,7 @@ use metadata::filesearch;
use metadata;
use middle::lint;
use syntax::attr::AttrMetaMethods;
use syntax::ast::NodeId;
use syntax::ast::{int_ty, uint_ty};
use syntax::codemap::Span;
@ -67,6 +68,7 @@ pub static use_softfp: uint = 1 << 26;
pub static gen_crate_map: uint = 1 << 27;
pub static prefer_dynamic: uint = 1 << 28;
pub static no_integrated_as: uint = 1 << 29;
pub static lto: uint = 1 << 30;
pub fn debugging_opts_map() -> ~[(&'static str, &'static str, uint)] {
~[("verbose", "in general, enable more debug printouts", verbose),
@ -120,6 +122,7 @@ pub fn debugging_opts_map() -> ~[(&'static str, &'static str, uint)] {
("prefer-dynamic", "Prefer dynamic linking to static linking", prefer_dynamic),
("no-integrated-as",
"Use external assembler rather than LLVM's integrated one", no_integrated_as),
("lto", "Perform LLVM link-time optimizations", lto),
]
}
@ -208,6 +211,7 @@ pub struct Session_ {
working_dir: Path,
lints: @mut HashMap<ast::NodeId, ~[(lint::lint, codemap::Span, ~str)]>,
node_id: @mut ast::NodeId,
outputs: @mut ~[OutputStyle],
}
pub type Session = @Session_;
@ -341,6 +345,9 @@ impl Session_ {
pub fn no_integrated_as(&self) -> bool {
self.debugging_opt(no_integrated_as)
}
pub fn lto(&self) -> bool {
self.debugging_opt(lto)
}
// pointless function, now...
pub fn str_of(&self, id: ast::Ident) -> @str {
@ -408,6 +415,29 @@ pub fn building_library(options: &options, crate: &ast::Crate) -> bool {
}
}
pub fn collect_outputs(options: &options, crate: &ast::Crate) -> ~[OutputStyle] {
let mut base = options.outputs.clone();
let mut iter = crate.attrs.iter().filter_map(|a| {
if "crate_type" == a.name() {
match a.value_str() {
Some(n) if "rlib" == n => Some(OutputRlib),
Some(n) if "dylib" == n => Some(OutputDylib),
Some(n) if "lib" == n => Some(OutputDylib),
Some(n) if "staticlib" == n => Some(OutputStaticlib),
Some(n) if "bin" == n => Some(OutputExecutable),
_ => None
}
} else {
None
}
});
base.extend(&mut iter);
if base.len() == 0 {
base.push(OutputExecutable);
}
return base;
}
pub fn sess_os_to_meta_os(os: abi::Os) -> metadata::loader::Os {
use metadata::loader;

View file

@ -99,6 +99,7 @@ pub mod back {
pub mod x86_64;
pub mod rpath;
pub mod target_strs;
pub mod lto;
}
pub mod metadata;

View file

@ -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);
}
}

View file

@ -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,
};
}

View file

@ -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

View file

@ -15,14 +15,23 @@ use syntax::visit;
use syntax::visit::Visitor;
use std::hashmap::HashSet;
use std::local_data;
use extra;
pub fn time<T, U>(do_it: bool, what: &str, u: U, f: |U| -> T) -> T {
local_data_key!(depth: uint);
if !do_it { return f(u); }
let old = local_data::get(depth, |d| d.map(|a| *a).unwrap_or(0));
local_data::set(depth, old + 1);
let start = extra::time::precise_time_s();
let rv = f(u);
let end = extra::time::precise_time_s();
println!("time: {:3.3f} s\t{}", end - start, what);
println!("{}time: {:3.3f} s\t{}", " ".repeat(old), end - start, what);
local_data::set(depth, old);
rv
}

View file

@ -211,3 +211,11 @@ extern "C" void
LLVMRustAddAlwaysInlinePass(LLVMPassManagerBuilderRef PMB, bool AddLifetimes) {
unwrap(PMB)->Inliner = createAlwaysInlinerPass(AddLifetimes);
}
extern "C" void
LLVMRustRunRestrictionPass(LLVMModuleRef M, char **symbols, size_t len) {
PassManager passes;
ArrayRef<const char*> ref(symbols, len);
passes.add(llvm::createInternalizePass(ref));
passes.run(*unwrap(M));
}

View file

@ -539,3 +539,22 @@ extern "C" char *LLVMTypeToString(LLVMTypeRef Type) {
unwrap<llvm::Type>(Type)->print(os);
return strdup(os.str().data());
}
extern "C" bool
LLVMRustLinkInExternalBitcode(LLVMModuleRef dst, char *bc, size_t len) {
Module *Dst = unwrap(dst);
MemoryBuffer* buf = MemoryBuffer::getMemBufferCopy(StringRef(bc, len));
std::string Err;
Module *Src = llvm::getLazyBitcodeModule(buf, Dst->getContext(), &Err);
if (Src == NULL) {
LLVMRustError = Err.c_str();
delete buf;
return false;
}
if (Linker::LinkModules(Dst, Src, Linker::DestroySource, &Err)) {
LLVMRustError = Err.c_str();
return false;
}
return true;
}

View file

@ -629,3 +629,6 @@ LLVMTypeToString
LLVMAddColdAttribute
LLVMCreateMemoryBufferWithMemoryRange
LLVMCreateMemoryBufferWithMemoryRangeCopy
LLVMPassManagerBuilderPopulateLTOPassManager
LLVMRustLinkInExternalBitcode
LLVMRustRunRestrictionPass

View file

@ -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"

View file

@ -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)

View file

@ -0,0 +1,6 @@
void foo();
int main() {
foo();
return 0;
}

View file

@ -0,0 +1,4 @@
#[crate_type = "staticlib"];
#[no_mangle]
pub extern "C" fn foo() {}

View file

@ -0,0 +1,6 @@
-include ../tools.mk
all:
$(RUSTC) lib.rs
$(RUSTC) main.rs -Z lto
$(call RUN,main)

View file

@ -0,0 +1 @@
#[crate_type = "rlib"];

View file

@ -0,0 +1,3 @@
extern mod lib;
fn main() {}