blob: b4856240e76ec02ab9cfcce5389d76bb58d9752e [file] [edit]
use crate::compiler::{BuildContext, CompileMode, CrateType, Unit};
use crate::util::interning::InternedString;
use crate::workspace::profiles;
use std::collections::hash_map::Entry;
use crate::util::data_structures::HashMap;
use crate::util::errors::CargoResult;
/// Possible ways to run rustc and request various parts of [LTO].
///
/// Variant | Flag | Object Code | Bitcode
/// -------------------|------------------------|-------------|--------
/// `Run` | `-C lto=foo` | n/a | n/a
/// `Off` | `-C lto=off` | n/a | n/a
/// `OnlyBitcode` | `-C linker-plugin-lto` | | ✓
/// `ObjectAndBitcode` | | ✓ | ✓
/// `OnlyObject` | `-C embed-bitcode=no` | ✓ |
///
/// [LTO]: https://doc.rust-lang.org/nightly/cargo/reference/profiles.html#lto
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Lto {
/// LTO is run for this rustc, and it's `-Clto=foo`. If the given value is
/// None, that corresponds to `-Clto` with no argument, which means do
/// "fat" LTO.
Run(Option<InternedString>),
/// LTO has been explicitly listed as "off". This means no thin-local-LTO,
/// no LTO anywhere, I really mean it!
Off,
/// This rustc invocation only needs to produce bitcode (it is *only* used
/// for LTO), there's no need to produce object files, so we can pass
/// `-Clinker-plugin-lto`
OnlyBitcode,
/// This rustc invocation needs to embed bitcode in object files. This means
/// that object files may be used for a normal link, and the crate may be
/// loaded for LTO later, so both are required.
ObjectAndBitcode,
/// This should not include bitcode. This is primarily to reduce disk
/// space usage.
OnlyObject,
}
pub fn generate(bcx: &BuildContext<'_, '_>) -> CargoResult<HashMap<Unit, Lto>> {
let mut map = HashMap::default();
for unit in bcx.roots.iter() {
let root_lto = match unit.profile.lto {
// LTO not requested, no need for bitcode.
profiles::Lto::Bool(false) => Lto::OnlyObject,
profiles::Lto::Off => Lto::Off,
_ => {
let crate_types = unit.target.rustc_crate_types();
if unit.target.for_host() {
Lto::OnlyObject
} else if needs_object(&crate_types) {
lto_when_needs_object(&crate_types)
} else {
// This may or may not participate in LTO, let's start
// with the minimum requirements. This may be expanded in
// `calculate` below if necessary.
Lto::OnlyBitcode
}
}
};
calculate(bcx, &mut map, unit, root_lto)?;
}
Ok(map)
}
/// Whether or not any of these crate types need object code.
fn needs_object(crate_types: &[CrateType]) -> bool {
crate_types.iter().any(|k| k.can_lto() || k.is_dynamic())
}
/// Lto setting to use when this unit needs object code.
fn lto_when_needs_object(crate_types: &[CrateType]) -> Lto {
if crate_types.iter().all(|ct| *ct == CrateType::Dylib) {
// A dylib whose parent is running LTO. rustc currently
// doesn't support LTO with dylibs, so bitcode is not
// needed.
Lto::OnlyObject
} else {
// Mixed rlib with a dylib or cdylib whose parent is running LTO. This
// needs both: bitcode for the rlib (for LTO) and object code for the
// dylib.
Lto::ObjectAndBitcode
}
}
fn calculate(
bcx: &BuildContext<'_, '_>,
map: &mut HashMap<Unit, Lto>,
unit: &Unit,
parent_lto: Lto,
) -> CargoResult<()> {
let crate_types = match unit.mode {
// Note: Doctest ignores LTO, but for now we'll compute it as-if it is
// a Bin, in case it is ever supported in the future.
CompileMode::Test | CompileMode::Doctest => vec![CrateType::Bin],
// Notes on other modes:
// - Check: Treat as the underlying type, it doesn't really matter.
// - Doc: LTO is N/A for the Doc unit itself since rustdoc does not
// support codegen flags. We still compute the dependencies, which
// are mostly `Check`.
// - RunCustomBuild is ignored because it is always "for_host".
_ => unit.target.rustc_crate_types(),
};
// LTO can only be performed if *all* of the crate types support it.
// For example, a cdylib/rlib combination won't allow LTO.
let all_lto_types = crate_types.iter().all(CrateType::can_lto);
// Compute the LTO based on the profile, and what our parent requires.
let lto = if unit.target.for_host() {
// Disable LTO for host builds since we only really want to perform LTO
// for the final binary, and LTO on plugins/build scripts/proc macros is
// largely not desired.
Lto::OnlyObject
} else if all_lto_types {
// Note that this ignores the `parent_lto` because this isn't a
// linkable crate type; this unit is not being embedded in the parent.
match unit.profile.lto {
profiles::Lto::Named(s) => Lto::Run(Some(s)),
profiles::Lto::Off => Lto::Off,
profiles::Lto::Bool(true) => Lto::Run(None),
profiles::Lto::Bool(false) => Lto::OnlyObject,
}
} else {
match (parent_lto, needs_object(&crate_types)) {
// An rlib whose parent is running LTO, we only need bitcode.
(Lto::Run(_), false) => Lto::OnlyBitcode,
// LTO when something needs object code.
(Lto::Run(_), true) | (Lto::OnlyBitcode, true) => lto_when_needs_object(&crate_types),
// LTO is disabled, continue to disable it.
(Lto::Off, _) => Lto::Off,
// If this doesn't have any requirements, or the requirements are
// already satisfied, then stay with our parent.
(_, false) | (Lto::OnlyObject, true) | (Lto::ObjectAndBitcode, true) => parent_lto,
}
};
// Merge the computed LTO. If this unit appears multiple times in the
// graph, the merge may expand the requirements.
let merged_lto = match map.entry(unit.clone()) {
// If we haven't seen this unit before then insert our value and keep
// going.
Entry::Vacant(v) => *v.insert(lto),
Entry::Occupied(mut v) => {
let result = match (lto, v.get()) {
// No change in requirements.
(Lto::OnlyBitcode, Lto::OnlyBitcode) => Lto::OnlyBitcode,
(Lto::OnlyObject, Lto::OnlyObject) => Lto::OnlyObject,
// Once we're running LTO we keep running LTO. We should always
// calculate the same thing here each iteration because if we
// see this twice then it means, for example, two unit tests
// depend on a binary, which is normal.
(Lto::Run(s), _) | (_, &Lto::Run(s)) => Lto::Run(s),
// Off means off! This has the same reasoning as `Lto::Run`.
(Lto::Off, _) | (_, Lto::Off) => Lto::Off,
// Once a target has requested both, that's the maximal amount
// of work that can be done, so we just keep doing that work.
(Lto::ObjectAndBitcode, _) | (_, Lto::ObjectAndBitcode) => Lto::ObjectAndBitcode,
// Upgrade so that both requirements can be met.
//
// This is where the trickiness happens. This unit needs
// bitcode and the previously calculated value for this unit
// says it didn't need bitcode (or vice versa). This means that
// we're a shared dependency between some targets which require
// LTO and some which don't. This means that instead of being
// either only-objects or only-bitcode we have to embed both in
// rlibs (used for different compilations), so we switch to
// including both.
(Lto::OnlyObject, Lto::OnlyBitcode) | (Lto::OnlyBitcode, Lto::OnlyObject) => {
Lto::ObjectAndBitcode
}
};
// No need to recurse if we calculated the same value as before.
if result == *v.get() {
return Ok(());
}
v.insert(result);
result
}
};
for dep in &bcx.unit_graph[unit] {
calculate(bcx, map, &dep.unit, merged_lto)?;
}
Ok(())
}