Skip to content

Commit 1caae51

Browse files
committed
Start preparing Cargo for pipelined rustc compilation
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. The major refactoring in this commit is to add a new form of `CompileMode`: `BuildRmeta`. This mode is introduced to represent that a dependency edge only depends on the metadata of a compilation rather than the the entire linked artifact. After this is introduced the next major change is to actually hook this up into the dependency graph. The approach taken by this commit is to have a postprocessing pass over the dependency graph. After we build a map of all dependencies between units a "pipelining" pass runs and actually introduces the `BuildRmeta` mode. This also makes it trivial to disable/enable pipelining which we'll probably want to do for a preview period at least! The `pipeline_compilations` function is intended to be extensively documented with the graph that it creates as well as how it works in terms of adding `BuildRmeta` nodes into the dependency graph. This commit is not all that will be required for pieplining compilations. It does, however, get the entire test suite passing with this refactoring. The way this works is by ensuring that a pipelined unit, one split from `Build` into both `Build` and `BuildRmeta`, to be a unit that doesn't actually do any work. That way the `BuildRmeta` actually does all the work currently and we should have a working Cargo like we did before. Subsequent commits will work in updating the `JobQueue` to account for pipelining... Note that this commit itself doesn't really contain any tests because there's no functional change to Cargo, only internal refactorings. This does have a large impact on the test suite because the `--emit` flag has now changed by default, so lots of test assertions needed updating.
1 parent 6562942 commit 1caae51

25 files changed

+596
-256
lines changed

