Add PGO support to soong

Bug: http://b/63768402
Bug: http://b/65598278

Add support for the 'pgo' property to specify how a module is processed
under PGO.  A sample property is below:

pgo: {
    instrumentation: true, // could be "sampling: true" when supported
    profile_file: "pgo_simple.profdata",
    benchmarks: ["pgo_simple"],
}

1. Runtime profiles can be gathered using "sampling" or
"instrumentation".  Sampling is not supported initially.

2. If 'toolchain/pgo-profiles' project is found,
'toolchain/pgo-profiles/${profile_file}' is passed to the compiler and
linker when building this module.

3. If ANDROID_PGO_INSTRUMENT environment variable is set, and includes a
benchmark in the 'benchmarks' list, appropriate flags (for e.g.
-fprofile-generate for instrumentation) are passed to the compiler and
linker when building this module.

Test: Add example modules that specify the pgo property and verify
appropriate flags and dependencies in the Ninja file.  Some
tests/examples are in https://android-review.googlesource.com/474805

Change-Id: I6242e0c904497a115e367dea6927ba1c4b906355
This commit is contained in:
Pirama Arumuga Nainar 2017-08-31 23:38:27 -07:00
parent d685385c6b
commit ada83ec0a6
5 changed files with 207 additions and 2 deletions

View File

