Skip to content

Commit 774d5eb

Browse files
committed
auto merge of rust-lang#15670 : epdtry/rust/fast-archive-builder, r=alexcrichton
When rustc produces an rlib, it includes the contents of each static library required by the crate. Currently each static library is added individually, by extracting the library with `ar x` and adding the objects to the rlib using `ar r`. Each `ar r` has significant overhead - it appears to scan through the full contents of the rlib before adding the new files. This patch avoids most of the overhead by adding all library objects (and other rlib components) at once using a single `ar r`. When building `librustc` (on Linux, using GNU ar), this patch gives a 60-80% reduction in linking time, from 90s to 10s one machine I tried and 25s to 8s on another. (Though `librustc` is a bit of a special case - it's a very large crate, so the rlib is large to begin with, and it also relies on a total of 45 static libraries due to the way LLVM is organized.) More reasonable crates such as `libstd` and `libcore` also get a small reduction in linking time (just from adding metadata, bitcode, and object code in one `ar` invocation instead of three), but this is not very noticeable since the time there is small to begin with (around 1s).
2 parents 1b0dc6a + 4d8de63 commit 774d5eb

File tree

2 files changed

+161
-67
lines changed

2 files changed

+161
-67
lines changed

src/librustc/back/link.rs

+33-20
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
use super::archive::{Archive, ArchiveConfig, METADATA_FILENAME};
11+
use super::archive::{Archive, ArchiveBuilder, ArchiveConfig, METADATA_FILENAME};
1212
use super::rpath;
1313
use super::rpath::RPathConfig;
1414
use super::svh::Svh;
@@ -983,7 +983,7 @@ fn link_binary_output(sess: &Session,
983983

984984
match crate_type {
985985
config::CrateTypeRlib => {
986-
link_rlib(sess, Some(trans), &obj_filename, &out_filename);
986+
link_rlib(sess, Some(trans), &obj_filename, &out_filename).build();
987987
}
988988
config::CrateTypeStaticlib => {
989989
link_staticlib(sess, &obj_filename, &out_filename);
@@ -1019,7 +1019,7 @@ fn archive_search_paths(sess: &Session) -> Vec<Path> {
10191019
fn link_rlib<'a>(sess: &'a Session,
10201020
trans: Option<&CrateTranslation>, // None == no metadata/bytecode
10211021
obj_filename: &Path,
1022-
out_filename: &Path) -> Archive<'a> {
1022+
out_filename: &Path) -> ArchiveBuilder<'a> {
10231023
let handler = &sess.diagnostic().handler;
10241024
let config = ArchiveConfig {
10251025
handler: handler,
@@ -1028,17 +1028,30 @@ fn link_rlib<'a>(sess: &'a Session,
10281028
os: sess.targ_cfg.os,
10291029
maybe_ar_prog: sess.opts.cg.ar.clone()
10301030
};
1031-
let mut a = Archive::create(config, obj_filename);
1031+
let mut ab = ArchiveBuilder::create(config);
1032+
ab.add_file(obj_filename).unwrap();
10321033

10331034
for &(ref l, kind) in sess.cstore.get_used_libraries().borrow().iter() {
10341035
match kind {
10351036
cstore::NativeStatic => {
1036-
a.add_native_library(l.as_slice()).unwrap();
1037+
ab.add_native_library(l.as_slice()).unwrap();
10371038
}
10381039
cstore::NativeFramework | cstore::NativeUnknown => {}
10391040
}
10401041
}
10411042

1043+
// After adding all files to the archive, we need to update the
1044+
// symbol table of the archive.
1045+
ab.update_symbols();
1046+
1047+
let mut ab = match sess.targ_cfg.os {
1048+
// For OSX/iOS, we must be careful to update symbols only when adding
1049+
// object files. We're about to start adding non-object files, so run
1050+
// `ar` now to process the object files.
1051+
abi::OsMacos | abi::OsiOS => ab.build().extend(),
1052+
_ => ab,
1053+
};
1054+
10421055
// Note that it is important that we add all of our non-object "magical
10431056
// files" *after* all of the object files in the archive. The reason for
10441057
// this is as follows:
@@ -1078,7 +1091,7 @@ fn link_rlib<'a>(sess: &'a Session,
10781091
sess.abort_if_errors();
10791092
}
10801093
}
1081-
a.add_file(&metadata, false);
1094+
ab.add_file(&metadata).unwrap();
10821095
remove(sess, &metadata);
10831096

10841097
// For LTO purposes, the bytecode of this library is also inserted
@@ -1105,25 +1118,18 @@ fn link_rlib<'a>(sess: &'a Session,
11051118
sess.abort_if_errors()
11061119
}
11071120
}
1108-
a.add_file(&bc_deflated, false);
1121+
ab.add_file(&bc_deflated).unwrap();
11091122
remove(sess, &bc_deflated);
11101123
if !sess.opts.cg.save_temps &&
11111124
!sess.opts.output_types.contains(&OutputTypeBitcode) {
11121125
remove(sess, &bc);
11131126
}
1114-
1115-
// After adding all files to the archive, we need to update the
1116-
// symbol table of the archive. This currently dies on OSX (see
1117-
// #11162), and isn't necessary there anyway
1118-
match sess.targ_cfg.os {
1119-
abi::OsMacos | abi::OsiOS => {}
1120-
_ => { a.update_symbols(); }
1121-
}
11221127
}
11231128

