diff --git a/Android.bp b/Android.bp index 189267080..b8a4f8243 100644 --- a/Android.bp +++ b/Android.bp @@ -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", diff --git a/cc/binary.go b/cc/binary.go index 4b10070f3..b2405b6fc 100644 --- a/cc/binary.go +++ b/cc/binary.go @@ -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, diff --git a/cc/cc.go b/cc/cc.go index af58f9d0e..b4b70ed51 100644 --- a/cc/cc.go +++ b/cc/cc.go @@ -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 = <o{} + 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{}, <OProperties{}, + &PgoProperties{}, ) android.InitDefaultsModule(module) diff --git a/cc/library.go b/cc/library.go index 2a866dc97..f1681db2a 100644 --- a/cc/library.go +++ b/cc/library.go @@ -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) diff --git a/cc/pgo.go b/cc/pgo.go new file mode 100644 index 000000000..ccddece99 --- /dev/null +++ b/cc/pgo.go @@ -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 +}