cargo/compiler/lto.rs
1use cargo_util_schemas::core::CrateType;
2
3use crate::compiler::CrateTypeExt as _;
4use crate::compiler::{BuildContext, CompileMode, Unit};
5use crate::util::interning::InternedString;
6use crate::workspace::profiles;
7use std::collections::hash_map::Entry;
8
9use crate::util::errors::CargoResult;
10use cargo_util::collections::HashMap;
11
12/// Possible ways to run rustc and request various parts of [LTO].
13///
14/// Variant | Flag | Object Code | Bitcode
15/// -------------------|------------------------|-------------|--------
16/// `Run` | `-C lto=foo` | n/a | n/a
17/// `Off` | `-C lto=off` | n/a | n/a
18/// `OnlyBitcode` | `-C linker-plugin-lto` | | ✓
19/// `ObjectAndBitcode` | | ✓ | ✓
20/// `OnlyObject` | `-C embed-bitcode=no` | ✓ |
21///
22/// [LTO]: https://doc.rust-lang.org/nightly/cargo/reference/profiles.html#lto
23#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
24pub enum Lto {
25 /// LTO is run for this rustc, and it's `-Clto=foo`. If the given value is
26 /// None, that corresponds to `-Clto` with no argument, which means do
27 /// "fat" LTO.
28 Run(Option<InternedString>),
29
30 /// LTO has been explicitly listed as "off". This means no thin-local-LTO,
31 /// no LTO anywhere, I really mean it!
32 Off,
33
34 /// This rustc invocation only needs to produce bitcode (it is *only* used
35 /// for LTO), there's no need to produce object files, so we can pass
36 /// `-Clinker-plugin-lto`
37 OnlyBitcode,
38
39 /// This rustc invocation needs to embed bitcode in object files. This means
40 /// that object files may be used for a normal link, and the crate may be
41 /// loaded for LTO later, so both are required.
42 ObjectAndBitcode,
43
44 /// This should not include bitcode. This is primarily to reduce disk
45 /// space usage.
46 OnlyObject,
47}
48
49pub fn generate(bcx: &BuildContext<'_, '_>) -> CargoResult<HashMap<Unit, Lto>> {
50 let mut map = HashMap::default();
51 for unit in bcx.roots.iter() {
52 let root_lto = match unit.profile.lto {
53 // LTO not requested, no need for bitcode.
54 profiles::Lto::Bool(false) => Lto::OnlyObject,
55 profiles::Lto::Off => Lto::Off,
56 _ => {
57 let crate_types = unit.target.rustc_crate_types();
58 if unit.target.for_host() {
59 Lto::OnlyObject
60 } else if needs_object(&crate_types) {
61 lto_when_needs_object(&crate_types)
62 } else {
63 // This may or may not participate in LTO, let's start
64 // with the minimum requirements. This may be expanded in
65 // `calculate` below if necessary.
66 Lto::OnlyBitcode
67 }
68 }
69 };
70 calculate(bcx, &mut map, unit, root_lto)?;
71 }
72 Ok(map)
73}
74
75/// Whether or not any of these crate types need object code.
76fn needs_object(crate_types: &[CrateType]) -> bool {
77 crate_types.iter().any(|k| k.can_lto() || k.is_dynamic())
78}
79
80/// Lto setting to use when this unit needs object code.
81fn lto_when_needs_object(crate_types: &[CrateType]) -> Lto {
82 if crate_types.iter().all(|ct| *ct == CrateType::Dylib) {
83 // A dylib whose parent is running LTO. rustc currently
84 // doesn't support LTO with dylibs, so bitcode is not
85 // needed.
86 Lto::OnlyObject
87 } else {
88 // Mixed rlib with a dylib or cdylib whose parent is running LTO. This
89 // needs both: bitcode for the rlib (for LTO) and object code for the
90 // dylib.
91 Lto::ObjectAndBitcode
92 }
93}
94
95fn calculate(
96 bcx: &BuildContext<'_, '_>,
97 map: &mut HashMap<Unit, Lto>,
98 unit: &Unit,
99 parent_lto: Lto,
100) -> CargoResult<()> {
101 let crate_types = match unit.mode {
102 // Note: Doctest ignores LTO, but for now we'll compute it as-if it is
103 // a Bin, in case it is ever supported in the future.
104 CompileMode::Test | CompileMode::Doctest => vec![CrateType::Bin],
105 // Notes on other modes:
106 // - Check: Treat as the underlying type, it doesn't really matter.
107 // - Doc: LTO is N/A for the Doc unit itself since rustdoc does not
108 // support codegen flags. We still compute the dependencies, which
109 // are mostly `Check`.
110 // - RunCustomBuild is ignored because it is always "for_host".
111 _ => unit.target.rustc_crate_types(),
112 };
113 // LTO can only be performed if *all* of the crate types support it.
114 // For example, a cdylib/rlib combination won't allow LTO.
115 let all_lto_types = crate_types.iter().all(CrateType::can_lto);
116 // Compute the LTO based on the profile, and what our parent requires.
117 let lto = if unit.target.for_host() {
118 // Disable LTO for host builds since we only really want to perform LTO
119 // for the final binary, and LTO on plugins/build scripts/proc macros is
120 // largely not desired.
121 Lto::OnlyObject
122 } else if all_lto_types {
123 // Note that this ignores the `parent_lto` because this isn't a
124 // linkable crate type; this unit is not being embedded in the parent.
125 match unit.profile.lto {
126 profiles::Lto::Named(s) => Lto::Run(Some(s)),
127 profiles::Lto::Off => Lto::Off,
128 profiles::Lto::Bool(true) => Lto::Run(None),
129 profiles::Lto::Bool(false) => Lto::OnlyObject,
130 }
131 } else {
132 match (parent_lto, needs_object(&crate_types)) {
133 // An rlib whose parent is running LTO, we only need bitcode.
134 (Lto::Run(_), false) => Lto::OnlyBitcode,
135 // LTO when something needs object code.
136 (Lto::Run(_), true) | (Lto::OnlyBitcode, true) => lto_when_needs_object(&crate_types),
137 // LTO is disabled, continue to disable it.
138 (Lto::Off, _) => Lto::Off,
139 // If this doesn't have any requirements, or the requirements are
140 // already satisfied, then stay with our parent.
141 (_, false) | (Lto::OnlyObject, true) | (Lto::ObjectAndBitcode, true) => parent_lto,
142 }
143 };
144
145 // Merge the computed LTO. If this unit appears multiple times in the
146 // graph, the merge may expand the requirements.
147 let merged_lto = match map.entry(unit.clone()) {
148 // If we haven't seen this unit before then insert our value and keep
149 // going.
150 Entry::Vacant(v) => *v.insert(lto),
151
152 Entry::Occupied(mut v) => {
153 let result = match (lto, v.get()) {
154 // No change in requirements.
155 (Lto::OnlyBitcode, Lto::OnlyBitcode) => Lto::OnlyBitcode,
156 (Lto::OnlyObject, Lto::OnlyObject) => Lto::OnlyObject,
157
158 // Once we're running LTO we keep running LTO. We should always
159 // calculate the same thing here each iteration because if we
160 // see this twice then it means, for example, two unit tests
161 // depend on a binary, which is normal.
162 (Lto::Run(s), _) | (_, &Lto::Run(s)) => Lto::Run(s),
163
164 // Off means off! This has the same reasoning as `Lto::Run`.
165 (Lto::Off, _) | (_, Lto::Off) => Lto::Off,
166
167 // Once a target has requested both, that's the maximal amount
168 // of work that can be done, so we just keep doing that work.
169 (Lto::ObjectAndBitcode, _) | (_, Lto::ObjectAndBitcode) => Lto::ObjectAndBitcode,
170
171 // Upgrade so that both requirements can be met.
172 //
173 // This is where the trickiness happens. This unit needs
174 // bitcode and the previously calculated value for this unit
175 // says it didn't need bitcode (or vice versa). This means that
176 // we're a shared dependency between some targets which require
177 // LTO and some which don't. This means that instead of being
178 // either only-objects or only-bitcode we have to embed both in
179 // rlibs (used for different compilations), so we switch to
180 // including both.
181 (Lto::OnlyObject, Lto::OnlyBitcode) | (Lto::OnlyBitcode, Lto::OnlyObject) => {
182 Lto::ObjectAndBitcode
183 }
184 };
185 // No need to recurse if we calculated the same value as before.
186 if result == *v.get() {
187 return Ok(());
188 }
189 v.insert(result);
190 result
191 }
192 };
193
194 for dep in &bcx.unit_graph[unit] {
195 calculate(bcx, map, &dep.unit, merged_lto)?;
196 }
197 Ok(())
198}