11241129
None => {}
11251130
}
1126-
return a;
1131+
1132+
ab
11271133
}
11281134

11291135
// Create a static archive
@@ -1139,9 +1145,13 @@ fn link_rlib<'a>(sess: &'a Session,
11391145
// link in the metadata object file (and also don't prepare the archive with a
11401146
// metadata file).
11411147
fn link_staticlib(sess: &Session, obj_filename: &Path, out_filename: &Path) {
1142-
let mut a = link_rlib(sess, None, obj_filename, out_filename);
1143-
a.add_native_library("morestack").unwrap();
1144-
a.add_native_library("compiler-rt").unwrap();
1148+
let ab = link_rlib(sess, None, obj_filename, out_filename);
1149+
let mut ab = match sess.targ_cfg.os {
1150+
abi::OsMacos | abi::OsiOS => ab.build().extend(),
1151+
_ => ab,
1152+
};
1153+
ab.add_native_library("morestack").unwrap();
1154+
ab.add_native_library("compiler-rt").unwrap();
11451155

11461156
let crates = sess.cstore.get_used_crates(cstore::RequireStatic);
11471157
let mut all_native_libs = vec![];
@@ -1155,12 +1165,15 @@ fn link_staticlib(sess: &Session, obj_filename: &Path, out_filename: &Path) {
11551165
continue
11561166
}
11571167
};
1158-
a.add_rlib(&p, name.as_slice(), sess.lto()).unwrap();
1168+
ab.add_rlib(&p, name.as_slice(), sess.lto()).unwrap();
11591169

11601170
let native_libs = csearch::get_native_libraries(&sess.cstore, cnum);
11611171
all_native_libs.extend(native_libs.move_iter());
11621172
}
11631173

