// 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 ( "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"` // Use clang lld instead of gnu ld. Use_clang_lld *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 = boolPtr(true) } } func (lto *lto) deps(ctx BaseModuleContext, deps Deps) Deps { return deps } 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() { var ltoFlag string if Bool(lto.Properties.Lto.Thin) { ltoFlag = "-flto=thin -fsplit-lto-unit" } else { ltoFlag = "-flto" } flags.Local.CFlags = append(flags.Local.CFlags, ltoFlag) flags.Local.LdFlags = append(flags.Local.LdFlags, ltoFlag) if ctx.Config().IsEnvTrue("USE_THINLTO_CACHE") && Bool(lto.Properties.Lto.Thin) && 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 do not inline // or unroll loops during LTO, in order to prevent significant size bloat. if !ctx.isPgoCompile() { flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,-plugin-opt,-inline-threshold=0", "-Wl,-plugin-opt,-unroll-threshold=0") } } return flags } // Can be called with a null receiver func (lto *lto) LTO() bool { if lto == nil || lto.Disabled() { return false } full := Bool(lto.Properties.Lto.Full) thin := Bool(lto.Properties.Lto.Thin) return full || thin } // Is lto.never explicitly set to true? func (lto *lto) Disabled() bool { return lto.Properties.Lto.Never != nil && *lto.Properties.Lto.Never } // Propagate lto requirements down from binaries func ltoDepsMutator(mctx android.TopDownMutatorContext) { if m, ok := mctx.Module().(*Module); ok && m.lto.LTO() { full := Bool(m.lto.Properties.Lto.Full) thin := Bool(m.lto.Properties.Lto.Thin) 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) switch tag { case StaticDepTag, staticExportDepTag, lateStaticDepTag, wholeStaticDepTag, objDepTag, reuseObjTag: if dep, ok := dep.(*Module); ok && dep.lto != nil && !dep.lto.Disabled() { if full && !Bool(dep.lto.Properties.Lto.Full) { dep.lto.Properties.FullDep = true } if thin && !Bool(dep.lto.Properties.Lto.Thin) { dep.lto.Properties.ThinDep = true } } // Recursively walk static dependencies return true } // Do not recurse down non-static dependencies return false }) } } // Create lto variants for modules that need them func ltoMutator(mctx android.BottomUpMutatorContext) { 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 && !Bool(m.lto.Properties.Lto.Full) { variationNames = append(variationNames, "lto-full") } if m.lto.Properties.ThinDep && !Bool(m.lto.Properties.Lto.Thin) { variationNames = append(variationNames, "lto-thin") } // Use correct dependencies if LTO property is explicitly set // (mutually exclusive) if Bool(m.lto.Properties.Lto.Full) { mctx.SetDependencyVariation("lto-full") } if Bool(m.lto.Properties.Lto.Thin) { mctx.SetDependencyVariation("lto-thin") } 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 = boolPtr(true) variation.lto.Properties.Lto.Thin = boolPtr(false) } if name == "lto-thin" { variation.lto.Properties.Lto.Full = boolPtr(false) variation.lto.Properties.Lto.Thin = boolPtr(true) } variation.Properties.PreventInstall = true variation.Properties.HideFromMake = true variation.lto.Properties.FullDep = false variation.lto.Properties.ThinDep = false } } } }