src/cargo/core/compiler/build_config.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ pub enum CompileMode {
127127
Test,
128128
/// Building a target with `rustc` (lib or bin).
129129
Build,
130+
BuildRmeta,
130131
/// Building a target with `rustc` to emit `rmeta` metadata only. If
131132
/// `test` is true, then it is also compiled with `--test` to check it like
132133
/// a test.
@@ -154,6 +155,7 @@ impl ser::Serialize for CompileMode {
154155
match *self {
155156
Test => "test".serialize(s),
156157
Build => "build".serialize(s),
158+
BuildRmeta => "build-rmeta".serialize(s),
157159
Check { .. } => "check".serialize(s),
158160
Bench => "bench".serialize(s),
159161
Doc { .. } => "doc".serialize(s),
@@ -202,9 +204,10 @@ impl CompileMode {
202204
/// List of all modes (currently used by `cargo clean -p` for computing
203205
/// all possible outputs).
204206
pub fn all_modes() -> &'static [CompileMode] {
205-
static ALL: [CompileMode; 9] = [
207+
static ALL: &[CompileMode] = &[
206208
CompileMode::Test,
207209
CompileMode::Build,
210+
CompileMode::BuildRmeta,
208211
CompileMode::Check { test: true },
209212
CompileMode::Check { test: false },
210213
CompileMode::Bench,
@@ -213,6 +216,6 @@ impl CompileMode {
213216
CompileMode::Doctest,
214217
CompileMode::RunCustomBuild,
215218
];
216-
&ALL
219+
ALL
217220
}
218221
}

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ use crate::core::profiles::Profiles;
88
use crate::core::{Dependency, Workspace};
99
use crate::core::{PackageId, PackageSet, Resolve};
1010
use crate::util::errors::CargoResult;
11-
use crate::util::{profile, Cfg, Config, Rustc};
12-
use crate::core::compiler::{Unit, Kind, BuildConfig, BuildOutput};
11+
use crate::core::compiler::{Unit, Kind, BuildConfig, BuildOutput, CompileMode};
1312
use crate::core::compiler::unit::UnitInterner;
13+
use crate::util::{profile, Cfg, Config, Rustc};
1414

1515
mod target_info;
1616
pub use self::target_info::{FileFlavor, TargetInfo};
@@ -179,7 +179,18 @@ impl<'a, 'cfg> BuildContext<'a, 'cfg> {
179179
}
180180

181181
pub fn extra_args_for(&self, unit: &Unit<'a>) -> Option<&Vec<String>> {
182-
self.extra_compiler_args.get(unit)
182+
// Extra arguments are currently only registered for top-level units and
183+
// this is how `cargo rustc` and `cargo rustdoc` are implemented. We may
184+
// split a top level unit though for pipelining, and the actual work
185+
// happens in the `BuildRmeta` stage and not the `Build` stage. To
186+
// handle that difference and ensure arguments get to the right place be
187+
// sure to switch `BuildRmeta` modes to `Build` for lookup.
188+
if unit.mode == CompileMode::BuildRmeta {
189+
let build = unit.with_mode(CompileMode::Build, self.units);
190+
self.extra_compiler_args.get(&build)
191+
} else {
192+
self.extra_compiler_args.get(unit)
193+
}
183194
}
184195
}
185196

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

Lines changed: 42 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::sync::Arc;
88
use lazycell::LazyCell;
99
use log::info;
1010

11-
use super::{BuildContext, Context, FileFlavor, Kind, Layout};
11+
use super::{BuildContext, Context, FileFlavor, Kind, Layout, CompileMode};
1212
use crate::core::compiler::Unit;
1313
use crate::core::{TargetKind, Workspace};
1414
use crate::util::{self, CargoResult};
@@ -299,18 +299,19 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> {
299299

300300
let mut ret = Vec::new();
301301
let mut unsupported = Vec::new();
302-
{
303-
if unit.mode.is_check() {
302+
match unit.mode {
303+
CompileMode::BuildRmeta | CompileMode::Check { .. } => {
304304
// This may be confusing. rustc outputs a file named `lib*.rmeta`
305305
// for both libraries and binaries.
306306
let path = out_dir.join(format!("lib{}.rmeta", file_stem));
307307
ret.push(OutputFile {
308-
path,
308+
path: path.clone(),
309309
hardlink: None,
310310
export_path: None,
311311
flavor: FileFlavor::Linkable,
312312
});
313-
} else {
313+
}
314+
CompileMode::Test | CompileMode::Build | CompileMode::Bench => {
314315
let mut add = |crate_type: &str, flavor: FileFlavor| -> CargoResult<()> {
315316
let crate_type = if crate_type == "lib" {
316317
"rlib"
@@ -324,34 +325,35 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> {
324325
bcx.target_triple(),
325326
)?;
326327

327-
match file_types {
328-
Some(types) => {
329-
for file_type in types {
330-
let path = out_dir.join(file_type.filename(&file_stem));
331-
let hardlink = link_stem
332-
.as_ref()
333-
.map(|&(ref ld, ref ls)| ld.join(file_type.filename(ls)));
334-
let export_path = if unit.target.is_custom_build() {
335-
None
336-
} else {
337-
self.export_dir.as_ref().and_then(|export_dir| {
338-
hardlink.as_ref().and_then(|hardlink| {
339-
Some(export_dir.join(hardlink.file_name().unwrap()))
340-
})
341-
})
342-
};
343-
ret.push(OutputFile {
344-
path,
345-
hardlink,
346-
export_path,
347-
flavor: file_type.flavor,
348-
});
349-
}
350-
}
328+
let types = match file_types {
329+
Some(types) => types,
351330
// Not supported; don't worry about it.
352331
None => {
353332
unsupported.push(crate_type.to_string());
333+
return Ok(());
354334
}
335+
};
336+
337+
for file_type in types {
338+
let path = out_dir.join(file_type.filename(&file_stem));
339+
let hardlink = link_stem
340+
.as_ref()
341+
.map(|&(ref ld, ref ls)| ld.join(file_type.filename(ls)));
342+
let export_path = if unit.target.is_custom_build() {
343+
None
344+
} else {
345+
self.export_dir.as_ref().and_then(|export_dir| {
346+
hardlink.as_ref().and_then(|hardlink| {
347+
Some(export_dir.join(hardlink.file_name().unwrap()))
348+
})
349+
})
350+
};
351+
ret.push(OutputFile {
352+
path,
353+
hardlink,
354+
export_path,
355+
flavor: file_type.flavor,
356+
});
355357
}
356358
Ok(())
357359
};
@@ -381,6 +383,9 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> {
381383
}
382384
}
383385
}
386+
CompileMode::Doc { .. } | CompileMode::Doctest | CompileMode::RunCustomBuild => {
387+
return Ok(Arc::new(ret));
388+
}
384389
}
385390
if ret.is_empty() {
386391
if !unsupported.is_empty() {
@@ -425,6 +430,14 @@ fn compute_metadata<'a, 'cfg>(
425430
cx: &Context<'a, 'cfg>,
426431
metas: &mut HashMap<Unit<'a>, Option<Metadata>>,
427432
) -> Option<Metadata> {
433+
// Check to see if this is a pipelined unit which means that it doesn't
434+
// actually do any work, but rather its dependency on an rmeta unit will do
435+
// all the work. In that case we'll take the same metadata as our rmeta
436+
// compile to ensure that our file names all align.
437+
if let Some(rmeta) = cx.rmeta_unit_if_pipelined(unit) {
438+
return compute_metadata(&rmeta, cx, metas);
439+
}
440+
428441
// No metadata for dylibs because of a couple issues:
429442
// - macOS encodes the dylib name in the executable,
430443
// - Windows rustc multiple files of which we can't easily link all of them.

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,18 @@ 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 map from a unit to the dependencies listed for it which are considered
47+
// as "order only" dependencies. These dependencies are only used to
48+
// sequence compilation correctly and shouldn't inject `--extern` flags, for
49+
// example.
50+
pub order_only_dependencies: HashMap<Unit<'a>, HashSet<Unit<'a>>>,
51+
52+
// A set of units which have been "pipelined". These units are in `Build`
53+
// mode and depend on a `BuildRmeta` mode unit. The pipelined `Build` units
54+
// should do no work because the `BuildRmeta` unit will do all the work for
55+
// them.
56+
pub pipelined_units: HashSet<Unit<'a>>,
4557
}
4658

4759
impl<'a, 'cfg> Context<'a, 'cfg> {
@@ -76,6 +88,8 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
7688
unit_dependencies: HashMap::new(),
7789
files: None,
7890
package_cache: HashMap::new(),
91+
order_only_dependencies: HashMap::new(),
92+
pipelined_units: HashSet::new(),
7993
})
8094
}
8195

@@ -266,6 +280,8 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
266280
self.bcx,
267281
&mut self.unit_dependencies,
268282
&mut self.package_cache,
283+
&mut self.order_only_dependencies,
284+
&mut self.pipelined_units,
269285
)?;
270286
let files = CompilationFiles::new(
271287
units,
@@ -453,6 +469,20 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
453469
}
454470
Ok(())
455471
}
472+
473+
/// Returns the `BuildRmeta` unit which is actually doing compilation if the
474+
/// `unit` specified has been pipelined.
475+
///
476+
/// If the unit specified has not been pipelined then `None` will be
477+
/// returned.
478+
pub fn rmeta_unit_if_pipelined(&self, unit: &Unit<'a>) -> Option<Unit<'a>> {
479+
if self.pipelined_units.contains(unit) {
480+
assert_eq!(unit.mode, CompileMode::Build);
481+
Some(unit.with_mode(CompileMode::BuildRmeta, self.bcx.units))
482+
} else {
483+
None
484+
}
485+
}
456486
}
457487

458488
#[derive(Default)]

0 commit comments

Comments
 (0)