Skip to content

Commit 7046fea

Browse files
committed
rustbuild: Compile rustc twice, not thrice
This commit switches the rustbuild build system to compiling the compiler twice for a normal bootstrap rather than the historical three times. Rust is a bootstrapped language which means that a previous version of the compiler is used to build the next version of the compiler. Over time, however, we change many parts of compiler artifacts such as the metadata format, symbol names, etc. These changes make artifacts from one compiler incompatible from another compiler. Consequently if a compiler wants to be able to use some artifacts then it itself must have compiled the artifacts. Historically the rustc build system has achieved this by compiling the compiler three times: * An older compiler (stage0) is downloaded to kick off the chain. * This compiler now compiles a new compiler (stage1) * The stage1 compiler then compiles another compiler (stage2) * Finally, the stage2 compiler needs libraries to link against, so it compiles all the libraries again. This entire process amounts in compiling the compiler three times. Additionally, this process always guarantees that the Rust source tree can compile itself because the stage2 compiler (created by a freshly created compiler) would successfully compile itself again. This property, ensuring Rust can compile itself, is quite important! In general, though, this third compilation is not required for general purpose development on the compiler. The third compiler (stage2) can reuse the libraries that were created during the second compile. In other words, the second compilation can produce both a compiler and the libraries that compiler will use. These artifacts *must* be compatible due to the way plugins work today anyway, and they were created by the same source code so they *should* be compatible as well. So given all that, this commit switches the default build process to only compile the compiler three times, avoiding this third compilation by copying artifacts from the previous one. Along the way a new entry in the Travis matrix was also added to ensure that our full bootstrap can succeed. This entry does not run tests, though, as it should not be necessary. To restore the old behavior of a full bootstrap (three compiles) you can either pass: ./configure --enable-full-bootstrap or if you're using config.toml: [build] full-bootstrap = true Overall this will hopefully be an easy 33% win in build times of the compiler. If we do 33% less work we should be 33% faster! This in turn should affect cycle times and such on Travis and AppVeyor positively as well as making it easier to work on the compiler itself.
1 parent b7e5148 commit 7046fea

File tree

10 files changed

+262
-113
lines changed

10 files changed

+262
-113
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ matrix:
1717
- env: IMAGE=i686-gnu-nopt
1818
- env: IMAGE=x86_64-freebsd
1919
- env: IMAGE=x86_64-gnu
20+
- env: IMAGE=x86_64-gnu-full-bootstrap
2021
- env: IMAGE=x86_64-gnu-cargotest
2122
- env: IMAGE=x86_64-gnu-debug
2223
- env: IMAGE=x86_64-gnu-nopt

configure

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,7 @@ opt_nosave manage-submodules 1 "let the build manage the git submodules"
693693
opt_nosave clang 0 "prefer clang to gcc for building the runtime"
694694
opt_nosave jemalloc 1 "build liballoc with jemalloc"
695695
opt elf-tls 1 "elf thread local storage on platforms where supported"
696+
opt full-bootstrap 0 "build three compilers instead of two"
696697

697698
valopt_nosave prefix "/usr/local" "set installation prefix"
698699
valopt_nosave local-rust-root "/usr/local" "set prefix for local rust binary"

