diff --git a/src/librustc/back/archive.rs b/src/librustc/back/archive.rs index 2dd53f7d514a5..eec15f798278d 100644 --- a/src/librustc/back/archive.rs +++ b/src/librustc/back/archive.rs @@ -20,6 +20,8 @@ use std::str; use extra::tempfile::TempDir; use syntax::abi; +pub static METADATA_FILENAME: &'static str = "metadata"; + pub struct Archive { priv sess: Session, priv dst: Path, @@ -40,7 +42,8 @@ fn run_ar(sess: Session, args: &str, cwd: Option<&Path>, } let o = Process::new(ar, args.as_slice(), opts).finish_with_output(); if !o.status.success() { - sess.err(format!("{} failed with: {}", ar, o.status)); + sess.err(format!("{} {} failed with: {}", ar, args.connect(" "), + o.status)); sess.note(format!("stdout ---\n{}", str::from_utf8(o.output))); sess.note(format!("stderr ---\n{}", str::from_utf8(o.error))); sess.abort_if_errors(); @@ -81,17 +84,40 @@ impl Archive { /// search in the relevant locations for a library named `name`. pub fn add_native_library(&mut self, name: &str) { let location = self.find_library(name); - self.add_archive(&location, name); + self.add_archive(&location, name, []); } /// Adds all of the contents of the rlib at the specified path to this /// archive. - pub fn add_rlib(&mut self, rlib: &Path) { - let name = rlib.filename_str().unwrap().split('-').next().unwrap(); - self.add_archive(rlib, name); + /// + /// This ignores adding the bytecode from the rlib, and if LTO is enabled + /// then the object file also isn't added. + pub fn add_rlib(&mut self, rlib: &Path, name: &str, lto: bool) { + let object = format!("{}.o", name); + let bytecode = format!("{}.bc", name); + let mut ignore = ~[METADATA_FILENAME, bytecode.as_slice()]; + if lto { + ignore.push(object.as_slice()); + } + self.add_archive(rlib, name, ignore); } - fn add_archive(&mut self, archive: &Path, name: &str) { + /// Adds an arbitrary file to this archive + pub fn add_file(&mut self, file: &Path) { + run_ar(self.sess, "r", None, [&self.dst, file]); + } + + /// Removes a file from this archive + pub fn remove_file(&mut self, file: &str) { + run_ar(self.sess, "d", None, [&self.dst, &Path::new(file)]); + } + + pub fn files(&self) -> ~[~str] { + let output = run_ar(self.sess, "t", None, [&self.dst]); + str::from_utf8(output.output).lines().map(|s| s.to_owned()).collect() + } + + fn add_archive(&mut self, archive: &Path, name: &str, skip: &[&str]) { let loc = TempDir::new("rsar").unwrap(); // First, extract the contents of the archive to a temporary directory @@ -102,10 +128,17 @@ impl Archive { // The reason for this is that archives are keyed off the name of the // files, so if two files have the same name they will override one // another in the archive (bad). + // + // We skip any files explicitly desired for skipping, and we also skip + // all SYMDEF files as these are just magical placeholders which get + // re-created when we make a new archive anyway. let files = fs::readdir(loc.path()); let mut inputs = ~[]; for file in files.iter() { let filename = file.filename_str().unwrap(); + if skip.iter().any(|s| *s == filename) { continue } + if filename.contains(".SYMDEF") { continue } + let filename = format!("r-{}-{}", name, filename); let new_filename = file.with_filename(filename); fs::rename(file, &new_filename); diff --git a/src/librustc/back/link.rs b/src/librustc/back/link.rs index 8ec0674f74f69..451213ab694f7 100644 --- a/src/librustc/back/link.rs +++ b/src/librustc/back/link.rs @@ -9,8 +9,9 @@ // except according to those terms. -use back::archive::Archive; +use back::archive::{Archive, METADATA_FILENAME}; use back::rpath; +use driver::driver::CrateTranslation; use driver::session::Session; use driver::session; use lib::llvm::llvm; @@ -21,6 +22,7 @@ use metadata::{encoder, cstore, filesearch, csearch}; use middle::trans::context::CrateContext; use middle::trans::common::gensym_name; use middle::ty; +use util::common::time; use util::ppaux; use std::c_str::ToCStr; @@ -32,6 +34,7 @@ use std::ptr; use std::run; use std::str; use std::io::fs; +use extra::tempfile::TempDir; use syntax::abi; use syntax::ast; use syntax::ast_map::{path, path_mod, path_name, path_pretty_name}; @@ -84,15 +87,18 @@ pub fn WriteOutputFile( pub mod write { + use back::lto; use back::link::{WriteOutputFile, output_type}; use back::link::{output_type_assembly, output_type_bitcode}; use back::link::{output_type_exe, output_type_llvm_assembly}; use back::link::{output_type_object}; + use driver::driver::CrateTranslation; use driver::session::Session; use driver::session; use lib::llvm::llvm; - use lib::llvm::{ModuleRef, ContextRef}; + use lib::llvm::{ModuleRef, TargetMachineRef, PassManagerRef}; use lib; + use util::common::time; use std::c_str::ToCStr; use std::libc::{c_uint, c_int}; @@ -101,10 +107,11 @@ pub mod write { use std::str; pub fn run_passes(sess: Session, - llcx: ContextRef, - llmod: ModuleRef, + trans: &CrateTranslation, output_type: output_type, output: &Path) { + let llmod = trans.module; + let llcx = trans.context; unsafe { llvm::LLVMInitializePasses(); @@ -191,49 +198,106 @@ pub mod write { } // Finally, run the actual optimization passes - llvm::LLVMRustRunFunctionPassManager(fpm, llmod); - llvm::LLVMRunPassManager(mpm, llmod); + time(sess.time_passes(), "llvm function passes", (), |()| + llvm::LLVMRustRunFunctionPassManager(fpm, llmod)); + time(sess.time_passes(), "llvm module passes", (), |()| + llvm::LLVMRunPassManager(mpm, llmod)); // Deallocate managers that we're now done with llvm::LLVMDisposePassManager(fpm); llvm::LLVMDisposePassManager(mpm); - if sess.opts.save_temps { + // Emit the bytecode if we're either saving our temporaries or + // emitting an rlib. Whenever an rlib is create, the bytecode is + // inserted into the archive in order to allow LTO against it. + if sess.opts.save_temps || + sess.outputs.iter().any(|&o| o == session::OutputRlib) { output.with_extension("bc").with_c_str(|buf| { llvm::LLVMWriteBitcodeToFile(llmod, buf); }) } - // Create a codegen-specific pass manager to emit the actual - // assembly or object files. This may not end up getting used, - // but we make it anyway for good measure. - let cpm = llvm::LLVMCreatePassManager(); - llvm::LLVMRustAddAnalysisPasses(tm, cpm, llmod); - llvm::LLVMRustAddLibraryInfo(cpm, llmod); - - match output_type { - output_type_none => {} - output_type_bitcode => { - output.with_c_str(|buf| { + if sess.lto() { + time(sess.time_passes(), "all lto passes", (), |()| + lto::run(sess, llmod, tm, trans.reachable)); + + if sess.opts.save_temps { + output.with_extension("lto.bc").with_c_str(|buf| { llvm::LLVMWriteBitcodeToFile(llmod, buf); }) } - output_type_llvm_assembly => { - output.with_c_str(|output| { - llvm::LLVMRustPrintModule(cpm, llmod, output) - }) - } - output_type_assembly => { - WriteOutputFile(sess, tm, cpm, llmod, output, lib::llvm::AssemblyFile); - } - output_type_exe | output_type_object => { - WriteOutputFile(sess, tm, cpm, llmod, output, lib::llvm::ObjectFile); + } + + // A codegen-specific pass manager is used to generate object + // files for an LLVM module. + // + // Apparently each of these pass managers is a one-shot kind of + // thing, so we create a new one for each type of output. The + // pass manager passed to the closure should be ensured to not + // escape the closure itself, and the manager should only be + // used once. + fn with_codegen(tm: TargetMachineRef, llmod: ModuleRef, + f: |PassManagerRef|) { + unsafe { + let cpm = llvm::LLVMCreatePassManager(); + llvm::LLVMRustAddAnalysisPasses(tm, cpm, llmod); + llvm::LLVMRustAddLibraryInfo(cpm, llmod); + f(cpm); + llvm::LLVMDisposePassManager(cpm); } } - llvm::LLVMDisposePassManager(cpm); + time(sess.time_passes(), "codegen passes", (), |()| { + match output_type { + output_type_none => {} + output_type_bitcode => { + output.with_c_str(|buf| { + llvm::LLVMWriteBitcodeToFile(llmod, buf); + }) + } + output_type_llvm_assembly => { + output.with_c_str(|output| { + with_codegen(tm, llmod, |cpm| { + llvm::LLVMRustPrintModule(cpm, llmod, output); + }) + }) + } + output_type_assembly => { + with_codegen(tm, llmod, |cpm| { + WriteOutputFile(sess, tm, cpm, llmod, output, + lib::llvm::AssemblyFile); + }); + + // If we're not using the LLVM assembler, this function + // could be invoked specially with output_type_assembly, + // so in this case we still want the metadata object + // file. + if sess.opts.output_type != output_type_assembly { + with_codegen(tm, trans.metadata_module, |cpm| { + let out = output.with_extension("metadata.o"); + WriteOutputFile(sess, tm, cpm, + trans.metadata_module, &out, + lib::llvm::ObjectFile); + }) + } + } + output_type_exe | output_type_object => { + with_codegen(tm, llmod, |cpm| { + WriteOutputFile(sess, tm, cpm, llmod, output, + lib::llvm::ObjectFile); + }); + with_codegen(tm, trans.metadata_module, |cpm| { + let out = output.with_extension("metadata.o"); + WriteOutputFile(sess, tm, cpm, + trans.metadata_module, &out, + lib::llvm::ObjectFile); + }) + } + } + }); llvm::LLVMRustDisposeTargetMachine(tm); + llvm::LLVMDisposeModule(trans.metadata_module); llvm::LLVMDisposeModule(llmod); llvm::LLVMContextDispose(llcx); if sess.time_llvm_passes() { llvm::LLVMRustPrintPassTimings(); } @@ -782,43 +846,25 @@ pub fn get_cc_prog(sess: Session) -> ~str { /// Perform the linkage portion of the compilation phase. This will generate all /// of the requested outputs for this compilation session. pub fn link_binary(sess: Session, - crate_types: &[~str], + trans: &CrateTranslation, obj_filename: &Path, - out_filename: &Path, - lm: LinkMeta) { + out_filename: &Path) { + // If we're generating a test executable, then ignore all other output + // styles at all other locations let outputs = if sess.opts.test { - // If we're generating a test executable, then ignore all other output - // styles at all other locations ~[session::OutputExecutable] } else { - // Always generate whatever was specified on the command line, but also - // look at what was in the crate file itself for generating output - // formats. - let mut outputs = sess.opts.outputs.clone(); - for ty in crate_types.iter() { - if "bin" == *ty { - outputs.push(session::OutputExecutable); - } else if "dylib" == *ty || "lib" == *ty { - outputs.push(session::OutputDylib); - } else if "rlib" == *ty { - outputs.push(session::OutputRlib); - } else if "staticlib" == *ty { - outputs.push(session::OutputStaticlib); - } - } - if outputs.len() == 0 { - outputs.push(session::OutputExecutable); - } - outputs + (*sess.outputs).clone() }; for output in outputs.move_iter() { - link_binary_output(sess, output, obj_filename, out_filename, lm); + link_binary_output(sess, trans, output, obj_filename, out_filename); } - // Remove the temporary object file if we aren't saving temps + // Remove the temporary object file and metadata if we aren't saving temps if !sess.opts.save_temps { fs::unlink(obj_filename); + fs::unlink(&obj_filename.with_extension("metadata.o")); } } @@ -832,11 +878,11 @@ fn is_writeable(p: &Path) -> bool { } fn link_binary_output(sess: Session, + trans: &CrateTranslation, output: session::OutputStyle, obj_filename: &Path, - out_filename: &Path, - lm: LinkMeta) { - let libname = output_lib_filename(lm); + out_filename: &Path) { + let libname = output_lib_filename(trans.link); let out_filename = match output { session::OutputRlib => { out_filename.with_filename(format!("lib{}.rlib", libname)) @@ -874,7 +920,7 @@ fn link_binary_output(sess: Session, match output { session::OutputRlib => { - link_rlib(sess, obj_filename, &out_filename); + link_rlib(sess, Some(trans), obj_filename, &out_filename); } session::OutputStaticlib => { link_staticlib(sess, obj_filename, &out_filename); @@ -894,9 +940,12 @@ fn link_binary_output(sess: Session, // rlib primarily contains the object file of the crate, but it also contains // all of the object files from native libraries. This is done by unzipping // native libraries and inserting all of the contents into this archive. -fn link_rlib(sess: Session, obj_filename: &Path, +fn link_rlib(sess: Session, + trans: Option<&CrateTranslation>, // None == no metadata/bytecode + obj_filename: &Path, out_filename: &Path) -> Archive { let mut a = Archive::create(sess, out_filename, obj_filename); + for &(ref l, kind) in cstore::get_used_libraries(sess.cstore).iter() { match kind { cstore::NativeStatic => { @@ -905,6 +954,48 @@ fn link_rlib(sess: Session, obj_filename: &Path, cstore::NativeFramework | cstore::NativeUnknown => {} } } + + // Note that it is important that we add all of our non-object "magical + // files" *after* all of the object files in the archive. The reason for + // this is as follows: + // + // * When performing LTO, this archive will be modified to remove + // obj_filename from above. The reason for this is described below. + // + // * When the system linker looks at an archive, it will attempt to + // determine the architecture of the archive in order to see whether its + // linkable. + // + // The algorithm for this detections is: iterate over the files in the + // archive. Skip magical SYMDEF names. Interpret the first file as an + // object file. Read architecture from the object file. + // + // * As one can probably see, if "metadata" and "foo.bc" were placed + // before all of the objects, then the architecture of this archive would + // not be correctly inferred once 'foo.o' is removed. + // + // Basically, all this means is that this code should not move above the + // code above. + match trans { + Some(trans) => { + // Instead of putting the metadata in an object file section, rlibs + // contain the metadata in a separate file. + let metadata = obj_filename.with_filename(METADATA_FILENAME); + fs::File::create(&metadata).write(trans.metadata); + a.add_file(&metadata); + fs::unlink(&metadata); + + // For LTO purposes, the bytecode of this library is also inserted + // into the archive. + let bc = obj_filename.with_extension("bc"); + a.add_file(&bc); + if !sess.opts.save_temps { + fs::unlink(&bc); + } + } + + None => {} + } return a; } @@ -916,20 +1007,24 @@ fn link_rlib(sess: Session, obj_filename: &Path, // // Additionally, there's no way for us to link dynamic libraries, so we warn // about all dynamic library dependencies that they're not linked in. +// +// There's no need to include metadata in a static archive, so ensure to not +// link in the metadata object file (and also don't prepare the archive with a +// metadata file). fn link_staticlib(sess: Session, obj_filename: &Path, out_filename: &Path) { - let mut a = link_rlib(sess, obj_filename, out_filename); + let mut a = link_rlib(sess, None, obj_filename, out_filename); a.add_native_library("morestack"); let crates = cstore::get_used_crates(sess.cstore, cstore::RequireStatic); for &(cnum, ref path) in crates.iter() { + let name = cstore::get_crate_data(sess.cstore, cnum).name; let p = match *path { Some(ref p) => p.clone(), None => { - sess.err(format!("could not find rlib for: `{}`", - cstore::get_crate_data(sess.cstore, cnum).name)); + sess.err(format!("could not find rlib for: `{}`", name)); continue } }; - a.add_rlib(&p); + a.add_rlib(&p, name, sess.lto()); let native_libs = csearch::get_native_libraries(sess.cstore, cnum); for &(kind, ref lib) in native_libs.iter() { let name = match kind { @@ -948,10 +1043,12 @@ fn link_staticlib(sess: Session, obj_filename: &Path, out_filename: &Path) { // links to all upstream files as well. fn link_natively(sess: Session, dylib: bool, obj_filename: &Path, out_filename: &Path) { + let tmpdir = TempDir::new("rustc").expect("needs a temp dir"); // The invocations of cc share some flags across platforms let cc_prog = get_cc_prog(sess); let mut cc_args = sess.targ_cfg.target_strs.cc_args.clone(); - cc_args.push_all_move(link_args(sess, dylib, obj_filename, out_filename)); + cc_args.push_all_move(link_args(sess, dylib, tmpdir.path(), + obj_filename, out_filename)); if (sess.opts.debugging_opts & session::print_link_args) != 0 { println!("{} link args: '{}'", cc_prog, cc_args.connect("' '")); } @@ -961,7 +1058,8 @@ fn link_natively(sess: Session, dylib: bool, obj_filename: &Path, // Invoke the system linker debug!("{} {}", cc_prog, cc_args.connect(" ")); - let prog = run::process_output(cc_prog, cc_args); + let prog = time(sess.time_passes(), "running linker", (), |()| + run::process_output(cc_prog, cc_args)); if !prog.status.success() { sess.err(format!("linking with `{}` failed: {}", cc_prog, prog.status)); @@ -982,6 +1080,7 @@ fn link_natively(sess: Session, dylib: bool, obj_filename: &Path, fn link_args(sess: Session, dylib: bool, + tmpdir: &Path, obj_filename: &Path, out_filename: &Path) -> ~[~str] { @@ -998,6 +1097,14 @@ fn link_args(sess: Session, ~"-o", out_filename.as_str().unwrap().to_owned(), obj_filename.as_str().unwrap().to_owned()]); + // When linking a dynamic library, we put the metadata into a section of the + // executable. This metadata is in a separate object file from the main + // object file, so we link that in here. + if dylib { + let metadata = obj_filename.with_extension("metadata.o"); + args.push(metadata.as_str().unwrap().to_owned()); + } + if sess.targ_cfg.os == abi::OsLinux { // GNU-style linkers will use this to omit linking to libraries which // don't actually fulfill any relocations, but only for libraries which @@ -1015,7 +1122,7 @@ fn link_args(sess: Session, } add_local_native_libraries(&mut args, sess); - add_upstream_rust_crates(&mut args, sess, dylib); + add_upstream_rust_crates(&mut args, sess, dylib, tmpdir); add_upstream_native_libraries(&mut args, sess); // # Telling the linker what we're doing @@ -1098,7 +1205,7 @@ fn add_local_native_libraries(args: &mut ~[~str], sess: Session) { // dependencies will be linked when producing the final output (instead of // the intermediate rlib version) fn add_upstream_rust_crates(args: &mut ~[~str], sess: Session, - dylib: bool) { + dylib: bool, tmpdir: &Path) { // Converts a library file-stem into a cc -l argument fn unlib(config: @session::config, stem: &str) -> ~str { if stem.starts_with("lib") && @@ -1123,14 +1230,49 @@ fn add_upstream_rust_crates(args: &mut ~[~str], sess: Session, // dynamic libraries. let crates = cstore::get_used_crates(cstore, cstore::RequireStatic); if crates.iter().all(|&(_, ref p)| p.is_some()) { - for (_, path) in crates.move_iter() { - let path = path.unwrap(); - args.push(path.as_str().unwrap().to_owned()); + for (cnum, path) in crates.move_iter() { + let cratepath = path.unwrap(); + + // When performing LTO on an executable output, all of the + // bytecode from the upstream libraries has already been + // included in our object file output. We need to modify all of + // the upstream archives to remove their corresponding object + // file to make sure we don't pull the same code in twice. + // + // We must continue to link to the upstream archives to be sure + // to pull in native static dependencies. As the final caveat, + // on linux it is apparently illegal to link to a blank archive, + // so if an archive no longer has any object files in it after + // we remove `lib.o`, then don't link against it at all. + // + // If we're not doing LTO, then our job is simply to just link + // against the archive. + if sess.lto() { + let name = cstore::get_crate_data(sess.cstore, cnum).name; + time(sess.time_passes(), format!("altering {}.rlib", name), + (), |()| { + let dst = tmpdir.join(cratepath.filename().unwrap()); + fs::copy(&cratepath, &dst); + let dst_str = dst.as_str().unwrap().to_owned(); + let mut archive = Archive::open(sess, dst); + archive.remove_file(format!("{}.o", name)); + let files = archive.files(); + if files.iter().any(|s| s.ends_with(".o")) { + args.push(dst_str); + } + }); + } else { + args.push(cratepath.as_str().unwrap().to_owned()); + } } return; } } + // If we're performing LTO, then it should have been previously required + // that all upstream rust depenencies were available in an rlib format. + assert!(!sess.lto()); + // This is a fallback of three different cases of linking: // // * When creating a dynamic library, all inputs are required to be dynamic diff --git a/src/librustc/back/lto.rs b/src/librustc/back/lto.rs new file mode 100644 index 0000000000000..7c8c6aabd7eab --- /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 6b76fdc52f8ea..c0ee53da97053 100644 --- a/src/librustc/driver/driver.rs +++ b/src/librustc/driver/driver.rs @@ -165,10 +165,7 @@ pub fn phase_2_configure_and_expand(sess: Session, let time_passes = sess.time_passes(); *sess.building_library = session::building_library(sess.opts, &crate); - let want_exe = sess.opts.outputs.iter().any(|&o| o == OutputExecutable); - if *sess.building_library && want_exe { - sess.err("cannot build both a library and an executable"); - } + *sess.outputs = session::collect_outputs(sess.opts, &crate); time(time_passes, "gated feature checking", (), |_| front::feature_gate::check_crate(sess, &crate)); @@ -335,8 +332,10 @@ pub fn phase_3_run_analysis_passes(sess: Session, pub struct CrateTranslation { context: ContextRef, module: ModuleRef, + metadata_module: ModuleRef, link: LinkMeta, - crate_types: ~[~str], + metadata: ~[u8], + reachable: ~[~str], } /// Run the translation phase to LLVM, after which the AST and analysis can @@ -362,8 +361,7 @@ pub fn phase_5_run_llvm_passes(sess: Session, time(sess.time_passes(), "LLVM passes", (), |_| link::write::run_passes(sess, - trans.context, - trans.module, + trans, output_type, &asm_filename)); @@ -376,8 +374,7 @@ pub fn phase_5_run_llvm_passes(sess: Session, } else { time(sess.time_passes(), "LLVM passes", (), |_| link::write::run_passes(sess, - trans.context, - trans.module, + trans, sess.opts.output_type, &outputs.obj_filename)); } @@ -390,10 +387,9 @@ pub fn phase_6_link_output(sess: Session, outputs: &OutputFilenames) { time(sess.time_passes(), "linking", (), |_| link::link_binary(sess, - trans.crate_types, + trans, &outputs.obj_filename, - &outputs.out_filename, - trans.link)); + &outputs.out_filename)); } pub fn stop_after_phase_3(sess: Session) -> bool { @@ -838,7 +834,8 @@ pub fn build_session_(sopts: @session::options, building_library: @mut false, working_dir: os::getcwd(), lints: @mut HashMap::new(), - node_id: @mut 1 + node_id: @mut 1, + outputs: @mut ~[], } } diff --git a/src/librustc/driver/session.rs b/src/librustc/driver/session.rs index 2d1d7033300b5..30d5b7780cf92 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 2185617c79f0d..3de33e13ecc19 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 3b0925164c982..7039eced9769f 100644 --- a/src/librustc/lib/llvm.rs +++ b/src/librustc/lib/llvm.rs @@ -1403,6 +1403,11 @@ pub mod llvm { pub fn LLVMPassManagerBuilderPopulateFunctionPassManager( PMB: PassManagerBuilderRef, PM: PassManagerRef); + pub fn LLVMPassManagerBuilderPopulateLTOPassManager( + PMB: PassManagerBuilderRef, + PM: PassManagerRef, + Internalize: Bool, + RunInliner: Bool); /** Destroys a memory buffer. */ pub fn LLVMDisposeMemoryBuffer(MemBuf: MemoryBufferRef); @@ -1736,6 +1741,12 @@ pub mod llvm { pub fn LLVMRustSetNormalizedTarget(M: ModuleRef, triple: *c_char); pub fn LLVMRustAddAlwaysInlinePass(P: PassManagerBuilderRef, AddLifetimes: bool); + pub fn LLVMRustLinkInExternalBitcode(M: ModuleRef, + bc: *c_char, + len: size_t) -> bool; + pub fn LLVMRustRunRestrictionPass(M: ModuleRef, + syms: **c_char, + len: size_t); } } diff --git a/src/librustc/metadata/encoder.rs b/src/librustc/metadata/encoder.rs index e0f63a92987bc..bf50da3d7892e 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 40fca0f42f1a2..5b1385c757973 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 72b9fc83c4ab8..8a89cd35d0e47 100644 --- a/src/librustc/middle/trans/base.rs +++ b/src/librustc/middle/trans/base.rs @@ -2929,7 +2929,7 @@ pub fn symname(sess: session::Session, name: &str, } pub fn decl_crate_map(sess: session::Session, mapmeta: LinkMeta, - llmod: ModuleRef) -> ValueRef { + llmod: ModuleRef) -> (~str, ValueRef) { let targ_cfg = sess.targ_cfg; let int_type = Type::int(targ_cfg.arch); let mut n_subcrates = 1; @@ -2963,7 +2963,7 @@ pub fn decl_crate_map(sess: session::Session, mapmeta: LinkMeta, lib::llvm::SetLinkage(map, lib::llvm::ExternalLinkage); } - return map; + return (sym_name, map); } pub fn fill_crate_map(ccx: @mut CrateContext, map: ValueRef) { @@ -3044,19 +3044,26 @@ pub fn crate_ctxt_to_encode_parms<'r>(cx: &'r CrateContext, ie: encoder::encode_ } } -pub fn write_metadata(cx: &CrateContext, crate: &ast::Crate) { - if !*cx.sess.building_library { return; } +pub fn write_metadata(cx: &CrateContext, crate: &ast::Crate) -> ~[u8] { + use extra::flate; + + if !*cx.sess.building_library { return ~[]; } let encode_inlined_item: encoder::encode_inlined_item = |ecx, ebml_w, path, ii| astencode::encode_inlined_item(ecx, ebml_w, path, ii, cx.maps); let encode_parms = crate_ctxt_to_encode_parms(cx, encode_inlined_item); - let llmeta = C_bytes(encoder::encode_metadata(encode_parms, crate)); + let metadata = encoder::encode_metadata(encode_parms, crate); + let compressed = encoder::metadata_encoding_version + + flate::deflate_bytes(metadata); + let llmeta = C_bytes(compressed); let llconst = C_struct([llmeta], false); - let mut llglobal = "rust_metadata".with_c_str(|buf| { + let name = format!("rust_metadata_{}_{}_{}", cx.link_meta.name, + cx.link_meta.vers, cx.link_meta.extras_hash); + let llglobal = name.with_c_str(|buf| { unsafe { - llvm::LLVMAddGlobal(cx.llmod, val_ty(llconst).to_ref(), buf) + llvm::LLVMAddGlobal(cx.metadata_llmod, val_ty(llconst).to_ref(), buf) } }); unsafe { @@ -3064,16 +3071,8 @@ pub fn write_metadata(cx: &CrateContext, crate: &ast::Crate) { cx.sess.targ_cfg.target_strs.meta_sect_name.with_c_str(|buf| { llvm::LLVMSetSection(llglobal, buf) }); - lib::llvm::SetLinkage(llglobal, lib::llvm::InternalLinkage); - - let t_ptr_i8 = Type::i8p(); - llglobal = llvm::LLVMConstBitCast(llglobal, t_ptr_i8.to_ref()); - let llvm_used = "llvm.used".with_c_str(|buf| { - llvm::LLVMAddGlobal(cx.llmod, Type::array(&t_ptr_i8, 1).to_ref(), buf) - }); - lib::llvm::SetLinkage(llvm_used, lib::llvm::AppendingLinkage); - llvm::LLVMSetInitializer(llvm_used, C_array(t_ptr_i8, [llglobal])); } + return metadata; } pub fn trans_crate(sess: session::Session, @@ -3140,7 +3139,7 @@ pub fn trans_crate(sess: session::Session, } // Translate the metadata. - write_metadata(ccx, &crate); + let metadata = write_metadata(ccx, &crate); if ccx.sess.trans_stats() { println("--- trans stats ---"); println!("n_static_tydescs: {}", ccx.stats.n_static_tydescs); @@ -3174,18 +3173,27 @@ pub fn trans_crate(sess: session::Session, let llcx = ccx.llcx; let link_meta = ccx.link_meta; let llmod = ccx.llmod; - let crate_types = crate.attrs.iter().filter_map(|a| { - if "crate_type" == a.name() { - a.value_str() - } else { - None - } - }).map(|a| a.to_owned()).collect(); + let mut reachable = ccx.reachable.iter().filter_map(|id| { + ccx.item_symbols.find(id).map(|s| s.to_owned()) + }).to_owned_vec(); + + // Make sure that some other crucial symbols are not eliminated from the + // module. This includes the main function (main/amain elsewhere), the crate + // map (used for debug log settings and I/O), and finally the curious + // rust_stack_exhausted symbol. This symbol is required for use by the + // libmorestack library that we link in, so we must ensure that this symbol + // is not internalized (if defined in the crate). + reachable.push(ccx.crate_map_name.to_owned()); + reachable.push(~"main"); + reachable.push(~"amain"); + reachable.push(~"rust_stack_exhausted"); return CrateTranslation { context: llcx, module: llmod, link: link_meta, - crate_types: crate_types, + metadata_module: ccx.metadata_llmod, + metadata: metadata, + reachable: reachable, }; } diff --git a/src/librustc/middle/trans/context.rs b/src/librustc/middle/trans/context.rs index 851a1233dcca7..c483a0f48f8c9 100644 --- a/src/librustc/middle/trans/context.rs +++ b/src/librustc/middle/trans/context.rs @@ -42,6 +42,7 @@ pub struct CrateContext { sess: session::Session, llmod: ModuleRef, llcx: ContextRef, + metadata_llmod: ModuleRef, td: TargetData, tn: TypeNames, externs: ExternMap, @@ -110,6 +111,7 @@ pub struct CrateContext { opaque_vec_type: Type, builder: BuilderRef_res, crate_map: ValueRef, + crate_map_name: ~str, // Set when at least one function uses GC. Needed so that // decl_gc_metadata knows whether to link to the module metadata, which // is not emitted by LLVM's GC pass when no functions use GC. @@ -134,11 +136,18 @@ impl CrateContext { let llmod = name.with_c_str(|buf| { llvm::LLVMModuleCreateWithNameInContext(buf, llcx) }); + let metadata_llmod = format!("{}_metadata", name).with_c_str(|buf| { + llvm::LLVMModuleCreateWithNameInContext(buf, llcx) + }); let data_layout: &str = sess.targ_cfg.target_strs.data_layout; let targ_triple: &str = sess.targ_cfg.target_strs.target_triple; - data_layout.with_c_str(|buf| llvm::LLVMSetDataLayout(llmod, buf)); + data_layout.with_c_str(|buf| { + llvm::LLVMSetDataLayout(llmod, buf); + llvm::LLVMSetDataLayout(metadata_llmod, buf); + }); targ_triple.with_c_str(|buf| { - llvm::LLVMRustSetNormalizedTarget(llmod, buf) + llvm::LLVMRustSetNormalizedTarget(llmod, buf); + llvm::LLVMRustSetNormalizedTarget(metadata_llmod, buf); }); let targ_cfg = sess.targ_cfg; @@ -159,7 +168,8 @@ impl CrateContext { tn.associate_type("tydesc", &tydesc_type); tn.associate_type("str_slice", &str_slice_ty); - let crate_map = decl_crate_map(sess, link_meta, llmod); + let (crate_map_name, crate_map) = decl_crate_map(sess, link_meta, + llmod); let dbg_cx = if sess.opts.debuginfo { Some(debuginfo::CrateDebugContext::new(llmod, name.to_owned())) } else { @@ -174,6 +184,7 @@ impl CrateContext { sess: sess, llmod: llmod, llcx: llcx, + metadata_llmod: metadata_llmod, td: td, tn: tn, externs: HashMap::new(), @@ -229,6 +240,7 @@ impl CrateContext { opaque_vec_type: opaque_vec_type, builder: BuilderRef_res(llvm::LLVMCreateBuilderInContext(llcx)), crate_map: crate_map, + crate_map_name: crate_map_name, uses_gc: false, dbg_cx: dbg_cx, do_not_commit_warning_issued: false diff --git a/src/librustc/util/common.rs b/src/librustc/util/common.rs index 643e0440860e0..ee068d7e6a149 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 0ae8991b2e740..76e24faebd93b 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 484cded314710..fb611dd15c227 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 d8ec1c868408d..ee82fa80f8743 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 94bb00aab7757..ef7199a6ca8cc 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 0000000000000..a491fda7dc232 --- /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 0000000000000..bb4036b06e13b --- /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 0000000000000..3da09eb6bb6a8 --- /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 0000000000000..4652556d34401 --- /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 0000000000000..3cdacc96ee9e0 --- /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 0000000000000..a3ed67729262b --- /dev/null +++ b/src/test/run-make/lto-smoke/main.rs @@ -0,0 +1,3 @@ +extern mod lib; + +fn main() {}