1174+
ab.update_symbols();
1175+
let _ = ab.build();
1176+
11641177
if !all_native_libs.is_empty() {
11651178
sess.warn("link against the following native artifacts when linking against \
11661179
this static library");

src/librustc_back/archive.rs

+128-47
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,17 @@ pub struct Archive<'a> {
3636
maybe_ar_prog: Option<String>
3737
}
3838

39+
/// Helper for adding many files to an archive with a single invocation of
40+
/// `ar`.
41+
#[must_use = "must call build() to finish building the archive"]
42+
pub struct ArchiveBuilder<'a> {
43+
archive: Archive<'a>,
44+
work_dir: TempDir,
45+
/// Filename of each member that should be added to the archive.
46+
members: Vec<Path>,
47+
should_update_symbols: bool,
48+
}
49+
3950
fn run_ar(handler: &ErrorHandler, maybe_ar_prog: &Option<String>,
4051
args: &str, cwd: Option<&Path>,
4152
paths: &[&Path]) -> ProcessOutput {
@@ -85,10 +96,8 @@ fn run_ar(handler: &ErrorHandler, maybe_ar_prog: &Option<String>,
8596
}
8697

8798
impl<'a> Archive<'a> {
88-
/// Initializes a new static archive with the given object file
89-
pub fn create<'b>(config: ArchiveConfig<'a>, initial_object: &'b Path) -> Archive<'a> {
99+
fn new(config: ArchiveConfig<'a>) -> Archive<'a> {
90100
let ArchiveConfig { handler, dst, lib_search_paths, os, maybe_ar_prog } = config;
91-
run_ar(handler, &maybe_ar_prog, "crus", None, [&dst, initial_object]);
92101
Archive {
93102
handler: handler,
94103
dst: dst,
@@ -100,17 +109,47 @@ impl<'a> Archive<'a> {
100109

101110
/// Opens an existing static archive
102111
pub fn open(config: ArchiveConfig<'a>) -> Archive<'a> {
103-
let ArchiveConfig { handler, dst, lib_search_paths, os, maybe_ar_prog } = config;
104-
assert!(dst.exists());
105-
Archive {
106-
handler: handler,
107-
dst: dst,
108-
lib_search_paths: lib_search_paths,
109-
os: os,
110-
maybe_ar_prog: maybe_ar_prog
112+
let archive = Archive::new(config);
113+
assert!(archive.dst.exists());
114+
archive
115+
}
116+
117+
/// Removes a file from this archive
118+
pub fn remove_file(&mut self, file: &str) {
119+
run_ar(self.handler, &self.maybe_ar_prog, "d", None, [&self.dst, &Path::new(file)]);
120+
}
121+
122+
/// Lists all files in an archive
123+
pub fn files(&self) -> Vec<String> {
124+
let output = run_ar(self.handler, &self.maybe_ar_prog, "t", None, [&self.dst]);
125+
let output = str::from_utf8(output.output.as_slice()).unwrap();
126+
// use lines_any because windows delimits output with `\r\n` instead of
127+
// just `\n`
128+
output.lines_any().map(|s| s.to_string()).collect()
129+
}
130+
131+
/// Creates an `ArchiveBuilder` for adding files to this archive.
132+
pub fn extend(self) -> ArchiveBuilder<'a> {
133+
ArchiveBuilder::new(self)
134+
}
135+
}
136+
137+
impl<'a> ArchiveBuilder<'a> {
138+
fn new(archive: Archive<'a>) -> ArchiveBuilder<'a> {
139+
ArchiveBuilder {
140+
archive: archive,
141+
work_dir: TempDir::new("rsar").unwrap(),
142+
members: vec![],
143+
should_update_symbols: false,
111144
}
112145
}
113146

147+
/// Create a new static archive, ready for adding files.
148+
pub fn create(config: ArchiveConfig<'a>) -> ArchiveBuilder<'a> {
149+
let archive = Archive::new(config);
150+
ArchiveBuilder::new(archive)
151+
}
152+
114153
/// Adds all of the contents of a native library to this archive. This will
115154
/// search in the relevant locations for a library named `name`.
116155
pub fn add_native_library(&mut self, name: &str) -> io::IoResult<()> {
@@ -135,48 +174,96 @@ impl<'a> Archive<'a> {
135174
}
136175

137176
/// Adds an arbitrary file to this archive
138-
pub fn add_file(&mut self, file: &Path, has_symbols: bool) {
139-
let cmd = if has_symbols {"r"} else {"rS"};
140-
run_ar(self.handler, &self.maybe_ar_prog, cmd, None, [&self.dst, file]);
141-
}
142-
143-
/// Removes a file from this archive
144-
pub fn remove_file(&mut self, file: &str) {
145-
run_ar(self.handler, &self.maybe_ar_prog, "d", None, [&self.dst, &Path::new(file)]);
177+
pub fn add_file(&mut self, file: &Path) -> io::IoResult<()> {
178+
let filename = Path::new(file.filename().unwrap());
179+
let new_file = self.work_dir.path().join(&filename);
180+
try!(fs::copy(file, &new_file));
181+
self.members.push(filename);
182+
Ok(())
146183
}
147184

148-
/// Updates all symbols in the archive (runs 'ar s' over it)
185+
/// Indicate that the next call to `build` should updates all symbols in
186+
/// the archive (run 'ar s' over it).
149187
pub fn update_symbols(&mut self) {
150-
run_ar(self.handler, &self.maybe_ar_prog, "s", None, [&self.dst]);
188+
self.should_update_symbols = true;
151189
}
152190

153-
/// Lists all files in an archive
154-
pub fn files(&self) -> Vec<String> {
155-
let output = run_ar(self.handler, &self.maybe_ar_prog, "t", None, [&self.dst]);
156-
let output = str::from_utf8(output.output.as_slice()).unwrap();
157-
// use lines_any because windows delimits output with `\r\n` instead of
158-
// just `\n`
159-
output.lines_any().map(|s| s.to_string()).collect()
191+
/// Combine the provided files, rlibs, and native libraries into a single
192+
/// `Archive`.
193+
pub fn build(self) -> Archive<'a> {
194+
// Get an absolute path to the destination, so `ar` will work even
195+
// though we run it from `self.work_dir`.
196+
let abs_dst = os::getcwd().join(&self.archive.dst);
197+
assert!(!abs_dst.is_relative());
198+
let mut args = vec![&abs_dst];
199+
let mut total_len = abs_dst.as_vec().len();
200+
201+
if self.members.is_empty() {
202+
// OSX `ar` does not allow using `r` with no members, but it does
203+
// allow running `ar s file.a` to update symbols only.
204+
if self.should_update_symbols {
205+
run_ar(self.archive.handler, &self.archive.maybe_ar_prog,
206+
"s", Some(self.work_dir.path()), args.as_slice());
207+
}
208+
return self.archive;
209+
}
210+
211+
// Don't allow the total size of `args` to grow beyond 32,000 bytes.
212+
// Windows will raise an error if the argument string is longer than
213+
// 32,768, and we leave a bit of extra space for the program name.
214+
static ARG_LENGTH_LIMIT: uint = 32000;
215+
216+
for member_name in self.members.iter() {
217+
let len = member_name.as_vec().len();
218+
219+
// `len + 1` to account for the space that's inserted before each
220+
// argument. (Windows passes command-line arguments as a single
221+
// string, not an array of strings.)
222+
if total_len + len + 1 > ARG_LENGTH_LIMIT {
223+
// Add the archive members seen so far, without updating the
224+
// symbol table (`S`).
225+
run_ar(self.archive.handler, &self.archive.maybe_ar_prog,
226+
"cruS", Some(self.work_dir.path()), args.as_slice());
227+
228+
args.clear();
229+
args.push(&abs_dst);
230+
total_len = abs_dst.as_vec().len();
231+
}
232+
233+
args.push(member_name);
234+
total_len += len + 1;
235+
}
236+
237+
// Add the remaining archive members, and update the symbol table if
238+
// necessary.
239+
let flags = if self.should_update_symbols { "crus" } else { "cruS" };
240+
run_ar(self.archive.handler, &self.archive.maybe_ar_prog,
241+
flags, Some(self.work_dir.path()), args.as_slice());
242+
243+
self.archive
160244
}
161245

162246
fn add_archive(&mut self, archive: &Path, name: &str,
163247
skip: &[&str]) -> io::IoResult<()> {
164248
let loc = TempDir::new("rsar").unwrap();
165249

166-
// First, extract the contents of the archive to a temporary directory
250+
// First, extract the contents of the archive to a temporary directory.
251+
// We don't unpack directly into `self.work_dir` due to the possibility
252+
// of filename collisions.
167253
let archive = os::make_absolute(archive);
168-
run_ar(self.handler, &self.maybe_ar_prog, "x", Some(loc.path()), [&archive]);
254+
run_ar(self.archive.handler, &self.archive.maybe_ar_prog,
255+
"x", Some(loc.path()), [&archive]);
169256

170257
// Next, we must rename all of the inputs to "guaranteed unique names".
171-
// The reason for this is that archives are keyed off the name of the
172-
// files, so if two files have the same name they will override one
173-
// another in the archive (bad).
258+
// We move each file into `self.work_dir` under its new unique name.
259+
// The reason for this renaming is that archives are keyed off the name
260+
// of the files, so if two files have the same name they will override
261+
// one another in the archive (bad).
174262
//
175263
// We skip any files explicitly desired for skipping, and we also skip
176264
// all SYMDEF files as these are just magical placeholders which get
177265
// re-created when we make a new archive anyway.
178266
let files = try!(fs::readdir(loc.path()));
179-
let mut inputs = Vec::new();
180267
for file in files.iter() {
181268
let filename = file.filename_str().unwrap();
182269
if skip.iter().any(|s| *s == filename) { continue }
@@ -192,29 +279,23 @@ impl<'a> Archive<'a> {
192279
} else {
193280
filename
194281
};
195-
let new_filename = file.with_filename(filename);
282+
let new_filename = self.work_dir.path().join(filename.as_slice());
196283
try!(fs::rename(file, &new_filename));
197-
inputs.push(new_filename);
284+
self.members.push(Path::new(filename));
198285
}
199-
if inputs.len() == 0 { return Ok(()) }
200-
201-
// Finally, add all the renamed files to this archive
202-
let mut args = vec!(&self.dst);
203-
args.extend(inputs.iter());
204-
run_ar(self.handler, &self.maybe_ar_prog, "r", None, args.as_slice());
205286
Ok(())
206287
}
207288

208289
fn find_library(&self, name: &str) -> Path {
209-
let (osprefix, osext) = match self.os {
290+
let (osprefix, osext) = match self.archive.os {
210291
abi::OsWin32 => ("", "lib"), _ => ("lib", "a"),
211292
};
212293
// On Windows, static libraries sometimes show up as libfoo.a and other
213294
// times show up as foo.lib
214295
let oslibname = format!("{}{}.{}", osprefix, name, osext);
215296
let unixlibname = format!("lib{}.a", name);
216297

217-
for path in self.lib_search_paths.iter() {
298+
for path in self.archive.lib_search_paths.iter() {
218299
debug!("looking for {} inside {}", name, path.display());
219300
let test = path.join(oslibname.as_slice());
220301
if test.exists() { return test }
@@ -223,9 +304,9 @@ impl<'a> Archive<'a> {
223304
if test.exists() { return test }
224305
}
225306
}
226-
self.handler.fatal(format!("could not find native static library `{}`, \
227-
perhaps an -L flag is missing?",
228-
name).as_slice());
307+
self.archive.handler.fatal(format!("could not find native static library `{}`, \
308+
perhaps an -L flag is missing?",
309+
name).as_slice());
229310
}
230311
}
231312

0 commit comments

Comments
 (0)