platform_build_soong/cc/lto.go
Yi Kong b8eaee68b6 Add option to emit ThinLTO indexes and imports for MLGO training
Test: presubmit
Bug: 293827654
Change-Id: I8de1287543194d8e47ff5b57d4cabc7416048bbf
2023-10-31 21:59:45 +09:00

254 lines
8.1 KiB
Go

// 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"
"github.com/google/blueprint/proptools"
)
// 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 propagate LTO options to a
// new variant of all static dependencies for each module with LTO enabled.
type LTOProperties struct {
// Lto must violate capitalization style for acronyms so that it can be
// referred to in blueprint files as "lto"
Lto struct {
Never *bool `android:"arch_variant"`
Thin *bool `android:"arch_variant"`
} `android:"arch_variant"`
LtoEnabled bool `blueprint:"mutated"`
LtoDefault bool `blueprint:"mutated"`
// Dep properties indicate that this module needs to be built with LTO
// since it is an object dependency of an LTO module.
LtoDep bool `blueprint:"mutated"`
NoLtoDep bool `blueprint:"mutated"`
// Use -fwhole-program-vtables cflag.
Whole_program_vtables *bool
}
type lto struct {
Properties LTOProperties
}
func (lto *lto) props() []interface{} {
return []interface{}{&lto.Properties}
}
func (lto *lto) begin(ctx BaseModuleContext) {
// First, determine the module independent default LTO mode.
ltoDefault := true
if ctx.Config().IsEnvTrue("DISABLE_LTO") {
ltoDefault = false
} else if lto.Never() {
ltoDefault = false
} else if ctx.Host() {
// Performance and binary size are less important for host binaries.
ltoDefault = false
} else if ctx.Arch().ArchType.Multilib == "lib32" {
// LP32 has many subtle issues and less test coverage.
ltoDefault = false
}
// Then, determine the actual LTO mode to use. If different from `ltoDefault`, a variant needs
// to be created.
ltoEnabled := ltoDefault
if lto.Never() {
ltoEnabled = false
} else if lto.ThinLTO() {
// Module explicitly requests for LTO.
ltoEnabled = true
} else if ctx.testBinary() || ctx.testLibrary() {
// Do not enable LTO for tests for better debugging.
ltoEnabled = false
} else if ctx.isVndk() {
// FIXME: ThinLTO for VNDK produces different output.
// b/169217596
ltoEnabled = false
}
lto.Properties.LtoDefault = ltoDefault
lto.Properties.LtoEnabled = ltoEnabled
}
func (lto *lto) flags(ctx BaseModuleContext, flags Flags) Flags {
// TODO(b/131771163): CFI and Fuzzer controls LTO flags by themselves.
// This has be checked late because these properties can be mutated.
if ctx.isCfi() || ctx.isFuzzer() {
return flags
}
if lto.Properties.LtoEnabled {
ltoCFlags := []string{"-flto=thin", "-fsplit-lto-unit"}
var ltoLdFlags []string
// The module did not explicitly turn on LTO. Only leverage LTO's
// better dead code elimination and CFG simplification, but do
// not perform costly optimizations for a balance between compile
// time, binary size and performance.
if !lto.ThinLTO() {
ltoLdFlags = append(ltoLdFlags, "-Wl,--lto-O0")
}
if Bool(lto.Properties.Whole_program_vtables) {
ltoCFlags = append(ltoCFlags, "-fwhole-program-vtables")
}
if ctx.Config().IsEnvTrue("USE_THINLTO_CACHE") {
// Set appropriate ThinLTO cache policy
cacheDirFormat := "-Wl,--thinlto-cache-dir="
cacheDir := android.PathForOutput(ctx, "thinlto-cache").String()
ltoLdFlags = append(ltoLdFlags, 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"
ltoLdFlags = append(ltoLdFlags, cachePolicyFormat+policy)
}
// Reduce the inlining threshold for a better balance of binary size and
// performance.
if !ctx.Darwin() {
if ctx.isPgoCompile() || ctx.isAfdoCompile() {
ltoLdFlags = append(ltoLdFlags, "-Wl,-plugin-opt,-import-instr-limit=40")
} else {
ltoLdFlags = append(ltoLdFlags, "-Wl,-plugin-opt,-import-instr-limit=5")
}
}
// For ML training
if ctx.Config().IsEnvTrue("THINLTO_EMIT_INDEXES_AND_IMPORTS") {
ltoLdFlags = append(ltoLdFlags, "-Wl,--save-temps=import")
ltoLdFlags = append(ltoLdFlags, "-Wl,--thinlto-emit-index-files")
}
flags.Local.CFlags = append(flags.Local.CFlags, ltoCFlags...)
flags.Local.AsFlags = append(flags.Local.AsFlags, ltoCFlags...)
flags.Local.LdFlags = append(flags.Local.LdFlags, ltoCFlags...)
flags.Local.LdFlags = append(flags.Local.LdFlags, ltoLdFlags...)
}
return flags
}
func (lto *lto) ThinLTO() bool {
return lto != nil && proptools.Bool(lto.Properties.Lto.Thin)
}
func (lto *lto) Never() bool {
return lto != nil && proptools.Bool(lto.Properties.Lto.Never)
}
// Propagate lto requirements down from binaries
func ltoDepsMutator(mctx android.TopDownMutatorContext) {
if m, ok := mctx.Module().(*Module); ok {
if m.lto == nil || m.lto.Properties.LtoEnabled == m.lto.Properties.LtoDefault {
return
}
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 m.lto.Properties.LtoEnabled {
dep.lto.Properties.LtoDep = true
} else {
dep.lto.Properties.NoLtoDep = true
}
}
// Recursively walk static dependencies
return true
})
}
}
// 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.LtoDep {
variationNames = append(variationNames, "lto-thin")
}
if m.lto.Properties.NoLtoDep {
variationNames = append(variationNames, "lto-none")
}
if !m.lto.Properties.LtoEnabled {
mctx.SetDependencyVariation("lto-none")
}
if m.lto.Properties.LtoEnabled {
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-thin" {
variation.lto.Properties.LtoEnabled = true
}
if name == "lto-none" {
variation.lto.Properties.LtoEnabled = false
}
variation.Properties.PreventInstall = true
variation.Properties.HideFromMake = true
variation.lto.Properties.LtoDefault = m.lto.Properties.LtoDefault
variation.lto.Properties.LtoDep = false
variation.lto.Properties.NoLtoDep = false
}
}
}
}