Skip to content

Commit 4d8de63

Browse files
committed
speed up static linking by combining ar invocations
1 parent 79e9f14 commit 4d8de63

File tree

2 files changed

+161
-67
lines changed

2 files changed

+161
-67
lines changed

src/librustc/back/link.rs

Lines changed: 33 additions & 20 deletions
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

Lines changed: 128 additions & 47 deletions
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)