Skip to content

Commit 2e09266

Browse files
committed
Auto merge of #6883 - alexcrichton:pipelining-v2, r=ehuss
Implement the Cargo half of pipelined compilation (take 2) This commit starts to lay the groundwork for #6660 where Cargo will invoke rustc in a "pipelined" fashion. The goal here is to execute one command to produce both an `*.rmeta` file as well as an `*.rlib` file for candidate compilations. In that case if another rlib depends on that compilation, then it can start as soon as the `*.rmeta` is ready and not have to wait for the `*.rlib` compilation. Initially attempted in #6864 with a pretty invasive refactoring this iteration is much more lightweight and fits much more cleanly into Cargo's backend. The approach taken here is to update the `DependencyQueue` structure to carry a piece of data on each dependency edge. This edge information represents the artifact that one node requires from another, and then we a node has no outgoing edges it's ready to build. A dependency on a metadata file is modeled as just that, a dependency on just the metadata and not the full build itself. Most of cargo's backend doesn't really need to know about this edge information so it's basically just calculated as we insert nodes into the `DependencyQueue`. Once that's all in place it's just a few pieces here and there to identify compilations that *can* be pipelined and then they're wired up to depend on the rmeta file instead of the rlib file. Closes #6660
2 parents 29b000f + dcd7c48 commit 2e09266

25 files changed

+1036
-565
lines changed

src/cargo/core/compiler/build_context/target_info.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ pub struct TargetInfo {
2020
}
2121

2222
/// Type of each file generated by a Unit.
23-
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
23+
#[derive(Clone, PartialEq, Eq, Debug)]
2424
pub enum FileFlavor {
2525
/// Not a special file type.
2626
Normal,
2727
/// Something you can link against (e.g., a library).
28-
Linkable,
28+
Linkable { rmeta: bool },
2929
/// Piece of external debug information (e.g., `.dSYM`/`.pdb` file).
3030
DebugInfo,
3131
}

src/cargo/core/compiler/context/compilation_files.rs

+11-2
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> {
308308
path,
309309
hardlink: None,
310310
export_path: None,
311-
flavor: FileFlavor::Linkable,
311+
flavor: FileFlavor::Linkable { rmeta: false },
312312
});
313313
} else {
314314
let mut add = |crate_type: &str, flavor: FileFlavor| -> CargoResult<()> {
@@ -372,12 +372,21 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> {
372372
add(
373373
kind.crate_type(),
374374
if kind.linkable() {
375-
FileFlavor::Linkable
375+
FileFlavor::Linkable { rmeta: false }
376376
} else {
377377
FileFlavor::Normal
378378
},
379379
)?;
380380
}
381+
let path = out_dir.join(format!("lib{}.rmeta", file_stem));
382+
if !unit.target.requires_upstream_objects() {
383+
ret.push(OutputFile {
384+
path,
385+
hardlink: None,
386+
export_path: None,
387+
flavor: FileFlavor::Linkable { rmeta: true },
388+
});
389+
}
381390
}
382391
}
383392
}

src/cargo/core/compiler/context/mod.rs

+41-6
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,17 @@ pub struct Context<'a, 'cfg: 'a> {
4242
unit_dependencies: HashMap<Unit<'a>, Vec<Unit<'a>>>,
4343
files: Option<CompilationFiles<'a, 'cfg>>,
4444
package_cache: HashMap<PackageId, &'a Package>,
45+
46+
/// A flag indicating whether pipelining is enabled for this compilation
47+
/// session. Pipelining largely only affects the edges of the dependency
48+
/// graph that we generate at the end, and otherwise it's pretty
49+
/// straightforward.
50+
pipelining: bool,
51+
52+
/// A set of units which are compiling rlibs and are expected to produce
53+
/// metadata files in addition to the rlib itself. This is only filled in
54+
/// when `pipelining` above is enabled.
55+
rmeta_required: HashSet<Unit<'a>>,
4556
}
4657

4758
impl<'a, 'cfg> Context<'a, 'cfg> {
@@ -60,6 +71,12 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
6071
.chain_err(|| "failed to create jobserver")?,
6172
};
6273