src/bootstrap/check.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -341,12 +341,22 @@ pub fn krate(build: &Build,
341341
println!("{} {} stage{} ({} -> {})", test_kind, name, compiler.stage,
342342
compiler.host, target);
343343

344+
// If we're not doing a full bootstrap but we're testing a stage2 version of
345+
// libstd, then what we're actually testing is the libstd produced in
346+
// stage1. Reflect that here by updating the compiler that we're working
347+
// with automatically.
348+
let compiler = if build.force_use_stage1(compiler, target) {
349+
Compiler::new(1, compiler.host)
350+
} else {
351+
compiler.clone()
352+
};
353+
344354
// Build up the base `cargo test` command.
345355
//
346356
// Pass in some standard flags then iterate over the graph we've discovered
347357
// in `cargo metadata` with the maps above and figure out what `-p`
348358
// arguments need to get passed.
349-
let mut cargo = build.cargo(compiler, mode, target, test_kind.subcommand());
359+
let mut cargo = build.cargo(&compiler, mode, target, test_kind.subcommand());
350360
cargo.arg("--manifest-path")
351361
.arg(build.src.join(path).join("Cargo.toml"))
352362
.arg("--features").arg(features);
@@ -380,7 +390,7 @@ pub fn krate(build: &Build,
380390
// Note that to run the compiler we need to run with the *host* libraries,
381391
// but our wrapper scripts arrange for that to be the case anyway.
382392
let mut dylib_path = dylib_path();
383-
dylib_path.insert(0, build.sysroot_libdir(compiler, target));
393+
dylib_path.insert(0, build.sysroot_libdir(&compiler, target));
384394
cargo.env(dylib_path_var(), env::join_paths(&dylib_path).unwrap());
385395

386396
if target.contains("android") {
@@ -399,10 +409,10 @@ pub fn krate(build: &Build,
399409

400410
if target.contains("android") {
401411
build.run(&mut cargo);
402-
krate_android(build, compiler, target, mode);
412+
krate_android(build, &compiler, target, mode);
403413
} else if target.contains("emscripten") {
404414
build.run(&mut cargo);
405-
krate_emscripten(build, compiler, target, mode);
415+
krate_emscripten(build, &compiler, target, mode);
406416
} else {
407417
cargo.args(&build.flags.cmd.test_args());
408418
build.run(&mut cargo);

src/bootstrap/compile.rs

Lines changed: 45 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@ use {Build, Compiler, Mode};
3333
/// This will build the standard library for a particular stage of the build
3434
/// using the `compiler` targeting the `target` architecture. The artifacts
3535
/// created will also be linked into the sysroot directory.
36-
pub fn std<'a>(build: &'a Build, target: &str, compiler: &Compiler<'a>) {
37-
println!("Building stage{} std artifacts ({} -> {})", compiler.stage,
38-
compiler.host, target);
39-
36+
pub fn std(build: &Build, target: &str, compiler: &Compiler) {
4037
let libdir = build.sysroot_libdir(compiler, target);
4138
let _ = fs::remove_dir_all(&libdir);
4239
t!(fs::create_dir_all(&libdir));
4340

41+
println!("Building stage{} std artifacts ({} -> {})", compiler.stage,
42+
compiler.host, target);
43+
4444
// Some platforms have startup objects that may be required to produce the
4545
// libstd dynamic library, for example.
4646
build_startup_objects(build, target, &libdir);
@@ -65,29 +65,30 @@ pub fn std<'a>(build: &'a Build, target: &str, compiler: &Compiler<'a>) {
6565

6666
build.run(&mut cargo);
6767
update_mtime(&libstd_stamp(build, &compiler, target));
68-
std_link(build, target, compiler.stage, compiler.host);
6968
}
7069

7170
/// Link all libstd rlibs/dylibs into the sysroot location.
7271
///
73-
/// Links those artifacts generated in the given `stage` for `target` produced
74-
/// by `compiler` into `host`'s sysroot.
72+
/// Links those artifacts generated by `compiler` to a the `stage` compiler's
73+
/// sysroot for the specified `host` and `target`.
74+
///
75+
/// Note that this assumes that `compiler` has already generated the libstd
76+
/// libraries for `target`, and this method will find them in the relevant
77+
/// output directory.
7578
pub fn std_link(build: &Build,
76-
target: &str,
77-
stage: u32,
78-
host: &str) {
79-
let compiler = Compiler::new(stage, &build.config.build);
80-
let target_compiler = Compiler::new(compiler.stage, host);
79+
compiler: &Compiler,
80+
target_compiler: &Compiler,
81+
target: &str) {
82+
println!("Copying stage{} std from stage{} ({} -> {} / {})",
83+
target_compiler.stage,
84+
compiler.stage,
85+
compiler.host,
86+
target_compiler.host,
87+
target);
8188
let libdir = build.sysroot_libdir(&target_compiler, target);
8289
let out_dir = build.cargo_out(&compiler, Mode::Libstd, target);
8390

84-
// If we're linking one compiler host's output into another, then we weren't
85-
// called from the `std` method above. In that case we clean out what's
86-
// already there.
87-
if host != compiler.host {
88-
let _ = fs::remove_dir_all(&libdir);
89-
t!(fs::create_dir_all(&libdir));
90-
}
91+
t!(fs::create_dir_all(&libdir));
9192
add_to_sysroot(&out_dir, &libdir);
9293

9394
if target.contains("musl") && !target.contains("mips") {
@@ -137,7 +138,7 @@ fn build_startup_objects(build: &Build, target: &str, into: &Path) {
137138
/// This will build libtest and supporting libraries for a particular stage of
138139
/// the build using the `compiler` targeting the `target` architecture. The
139140
/// artifacts created will also be linked into the sysroot directory.
140-
pub fn test<'a>(build: &'a Build, target: &str, compiler: &Compiler<'a>) {
141+
pub fn test(build: &Build, target: &str, compiler: &Compiler) {
141142
println!("Building stage{} test artifacts ({} -> {})", compiler.stage,
142143
compiler.host, target);
143144
let out_dir = build.cargo_out(compiler, Mode::Libtest, target);
@@ -147,19 +148,13 @@ pub fn test<'a>(build: &'a Build, target: &str, compiler: &Compiler<'a>) {
147148
.arg(build.src.join("src/rustc/test_shim/Cargo.toml"));
148149
build.run(&mut cargo);
149150
update_mtime(&libtest_stamp(build, compiler, target));
150-
test_link(build, target, compiler.stage, compiler.host);
151151
}
152152

153-
/// Link all libtest rlibs/dylibs into the sysroot location.
154-
///
155-
/// Links those artifacts generated in the given `stage` for `target` produced
156-
/// by `compiler` into `host`'s sysroot.
153+
/// Same as `std_link`, only for libtest
157154
pub fn test_link(build: &Build,
158-
target: &str,
159-
stage: u32,
160-
host: &str) {
161-
let compiler = Compiler::new(stage, &build.config.build);
162-
let target_compiler = Compiler::new(compiler.stage, host);
155+
compiler: &Compiler,
156+
target_compiler: &Compiler,
157+
target: &str) {
163158
let libdir = build.sysroot_libdir(&target_compiler, target);
164159
let out_dir = build.cargo_out(&compiler, Mode::Libtest, target);
165160
add_to_sysroot(&out_dir, &libdir);
@@ -170,7 +165,7 @@ pub fn test_link(build: &Build,
170165
/// This will build the compiler for a particular stage of the build using
171166
/// the `compiler` targeting the `target` architecture. The artifacts
172167
/// created will also be linked into the sysroot directory.
173-
pub fn rustc<'a>(build: &'a Build, target: &str, compiler: &Compiler<'a>) {
168+
pub fn rustc(build: &Build, target: &str, compiler: &Compiler) {
174169
println!("Building stage{} compiler artifacts ({} -> {})",
175170
compiler.stage, compiler.host, target);
176171

@@ -222,20 +217,13 @@ pub fn rustc<'a>(build: &'a Build, target: &str, compiler: &Compiler<'a>) {
222217
cargo.env("CFG_DEFAULT_AR", s);
223218
}
224219
build.run(&mut cargo);
225-
226-
rustc_link(build, target, compiler.stage, compiler.host);
227220
}
228221

229-
/// Link all librustc rlibs/dylibs into the sysroot location.
230-
///
231-
/// Links those artifacts generated in the given `stage` for `target` produced
232-
/// by `compiler` into `host`'s sysroot.
222+
/// Same as `std_link`, only for librustc
233223
pub fn rustc_link(build: &Build,
234-
target: &str,
235-
stage: u32,
236-
host: &str) {
237-
let compiler = Compiler::new(stage, &build.config.build);
238-
let target_compiler = Compiler::new(compiler.stage, host);
224+
compiler: &Compiler,
225+
target_compiler: &Compiler,
226+
target: &str) {
239227
let libdir = build.sysroot_libdir(&target_compiler, target);
240228
let out_dir = build.cargo_out(&compiler, Mode::Librustc, target);
241229
add_to_sysroot(&out_dir, &libdir);
@@ -259,6 +247,17 @@ fn compiler_file(compiler: &Path, file: &str) -> PathBuf {
259247
PathBuf::from(out.trim())
260248
}
261249

250+
pub fn create_sysroot(build: &Build, compiler: &Compiler) {
251+
// nothing to do in stage0
252+
if compiler.stage == 0 {
253+
return
254+
}
255+
256+
let sysroot = build.sysroot(compiler);
257+
let _ = fs::remove_dir_all(&sysroot);
258+
t!(fs::create_dir_all(&sysroot));
259+
}
260+
262261
/// Prepare a new compiler from the artifacts in `stage`
263262
///
264263
/// This will assemble a compiler in `build/$host/stage$stage`. The compiler
@@ -269,18 +268,17 @@ pub fn assemble_rustc(build: &Build, stage: u32, host: &str) {
269268
if stage == 0 {
270269
return
271270
}
271+
272+
println!("Copying stage{} compiler ({})", stage, host);
273+
272274
// The compiler that we're assembling
273275
let target_compiler = Compiler::new(stage, host);
274276

275277
// The compiler that compiled the compiler we're assembling
276278
let build_compiler = Compiler::new(stage - 1, &build.config.build);
277279

278-
// Clear out old files
279-
let sysroot = build.sysroot(&target_compiler);
280-
let _ = fs::remove_dir_all(&sysroot);
281-
t!(fs::create_dir_all(&sysroot));
282-
283280
// Link in all dylibs to the libdir
281+
let sysroot = build.sysroot(&target_compiler);
284282
let sysroot_libdir = sysroot.join(libdir(host));
285283
t!(fs::create_dir_all(&sysroot_libdir));
286284
let src_libdir = build.sysroot_libdir(&build_compiler, host);

src/bootstrap/config.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ pub struct Config {
4646
pub docs: bool,
4747
pub vendor: bool,
4848
pub target_config: HashMap<String, Target>,
49+
pub full_bootstrap: bool,
4950

5051
// llvm codegen options
5152
pub llvm_assertions: bool,
@@ -134,6 +135,7 @@ struct Build {
134135
vendor: Option<bool>,
135136
nodejs: Option<String>,
136137
python: Option<String>,
138+
full_bootstrap: Option<bool>,
137139
}
138140

139141
/// TOML representation of various global install decisions.
@@ -264,6 +266,7 @@ impl Config {
264266
set(&mut config.docs, build.docs);
265267
set(&mut config.submodules, build.submodules);
266268
set(&mut config.vendor, build.vendor);
269+
set(&mut config.full_bootstrap, build.full_bootstrap);
267270

268271
if let Some(ref install) = toml.install {
269272
config.prefix = install.prefix.clone();
@@ -393,6 +396,7 @@ impl Config {
393396
("NINJA", self.ninja),
394397
("CODEGEN_TESTS", self.codegen_tests),
395398
("VENDOR", self.vendor),
399+
("FULL_BOOTSTRAP", self.full_bootstrap),
396400
}
397401

398402
match key {

src/bootstrap/config.toml.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,13 @@
100100
# Indicate whether the vendored sources are used for Rust dependencies or not
101101
#vendor = false
102102

103+
# Typically the build system will build the rust compiler twice. The second
104+
# compiler, however, will simply use its own libraries to link against. If you
105+
# would rather to perform a full bootstrap, compiling the compiler three times,
106+
# then you can set this option to true. You shouldn't ever need to set this
107+
# option to true.
108+
#full-bootstrap = false
109+
103110
# =============================================================================
104111
# General install configuration options
105112
# =============================================================================

src/bootstrap/doc.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,11 @@ pub fn std(build: &Build, stage: u32, target: &str) {
137137
let out = build.doc_out(target);
138138
t!(fs::create_dir_all(&out));
139139
let compiler = Compiler::new(stage, &build.config.build);
140+
let compiler = if build.force_use_stage1(&compiler, target) {
141+
Compiler::new(1, compiler.host)
142+
} else {
143+
compiler
144+
};
140145
let out_dir = build.stage_out(&compiler, Mode::Libstd)
141146
.join(target).join("doc");
142147
let rustdoc = build.rustdoc(&compiler);
@@ -160,6 +165,11 @@ pub fn test(build: &Build, stage: u32, target: &str) {
160165
let out = build.doc_out(target);
161166
t!(fs::create_dir_all(&out));
162167
let compiler = Compiler::new(stage, &build.config.build);
168+
let compiler = if build.force_use_stage1(&compiler, target) {
169+
Compiler::new(1, compiler.host)
170+
} else {
171+
compiler
172+
};
163173
let out_dir = build.stage_out(&compiler, Mode::Libtest)
164174
.join(target).join("doc");
165175
let rustdoc = build.rustdoc(&compiler);
@@ -182,6 +192,11 @@ pub fn rustc(build: &Build, stage: u32, target: &str) {
182192
let out = build.doc_out(target);
183193
t!(fs::create_dir_all(&out));
184194
let compiler = Compiler::new(stage, &build.config.build);
195+
let compiler = if build.force_use_stage1(&compiler, target) {
196+
Compiler::new(1, compiler.host)
197+
} else {
198+
compiler
199+
};
185200
let out_dir = build.stage_out(&compiler, Mode::Librustc)
186201
.join(target).join("doc");
187202
let rustdoc = build.rustdoc(&compiler);

src/bootstrap/lib.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -572,9 +572,7 @@ impl Build {
572572
let mut cmd = Command::new(self.tool(&compiler, tool));
573573
let host = compiler.host;
574574
let mut paths = vec![
575-
self.cargo_out(compiler, Mode::Libstd, host).join("deps"),
576-
self.cargo_out(compiler, Mode::Libtest, host).join("deps"),
577-
self.cargo_out(compiler, Mode::Librustc, host).join("deps"),
575+
self.sysroot_libdir(compiler, compiler.host),
578576
self.cargo_out(compiler, Mode::Tool, host).join("deps"),
579577
];
580578

@@ -880,6 +878,30 @@ impl Build {
880878
fn python(&self) -> &Path {
881879
self.config.python.as_ref().unwrap()
882880
}
881+
882+
/// Tests whether the `compiler` compiling for `target` should be forced to
883+
/// use a stage1 compiler instead.
884+
///
885+
/// Currently, by default, the build system does not perform a "full
886+
/// bootstrap" by default where we compile the compiler three times.
887+
/// Instead, we compile the compiler two times. The final stage (stage2)
888+
/// just copies the libraries from the previous stage, which is what this
889+
/// method detects.
890+
///
891+
/// Here we return `true` if:
892+
///
893+
/// * The build isn't performing a full bootstrap
894+
/// * The `compiler` is in the final stage, 2
895+
/// * We're not cross-compiling, so the artifacts are already available in
896+
/// stage1
897+
///
898+
/// When all of these conditions are met the build will lift artifacts from
899+
/// the previous stage forward.
900+
fn force_use_stage1(&self, compiler: &Compiler, target: &str) -> bool {
901+
!self.config.full_bootstrap &&
902+
compiler.stage >= 2 &&
903+
self.config.host.iter().any(|h| h == target)
904+
}
883905
}
884906

885907
impl<'a> Compiler<'a> {

0 commit comments

Comments
 (0)