| // Copyright 2017 Google Inc. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package cc |
| |
| import ( |
| "github.com/google/blueprint/proptools" |
| |
| "android/soong/android" |
| ) |
| |
| // LTO (link-time optimization) allows the compiler to optimize and generate |
| // code for the entire module at link time, rather than per-compilation |
| // unit. LTO is required for Clang CFI and other whole-program optimization |
| // techniques. LTO also allows cross-compilation unit optimizations that should |
| // result in faster and smaller code, at the expense of additional compilation |
| // time. |
| // |
| // To properly build a module with LTO, the module and all recursive static |
| // dependencies should be compiled with -flto which directs the compiler to emit |
| // bitcode rather than native object files. These bitcode files are then passed |
| // by the linker to the LLVM plugin for compilation at link time. Static |
| // dependencies not built as bitcode will still function correctly but cannot be |
| // optimized at link time and may not be compatible with features that require |
| // LTO, such as CFI. |
| // |
| // This file adds support to soong to automatically propogate LTO options to a |
| // new variant of all static dependencies for each module with LTO enabled. |
| |
| type LTOProperties struct { |
| // Lto must violate capitialization style for acronyms so that it can be |
| // referred to in blueprint files as "lto" |
| Lto struct { |
| Never *bool `android:"arch_variant"` |
| Full *bool `android:"arch_variant"` |
| Thin *bool `android:"arch_variant"` |
| } `android:"arch_variant"` |
| |
| // Dep properties indicate that this module needs to be built with LTO |
| // since it is an object dependency of an LTO module. |
| FullDep bool `blueprint:"mutated"` |
| ThinDep bool `blueprint:"mutated"` |
| NoLtoDep bool `blueprint:"mutated"` |
| |
| // Use clang lld instead of gnu ld. |
| Use_clang_lld *bool |
| |
| // Use -fwhole-program-vtables cflag. |
| Whole_program_vtables *bool |
| } |
| |
| type lto struct { |
| Properties LTOProperties |
| } |
| |
| func (lto *lto) props() []interface{} { |
| return []interface{}{<o.Properties} |
| } |
| |
| func (lto *lto) begin(ctx BaseModuleContext) { |
| if ctx.Config().IsEnvTrue("DISABLE_LTO") { |
| lto.Properties.Lto.Never = proptools.BoolPtr(true) |
| } |
| } |
| |
| func (lto *lto) useClangLld(ctx BaseModuleContext) bool { |
| if lto.Properties.Use_clang_lld != nil { |
| return Bool(lto.Properties.Use_clang_lld) |
| } |
| return true |
| } |
| |
| func (lto *lto) flags(ctx BaseModuleContext, flags Flags) Flags { |
| // TODO(b/131771163): Disable LTO when using explicit fuzzing configurations. |
| // LTO breaks fuzzer builds. |
| if inList("-fsanitize=fuzzer-no-link", flags.Local.CFlags) { |
| return flags |
| } |
| |
| if lto.LTO(ctx) { |
| var ltoCFlag string |
| var ltoLdFlag string |
| if lto.ThinLTO() { |
| ltoCFlag = "-flto=thin -fsplit-lto-unit" |
| } else if lto.FullLTO() { |
| ltoCFlag = "-flto" |
| } else { |
| ltoCFlag = "-flto=thin -fsplit-lto-unit" |
| ltoLdFlag = "-Wl,--lto-O0" |
| } |
| |
| flags.Local.CFlags = append(flags.Local.CFlags, ltoCFlag) |
| flags.Local.LdFlags = append(flags.Local.LdFlags, ltoCFlag) |
| flags.Local.LdFlags = append(flags.Local.LdFlags, ltoLdFlag) |
| |
| if Bool(lto.Properties.Whole_program_vtables) { |
| flags.Local.CFlags = append(flags.Local.CFlags, "-fwhole-program-vtables") |
| } |
| |
| if (lto.DefaultThinLTO(ctx) || lto.ThinLTO()) && ctx.Config().IsEnvTrue("USE_THINLTO_CACHE") && lto.useClangLld(ctx) { |
| // Set appropriate ThinLTO cache policy |
| cacheDirFormat := "-Wl,--thinlto-cache-dir=" |
| cacheDir := android.PathForOutput(ctx, "thinlto-cache").String() |
| flags.Local.LdFlags = append(flags.Local.LdFlags, cacheDirFormat+cacheDir) |
| |
| // Limit the size of the ThinLTO cache to the lesser of 10% of available |
| // disk space and 10GB. |
| cachePolicyFormat := "-Wl,--thinlto-cache-policy=" |
| policy := "cache_size=10%:cache_size_bytes=10g" |
| flags.Local.LdFlags = append(flags.Local.LdFlags, cachePolicyFormat+policy) |
| } |
| |
| // If the module does not have a profile, be conservative and limit cross TU inline |
| // limit to 5 LLVM IR instructions, to balance binary size increase and performance. |
| if !ctx.isPgoCompile() && !ctx.isAfdoCompile() { |
| flags.Local.LdFlags = append(flags.Local.LdFlags, |
| "-Wl,-plugin-opt,-import-instr-limit=5") |
| } |
| } |
| return flags |
| } |
| |
| func (lto *lto) LTO(ctx BaseModuleContext) bool { |
| return lto.ThinLTO() || lto.FullLTO() || lto.DefaultThinLTO(ctx) |
| } |
| |
| func (lto *lto) DefaultThinLTO(ctx BaseModuleContext) bool { |
| host := ctx.Host() |
| vndk := ctx.isVndk() // b/169217596 |
| return GlobalThinLTO(ctx) && !lto.Never() && !host && !vndk |
| } |
| |
| func (lto *lto) FullLTO() bool { |
| return lto != nil && Bool(lto.Properties.Lto.Full) |
| } |
| |
| func (lto *lto) ThinLTO() bool { |
| return lto != nil && Bool(lto.Properties.Lto.Thin) |
| } |
| |
| func (lto *lto) Never() bool { |
| return lto != nil && Bool(lto.Properties.Lto.Never) |
| } |
| |
| func GlobalThinLTO(ctx android.BaseModuleContext) bool { |
| return ctx.Config().IsEnvTrue("GLOBAL_THINLTO") |
| } |
| |
| // Propagate lto requirements down from binaries |
| func ltoDepsMutator(mctx android.TopDownMutatorContext) { |
| globalThinLTO := GlobalThinLTO(mctx) |
| |
| if m, ok := mctx.Module().(*Module); ok { |
| full := m.lto.FullLTO() |
| thin := m.lto.ThinLTO() |
| never := m.lto.Never() |
| if full && thin { |
| mctx.PropertyErrorf("LTO", "FullLTO and ThinLTO are mutually exclusive") |
| } |
| |
| mctx.WalkDeps(func(dep android.Module, parent android.Module) bool { |
| tag := mctx.OtherModuleDependencyTag(dep) |
| libTag, isLibTag := tag.(libraryDependencyTag) |
| |
| // Do not recurse down non-static dependencies |
| if isLibTag { |
| if !libTag.static() { |
| return false |
| } |
| } else { |
| if tag != objDepTag && tag != reuseObjTag { |
| return false |
| } |
| } |
| |
| if dep, ok := dep.(*Module); ok { |
| if full && !dep.lto.FullLTO() { |
| dep.lto.Properties.FullDep = true |
| } |
| if !globalThinLTO && thin && !dep.lto.ThinLTO() { |
| dep.lto.Properties.ThinDep = true |
| } |
| if globalThinLTO && never && !dep.lto.Never() { |
| dep.lto.Properties.NoLtoDep = true |
| } |
| } |
| |
| // Recursively walk static dependencies |
| return true |
| }) |
| } |
| } |
| |
| // Create lto variants for modules that need them |
| func ltoMutator(mctx android.BottomUpMutatorContext) { |
| globalThinLTO := GlobalThinLTO(mctx) |
| |
| if m, ok := mctx.Module().(*Module); ok && m.lto != nil { |
| // Create variations for LTO types required as static |
| // dependencies |
| variationNames := []string{""} |
| if m.lto.Properties.FullDep && !m.lto.FullLTO() { |
| variationNames = append(variationNames, "lto-full") |
| } |
| if !globalThinLTO && m.lto.Properties.ThinDep && !m.lto.ThinLTO() { |
| variationNames = append(variationNames, "lto-thin") |
| } |
| if globalThinLTO && m.lto.Properties.NoLtoDep && !m.lto.Never() { |
| variationNames = append(variationNames, "lto-none") |
| } |
| |
| // Use correct dependencies if LTO property is explicitly set |
| // (mutually exclusive) |
| if m.lto.FullLTO() { |
| mctx.SetDependencyVariation("lto-full") |
| } |
| if !globalThinLTO && m.lto.ThinLTO() { |
| mctx.SetDependencyVariation("lto-thin") |
| } |
| // Never must be the last, it overrides Thin or Full. |
| if globalThinLTO && m.lto.Never() { |
| mctx.SetDependencyVariation("lto-none") |
| } |
| |
| if len(variationNames) > 1 { |
| modules := mctx.CreateVariations(variationNames...) |
| for i, name := range variationNames { |
| variation := modules[i].(*Module) |
| // Default module which will be |
| // installed. Variation set above according to |
| // explicit LTO properties |
| if name == "" { |
| continue |
| } |
| |
| // LTO properties for dependencies |
| if name == "lto-full" { |
| variation.lto.Properties.Lto.Full = proptools.BoolPtr(true) |
| } |
| if name == "lto-thin" { |
| variation.lto.Properties.Lto.Thin = proptools.BoolPtr(true) |
| } |
| if name == "lto-none" { |
| variation.lto.Properties.Lto.Never = proptools.BoolPtr(true) |
| } |
| variation.Properties.PreventInstall = true |
| variation.Properties.HideFromMake = true |
| variation.lto.Properties.FullDep = false |
| variation.lto.Properties.ThinDep = false |
| variation.lto.Properties.NoLtoDep = false |
| } |
| } |
| } |
| } |