74+
let pipelining = bcx
75+
.config
76+
.get_bool("build.pipelining")?
77+
.map(|t| t.val)
78+
.unwrap_or(false);
79+
6380
Ok(Self {
6481
bcx,
6582
compilation: Compilation::new(bcx)?,
@@ -76,6 +93,8 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
7693
unit_dependencies: HashMap::new(),
7794
files: None,
7895
package_cache: HashMap::new(),
96+
rmeta_required: HashSet::new(),
97+
pipelining,
7998
})
8099
}
81100

@@ -261,12 +280,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
261280
self.primary_packages
262281
.extend(units.iter().map(|u| u.pkg.package_id()));
263282

264-
build_unit_dependencies(
265-
units,
266-
self.bcx,
267-
&mut self.unit_dependencies,
268-
&mut self.package_cache,
269-
)?;
283+
build_unit_dependencies(self, units)?;
270284
let files = CompilationFiles::new(
271285
units,
272286
host_layout,
@@ -453,6 +467,27 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
453467
}
454468
Ok(())
455469
}
470+
471+
/// Returns whether when `parent` depends on `dep` if it only requires the
472+
/// metadata file from `dep`.
473+
pub fn only_requires_rmeta(&self, parent: &Unit<'a>, dep: &Unit<'a>) -> bool {
474+
// this is only enabled when pipelining is enabled
475+
self.pipelining
476+
// We're only a candidate for requiring an `rmeta` file if we
477+
// ourselves are building an rlib,
478+
&& !parent.target.requires_upstream_objects()
479+
&& parent.mode == CompileMode::Build
480+
// Our dependency must also be built as an rlib, otherwise the
481+
// object code must be useful in some fashion
482+
&& !dep.target.requires_upstream_objects()
483+
&& dep.mode == CompileMode::Build
484+
}
485+
486+
/// Returns whether when `unit` is built whether it should emit metadata as
487+
/// well because some compilations rely on that.
488+
pub fn rmeta_required(&self, unit: &Unit<'a>) -> bool {
489+
self.rmeta_required.contains(unit)
490+
}
456491
}
457492

458493
#[derive(Default)]

src/cargo/core/compiler/context/unit_dependencies.rs

+52-36
Original file line numberDiff line numberDiff line change
@@ -15,41 +15,35 @@
1515
//! (for example, with and without tests), so we actually build a dependency
1616
//! graph of `Unit`s, which capture these properties.
1717
18-
use std::cell::RefCell;
19-
use std::collections::{HashMap, HashSet};
20-
21-
use log::trace;
22-
23-
use super::{BuildContext, CompileMode, Kind};
2418
use crate::core::compiler::Unit;
19+
use crate::core::compiler::{BuildContext, CompileMode, Context, Kind};
2520
use crate::core::dependency::Kind as DepKind;
2621
use crate::core::package::Downloads;
2722
use crate::core::profiles::UnitFor;
2823
use crate::core::{Package, PackageId, Target};
2924
use crate::CargoResult;
25+
use log::trace;
26+
use std::collections::{HashMap, HashSet};
3027

3128
struct State<'a: 'tmp, 'cfg: 'a, 'tmp> {
32-
bcx: &'tmp BuildContext<'a, 'cfg>,
33-
deps: &'tmp mut HashMap<Unit<'a>, Vec<Unit<'a>>>,
34-
pkgs: RefCell<&'tmp mut HashMap<PackageId, &'a Package>>,
29+
cx: &'tmp mut Context<'a, 'cfg>,
3530
waiting_on_download: HashSet<PackageId>,
3631
downloads: Downloads<'a, 'cfg>,
3732
}
3833