@ -119,6 +119,7 @@ bootstrap_go_package {
"cc/gen.go",
"cc/lto.go",
"cc/makevars.go",
"cc/pgo.go",
"cc/prebuilt.go",
"cc/proto.go",
"cc/relocation_packer.go",

View File

@ -325,6 +325,7 @@ func (binary *binaryDecorator) link(ctx ModuleContext,
linkerDeps = append(linkerDeps, deps.SharedLibsDeps...)
linkerDeps = append(linkerDeps, deps.LateSharedLibsDeps...)
linkerDeps = append(linkerDeps, objs.tidyFiles...)
linkerDeps = append(linkerDeps, flags.LdFlagsDeps...)
TransformObjToDynamicBinary(ctx, objs.objFiles, sharedLibs, deps.StaticLibs,
deps.LateStaticLibs, deps.WholeStaticLibs, linkerDeps, deps.CrtBegin, deps.CrtEnd, true,

View File

@ -134,7 +134,8 @@ type Flags struct {
RequiredInstructionSet string
DynamicLinker string
CFlagsDeps android.Paths // Files depended on by compiler flags
CFlagsDeps android.Paths // Files depended on by compiler flags
LdFlagsDeps android.Paths // Files depended on by linker flags
GroupStaticLibs bool
}
@ -308,6 +309,7 @@ type Module struct {
sabi *sabi
vndkdep *vndkdep
lto *lto
pgo *pgo
androidMkSharedLibDeps []string
@ -350,6 +352,9 @@ func (c *Module) Init() android.Module {
if c.lto != nil {
c.AddProperties(c.lto.props()...)
}
if c.pgo != nil {
c.AddProperties(c.pgo.props()...)
}
for _, feature := range c.features {
c.AddProperties(feature.props()...)
}
@ -506,6 +511,7 @@ func newModule(hod android.HostOrDeviceSupported, multilib android.Multilib) *Mo
module.sabi = &sabi{}
module.vndkdep = &vndkdep{}
module.lto = &lto{}
module.pgo = &pgo{}
return module
}
@ -557,6 +563,9 @@ func (c *Module) GenerateAndroidBuildActions(actx android.ModuleContext) {
if c.lto != nil {
flags = c.lto.flags(ctx, flags)
}
if c.pgo != nil {
flags = c.pgo.flags(ctx, flags)
}
for _, feature := range c.features {
flags = feature.flags(ctx, flags)
}
@ -643,6 +652,9 @@ func (c *Module) begin(ctx BaseModuleContext) {
if c.lto != nil {
c.lto.begin(ctx)
}
if c.pgo != nil {
c.pgo.begin(ctx)
}
for _, feature := range c.features {
feature.begin(ctx)
}
@ -1250,6 +1262,7 @@ func DefaultsFactory(props ...interface{}) android.Module {
&SAbiProperties{},
&VndkProperties{},
&LTOProperties{},
&PgoProperties{},
)
android.InitDefaultsModule(module)

View File

@ -484,6 +484,7 @@ func (library *libraryDecorator) linkShared(ctx ModuleContext,
flags Flags, deps PathDeps, objs Objects) android.Path {
var linkerDeps android.Paths
linkerDeps = append(linkerDeps, flags.LdFlagsDeps...)
versionScript := android.OptionalPathForModuleSrc(ctx, library.Properties.Version_script)
unexportedSymbols := android.OptionalPathForModuleSrc(ctx, library.Properties.Unexported_symbols_list)
@ -628,7 +629,6 @@ func (library *libraryDecorator) link(ctx ModuleContext,
flags Flags, deps PathDeps, objs Objects) android.Path {
objs = objs.Append(deps.Objs)
var out android.Path
if library.static() || library.header() {
out = library.linkStatic(ctx, flags, deps, objs)

190
cc/pgo.go Normal file
View File

@ -0,0 +1,190 @@
// 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 (
"fmt"
"strings"
"android/soong/android"
)
var (
// Add flags to ignore warnings that profiles are old or missing for
// some functions
profileUseOtherFlags = []string{}
)
const pgoProfileProject = "toolchain/pgo-profiles"
const profileInstrumentFlag = "-fprofile-generate=/data/local/tmp"
const profileSamplingFlag = "-gline-tables-only"
const profileUseInstrumentFormat = "-fprofile-use=%s"
const profileUseSamplingFormat = "-fprofile-sample-use=%s"
type PgoProperties struct {
Pgo struct {
Instrumentation *bool
Sampling *bool
Profile_file *string `android:"arch_variant"`
Benchmarks []string
} `android:"arch_variant"`
PgoPresent bool `blueprint:"mutated"`
ShouldProfileModule bool `blueprint:"mutated"`
}
type pgo struct {
Properties PgoProperties
}
func (pgo *pgo) props() []interface{} {
return []interface{}{&pgo.Properties}
}
func (pgo *pgo) profileGatherFlags(ctx ModuleContext) string {
if *pgo.Properties.Pgo.Instrumentation {
return profileInstrumentFlag
}
if *pgo.Properties.Pgo.Sampling {
return profileSamplingFlag
}
return ""
}
func (pgo *pgo) profileUseFlag(ctx ModuleContext, file string) string {
if *pgo.Properties.Pgo.Instrumentation {
return fmt.Sprintf(profileUseInstrumentFormat, file)
}
if *pgo.Properties.Pgo.Sampling {
return fmt.Sprintf(profileUseSamplingFormat, file)
}
return ""
}
func (pgo *pgo) profileUseFlags(ctx ModuleContext, file string) []string {
flags := []string{pgo.profileUseFlag(ctx, file)}
flags = append(flags, profileUseOtherFlags...)
return flags
}
func (props *PgoProperties) isPGO(ctx BaseModuleContext) bool {
isInstrumentation := props.Pgo.Instrumentation != nil
isSampling := props.Pgo.Sampling != nil
profileKindPresent := isInstrumentation || isSampling
filePresent := props.Pgo.Profile_file != nil
benchmarksPresent := len(props.Pgo.Benchmarks) > 0
// If all three properties are absent, PGO is OFF for this module
if !profileKindPresent && !filePresent && !benchmarksPresent {
return false
}
// If at least one property exists, validate that all properties exist
if !profileKindPresent || !filePresent || !benchmarksPresent {
var missing []string
if !profileKindPresent {
missing = append(missing, "profile kind (either \"instrumentation\" or \"sampling\" property)")
}
if !filePresent {
missing = append(missing, "profile_file property")
}
if !benchmarksPresent {
missing = append(missing, "non-empty benchmarks property")
}
missingProps := strings.Join(missing, ", ")
ctx.ModuleErrorf("PGO specification is missing properties: " + missingProps)
}
// Sampling not supported yet
//
// TODO When sampling support is turned on, check that instrumentation and
// sampling are not simultaneously specified
if isSampling {
ctx.PropertyErrorf("pgo.sampling", "\"sampling\" is not supported yet)")
}
return true
}
func getPgoProfilesDir(ctx ModuleContext) android.OptionalPath {
return android.ExistentPathForSource(ctx, "", pgoProfileProject)
}
func (pgo *pgo) begin(ctx BaseModuleContext) {
// TODO Evaluate if we need to support PGO for host modules
if ctx.Host() {
return
}
// Check if PGO is needed for this module
pgo.Properties.PgoPresent = pgo.Properties.isPGO(ctx)
if !pgo.Properties.PgoPresent {
return
}
// This module should be instrumented if ANDROID_PGO_INSTRUMENT is set
// and includes a benchmark listed for this module
//
// TODO Validate that each benchmark instruments at least one module
pgo.Properties.ShouldProfileModule = false
pgoBenchmarks := ctx.AConfig().Getenv("ANDROID_PGO_INSTRUMENT")
pgoBenchmarksMap := make(map[string]bool)
for _, b := range strings.Split(pgoBenchmarks, ",") {
pgoBenchmarksMap[b] = true
}
for _, b := range pgo.Properties.Pgo.Benchmarks {
if pgoBenchmarksMap[b] == true {
pgo.Properties.ShouldProfileModule = true
break
}
}
}
func (pgo *pgo) flags(ctx ModuleContext, flags Flags) Flags {
if ctx.Host() {
return flags
}
props := pgo.Properties
// Add flags to profile this module based on its profile_kind
if props.ShouldProfileModule {
profileGatherFlags := pgo.profileGatherFlags(ctx)
flags.LdFlags = append(flags.LdFlags, profileGatherFlags)
flags.CFlags = append(flags.CFlags, profileGatherFlags)
return flags
}
// If the PGO profiles project is found, and this module has PGO
// enabled, add flags to use the profile
if profilesDir := getPgoProfilesDir(ctx); props.PgoPresent && profilesDir.Valid() {
profileFile := android.PathForSource(ctx, profilesDir.String(), *(props.Pgo.Profile_file))
profileUseFlags := pgo.profileUseFlags(ctx, profileFile.String())
flags.CFlags = append(flags.CFlags, profileUseFlags...)
flags.LdFlags = append(flags.LdFlags, profileUseFlags...)
// Update CFlagsDeps and LdFlagsDeps so the module is rebuilt
// if profileFile gets updated
flags.CFlagsDeps = append(flags.CFlagsDeps, profileFile)
flags.LdFlagsDeps = append(flags.LdFlagsDeps, profileFile)
}
return flags
}