cargo/core/compiler/lto.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
use crate::core::compiler::{BuildContext, CompileMode, CrateType, Unit};
use crate::core::profiles;
use crate::util::interning::InternedString;
use crate::util::errors::CargoResult;
use std::collections::hash_map::{Entry, HashMap};
/// 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::new();
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::Bench | 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(())
}