3934
pub fn build_unit_dependencies<'a, 'cfg>(
35+
cx: &mut Context<'a, 'cfg>,
4036
roots: &[Unit<'a>],
41-
bcx: &BuildContext<'a, 'cfg>,
42-
deps: &mut HashMap<Unit<'a>, Vec<Unit<'a>>>,
43-
pkgs: &mut HashMap<PackageId, &'a Package>,
4437
) -> CargoResult<()> {
45-
assert!(deps.is_empty(), "can only build unit deps once");
38+
assert!(
39+
cx.unit_dependencies.is_empty(),
40+
"can only build unit deps once"
41+
);
4642

4743
let mut state = State {
48-
bcx,
49-
deps,
50-
pkgs: RefCell::new(pkgs),
44+
downloads: cx.bcx.packages.enable_download()?,
45+
cx,
5146
waiting_on_download: HashSet::new(),
52-
downloads: bcx.packages.enable_download()?,
5347
};
5448

5549
loop {
@@ -62,7 +56,7 @@ pub fn build_unit_dependencies<'a, 'cfg>(
6256
// cleared, and avoid building the lib thrice (once with `panic`, once
6357
// without, once for `--test`). In particular, the lib included for
6458
// Doc tests and examples are `Build` mode here.
65-
let unit_for = if unit.mode.is_any_test() || bcx.build_config.test() {
59+
let unit_for = if unit.mode.is_any_test() || state.cx.bcx.build_config.test() {
6660
UnitFor::new_test()
6761
} else if unit.target.is_custom_build() {
6862
// This normally doesn't happen, except `clean` aggressively
@@ -79,20 +73,23 @@ pub fn build_unit_dependencies<'a, 'cfg>(
7973

8074
if !state.waiting_on_download.is_empty() {
8175
state.finish_some_downloads()?;
82-
state.deps.clear();
76+
state.cx.unit_dependencies.clear();
8377
} else {
8478
break;
8579
}
8680
}
87-
trace!("ALL UNIT DEPENDENCIES {:#?}", state.deps);
8881

8982
connect_run_custom_build_deps(&mut state);
9083

84+
trace!("ALL UNIT DEPENDENCIES {:#?}", state.cx.unit_dependencies);
85+
86+
record_units_requiring_metadata(state.cx);
87+
9188
// Dependencies are used in tons of places throughout the backend, many of
9289
// which affect the determinism of the build itself. As a result be sure
9390
// that dependency lists are always sorted to ensure we've always got a
9491
// deterministic output.
95-
for list in state.deps.values_mut() {
92+
for list in state.cx.unit_dependencies.values_mut() {
9693
list.sort();
9794
}
9895

@@ -104,16 +101,16 @@ fn deps_of<'a, 'cfg, 'tmp>(
104101
state: &mut State<'a, 'cfg, 'tmp>,
105102
unit_for: UnitFor,
106103
) -> CargoResult<()> {
107-
// Currently the `deps` map does not include `unit_for`. This should
104+
// Currently the `unit_dependencies` map does not include `unit_for`. This should
108105
// be safe for now. `TestDependency` only exists to clear the `panic`
109106
// flag, and you'll never ask for a `unit` with `panic` set as a
110107
// `TestDependency`. `CustomBuild` should also be fine since if the
111108
// requested unit's settings are the same as `Any`, `CustomBuild` can't
112109
// affect anything else in the hierarchy.
113-
if !state.deps.contains_key(unit) {
110+
if !state.cx.unit_dependencies.contains_key(unit) {
114111
let unit_deps = compute_deps(unit, state, unit_for)?;
115112
let to_insert: Vec<_> = unit_deps.iter().map(|&(unit, _)| unit).collect();
116-
state.deps.insert(*unit, to_insert);
113+
state.cx.unit_dependencies.insert(*unit, to_insert);
117114
for (unit, unit_for) in unit_deps {
118115
deps_of(&unit, state, unit_for)?;
119116
}
@@ -131,13 +128,13 @@ fn compute_deps<'a, 'cfg, 'tmp>(
131128
unit_for: UnitFor,
132129
) -> CargoResult<Vec<(Unit<'a>, UnitFor)>> {
133130
if unit.mode.is_run_custom_build() {
134-
return compute_deps_custom_build(unit, state.bcx);
131+
return compute_deps_custom_build(unit, state.cx.bcx);
135132
} else if unit.mode.is_doc() && !unit.mode.is_any_test() {
136133
// Note: this does not include doc test.
137134
return compute_deps_doc(unit, state);
138135
}
139136

140-
let bcx = state.bcx;
137+
let bcx = state.cx.bcx;
141138
let id = unit.pkg.package_id();
142139
let deps = bcx.resolve.deps(id).filter(|&(_id, deps)| {
143140
assert!(!deps.is_empty());
@@ -295,7 +292,7 @@ fn compute_deps_doc<'a, 'cfg, 'tmp>(
295292
unit: &Unit<'a>,
296293
state: &mut State<'a, 'cfg, 'tmp>,
297294
) -> CargoResult<Vec<(Unit<'a>, UnitFor)>> {
298-
let bcx = state.bcx;
295+
let bcx = state.cx.bcx;
299296
let deps = bcx
300297
.resolve
301298
.deps(unit.pkg.package_id())
@@ -448,7 +445,7 @@ fn connect_run_custom_build_deps(state: &mut State<'_, '_, '_>) {
448445
// have the build script as the key and the library would be in the
449446
// value's set.
450447
let mut reverse_deps = HashMap::new();
451-
for (unit, deps) in state.deps.iter() {
448+
for (unit, deps) in state.cx.unit_dependencies.iter() {
452449
for dep in deps {
453450
if dep.mode == CompileMode::RunCustomBuild {
454451
reverse_deps
@@ -469,7 +466,8 @@ fn connect_run_custom_build_deps(state: &mut State<'_, '_, '_>) {
469466
// `dep_build_script` to manufacture an appropriate build script unit to
470467
// depend on.
471468
for unit in state
472-
.deps
469+
.cx
470+
.unit_dependencies
473471
.keys()
474472
.filter(|k| k.mode == CompileMode::RunCustomBuild)
475473
{
@@ -480,13 +478,13 @@ fn connect_run_custom_build_deps(state: &mut State<'_, '_, '_>) {
480478

481479
let to_add = reverse_deps
482480
.iter()
483-
.flat_map(|reverse_dep| state.deps[reverse_dep].iter())
481+
.flat_map(|reverse_dep| state.cx.unit_dependencies[reverse_dep].iter())
484482
.filter(|other| {
485483
other.pkg != unit.pkg
486484
&& other.target.linkable()
487485
&& other.pkg.manifest().links().is_some()
488486
})
489-
.filter_map(|other| dep_build_script(other, state.bcx).map(|p| p.0))
487+
.filter_map(|other| dep_build_script(other, state.cx.bcx).map(|p| p.0))
490488
.collect::<HashSet<_>>();
491489

492490
if !to_add.is_empty() {
@@ -497,21 +495,39 @@ fn connect_run_custom_build_deps(state: &mut State<'_, '_, '_>) {
497495

498496
// And finally, add in all the missing dependencies!
499497
for (unit, new_deps) in new_deps {
500-
state.deps.get_mut(&unit).unwrap().extend(new_deps);
498+
state
499+
.cx
500+
.unit_dependencies
501+
.get_mut(&unit)
502+
.unwrap()
503+
.extend(new_deps);
504+
}
505+
}
506+
507+
/// Records the list of units which are required to emit metadata.
508+
///
509+
/// Units which depend only on the metadata of others requires the others to
510+
/// actually produce metadata, so we'll record that here.
511+
fn record_units_requiring_metadata(cx: &mut Context<'_, '_>) {
512+
for (key, deps) in cx.unit_dependencies.iter() {
513+
for dep in deps {
514+
if cx.only_requires_rmeta(key, dep) {
515+
cx.rmeta_required.insert(*dep);
516+
}
517+
}
501518
}
502519
}
503520

504521
impl<'a, 'cfg, 'tmp> State<'a, 'cfg, 'tmp> {
505522
fn get(&mut self, id: PackageId) -> CargoResult<Option<&'a Package>> {
506-
let mut pkgs = self.pkgs.borrow_mut();
507-
if let Some(pkg) = pkgs.get(&id) {
523+
if let Some(pkg) = self.cx.package_cache.get(&id) {
508524
return Ok(Some(pkg));
509525
}
510526
if !self.waiting_on_download.insert(id) {
511527
return Ok(None);
512528
}
513529
if let Some(pkg) = self.downloads.start(id)? {
514-
pkgs.insert(id, pkg);
530+
self.cx.package_cache.insert(id, pkg);
515531
self.waiting_on_download.remove(&id);
516532
return Ok(Some(pkg));
517533
}
@@ -531,7 +547,7 @@ impl<'a, 'cfg, 'tmp> State<'a, 'cfg, 'tmp> {
531547
loop {
532548
let pkg = self.downloads.wait()?;
533549
self.waiting_on_download.remove(&pkg.package_id());
534-
self.pkgs.borrow_mut().insert(pkg.package_id(), pkg);
550+
self.cx.package_cache.insert(pkg.package_id(), pkg);
535551

536552
// Arbitrarily choose that 5 or more packages concurrently download
537553
// is a good enough number to "fill the network pipe". If we have

0 commit comments

Comments
 (0)