#
cargo/core/compiler/
lto.rs

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