1// Copyright 2017 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package cc 16 17import ( 18 "fmt" 19 "path/filepath" 20 "strings" 21 22 "github.com/google/blueprint/proptools" 23 24 "android/soong/android" 25) 26 27var ( 28 // Add flags to ignore warnings that profiles are old or missing for 29 // some functions. 30 profileUseOtherFlags = []string{ 31 "-Wno-backend-plugin", 32 } 33 34 globalPgoProfileProjects = []string{ 35 "toolchain/pgo-profiles", 36 "vendor/google_data/pgo_profile", 37 } 38) 39 40var pgoProfileProjectsConfigKey = android.NewOnceKey("PgoProfileProjects") 41 42const profileInstrumentFlag = "-fprofile-generate=/data/local/tmp" 43const profileUseInstrumentFormat = "-fprofile-use=%s" 44const profileUseSamplingFormat = "-fprofile-sample-accurate -fprofile-sample-use=%s" 45 46func getPgoProfileProjects(config android.DeviceConfig) []string { 47 return config.OnceStringSlice(pgoProfileProjectsConfigKey, func() []string { 48 return append(globalPgoProfileProjects, config.PgoAdditionalProfileDirs()...) 49 }) 50} 51 52func recordMissingProfileFile(ctx BaseModuleContext, missing string) { 53 getNamedMapForConfig(ctx.Config(), modulesMissingProfileFileKey).Store(missing, true) 54} 55 56type PgoProperties struct { 57 Pgo struct { 58 Instrumentation *bool 59 Sampling *bool `android:"arch_variant"` 60 Profile_file *string `android:"arch_variant"` 61 Benchmarks []string 62 Enable_profile_use *bool `android:"arch_variant"` 63 // Additional compiler flags to use when building this module 64 // for profiling (either instrumentation or sampling). 65 Cflags []string `android:"arch_variant"` 66 } `android:"arch_variant"` 67 68 PgoPresent bool `blueprint:"mutated"` 69 ShouldProfileModule bool `blueprint:"mutated"` 70 PgoCompile bool `blueprint:"mutated"` 71 PgoInstrLink bool `blueprint:"mutated"` 72} 73 74type pgo struct { 75 Properties PgoProperties 76} 77 78func (props *PgoProperties) isInstrumentation() bool { 79 return props.Pgo.Instrumentation != nil && *props.Pgo.Instrumentation == true 80} 81 82func (props *PgoProperties) isSampling() bool { 83 return props.Pgo.Sampling != nil && *props.Pgo.Sampling == true 84} 85 86func (pgo *pgo) props() []interface{} { 87 return []interface{}{&pgo.Properties} 88} 89 90func (props *PgoProperties) addInstrumentationProfileGatherFlags(ctx ModuleContext, flags Flags) Flags { 91 // Add to C flags iff PGO is explicitly enabled for this module. 92 if props.ShouldProfileModule { 93 flags.Local.CFlags = append(flags.Local.CFlags, props.Pgo.Cflags...) 94 flags.Local.CFlags = append(flags.Local.CFlags, profileInstrumentFlag) 95 } 96 flags.Local.LdFlags = append(flags.Local.LdFlags, profileInstrumentFlag) 97 return flags 98} 99func (props *PgoProperties) addSamplingProfileGatherFlags(ctx ModuleContext, flags Flags) Flags { 100 flags.Local.CFlags = append(flags.Local.CFlags, props.Pgo.Cflags...) 101 return flags 102} 103 104func (props *PgoProperties) getPgoProfileFile(ctx BaseModuleContext) android.OptionalPath { 105 profileFile := *props.Pgo.Profile_file 106 107 // Test if the profile_file is present in any of the PGO profile projects 108 for _, profileProject := range getPgoProfileProjects(ctx.DeviceConfig()) { 109 // Bug: http://b/74395273 If the profile_file is unavailable, 110 // use a versioned file named 111 // <profile_file>.<arbitrary-version> when available. This 112 // works around an issue where ccache serves stale cache 113 // entries when the profile file has changed. 114 globPattern := filepath.Join(profileProject, profileFile+".*") 115 versionedProfiles, err := ctx.GlobWithDeps(globPattern, nil) 116 if err != nil { 117 ctx.ModuleErrorf("glob: %s", err.Error()) 118 } 119 120 path := android.ExistentPathForSource(ctx, profileProject, profileFile) 121 if path.Valid() { 122 if len(versionedProfiles) != 0 { 123 ctx.PropertyErrorf("pgo.profile_file", "Profile_file has multiple versions: "+filepath.Join(profileProject, profileFile)+", "+strings.Join(versionedProfiles, ", ")) 124 } 125 return path 126 } 127 128 if len(versionedProfiles) > 1 { 129 ctx.PropertyErrorf("pgo.profile_file", "Profile_file has multiple versions: "+strings.Join(versionedProfiles, ", ")) 130 } else if len(versionedProfiles) == 1 { 131 return android.OptionalPathForPath(android.PathForSource(ctx, versionedProfiles[0])) 132 } 133 } 134 135 // Record that this module's profile file is absent 136 missing := *props.Pgo.Profile_file + ":" + ctx.ModuleDir() + "/Android.bp:" + ctx.ModuleName() 137 recordMissingProfileFile(ctx, missing) 138 139 return android.OptionalPathForPath(nil) 140} 141 142func (props *PgoProperties) profileUseFlag(ctx ModuleContext, file string) string { 143 if props.isInstrumentation() { 144 return fmt.Sprintf(profileUseInstrumentFormat, file) 145 } 146 if props.isSampling() { 147 return fmt.Sprintf(profileUseSamplingFormat, file) 148 } 149 return "" 150} 151 152func (props *PgoProperties) profileUseFlags(ctx ModuleContext, file string) []string { 153 flags := []string{props.profileUseFlag(ctx, file)} 154 flags = append(flags, profileUseOtherFlags...) 155 return flags 156} 157 158func (props *PgoProperties) addProfileUseFlags(ctx ModuleContext, flags Flags) Flags { 159 // Return if 'pgo' property is not present in this module. 160 if !props.PgoPresent { 161 return flags 162 } 163 164 if props.PgoCompile { 165 profileFile := props.getPgoProfileFile(ctx) 166 profileFilePath := profileFile.Path() 167 profileUseFlags := props.profileUseFlags(ctx, profileFilePath.String()) 168 169 flags.Local.CFlags = append(flags.Local.CFlags, profileUseFlags...) 170 flags.Local.LdFlags = append(flags.Local.LdFlags, profileUseFlags...) 171 172 // Update CFlagsDeps and LdFlagsDeps so the module is rebuilt 173 // if profileFile gets updated 174 flags.CFlagsDeps = append(flags.CFlagsDeps, profileFilePath) 175 flags.LdFlagsDeps = append(flags.LdFlagsDeps, profileFilePath) 176 177 if props.isSampling() { 178 flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,-mllvm,-no-warn-sample-unused=true") 179 } 180 } 181 return flags 182} 183 184func (props *PgoProperties) isPGO(ctx BaseModuleContext) bool { 185 isInstrumentation := props.isInstrumentation() 186 isSampling := props.isSampling() 187 188 profileKindPresent := isInstrumentation || isSampling 189 filePresent := props.Pgo.Profile_file != nil 190 benchmarksPresent := len(props.Pgo.Benchmarks) > 0 191 192 // If all three properties are absent, PGO is OFF for this module 193 if !profileKindPresent && !filePresent && !benchmarksPresent { 194 return false 195 } 196 197 // profileKindPresent and filePresent are mandatory properties. 198 if !profileKindPresent || !filePresent { 199 var missing []string 200 if !profileKindPresent { 201 missing = append(missing, "profile kind (either \"instrumentation\" or \"sampling\" property)") 202 } 203 if !filePresent { 204 missing = append(missing, "profile_file property") 205 } 206 missingProps := strings.Join(missing, ", ") 207 ctx.ModuleErrorf("PGO specification is missing properties: " + missingProps) 208 } 209 210 // Benchmark property is mandatory for instrumentation PGO. 211 if isInstrumentation && !benchmarksPresent { 212 ctx.ModuleErrorf("Instrumentation PGO specification is missing benchmark property") 213 } 214 215 if isSampling && isInstrumentation { 216 ctx.PropertyErrorf("pgo", "Exactly one of \"instrumentation\" and \"sampling\" properties must be set") 217 } 218 219 return true 220} 221 222func (pgo *pgo) begin(ctx BaseModuleContext) { 223 // TODO Evaluate if we need to support PGO for host modules 224 if ctx.Host() { 225 return 226 } 227 228 // Check if PGO is needed for this module 229 pgo.Properties.PgoPresent = pgo.Properties.isPGO(ctx) 230 231 if !pgo.Properties.PgoPresent { 232 return 233 } 234 235 // This module should be instrumented if ANDROID_PGO_INSTRUMENT is set 236 // and includes 'all', 'ALL' or a benchmark listed for this module. 237 // 238 // TODO Validate that each benchmark instruments at least one module 239 pgo.Properties.ShouldProfileModule = false 240 pgoBenchmarks := ctx.Config().Getenv("ANDROID_PGO_INSTRUMENT") 241 pgoBenchmarksMap := make(map[string]bool) 242 for _, b := range strings.Split(pgoBenchmarks, ",") { 243 pgoBenchmarksMap[b] = true 244 } 245 246 if pgoBenchmarksMap["all"] == true || pgoBenchmarksMap["ALL"] == true { 247 pgo.Properties.ShouldProfileModule = true 248 pgo.Properties.PgoInstrLink = pgo.Properties.isInstrumentation() 249 } else { 250 for _, b := range pgo.Properties.Pgo.Benchmarks { 251 if pgoBenchmarksMap[b] == true { 252 pgo.Properties.ShouldProfileModule = true 253 pgo.Properties.PgoInstrLink = pgo.Properties.isInstrumentation() 254 break 255 } 256 } 257 } 258 259 // PGO profile use is not feasible for a Clang coverage build because 260 // -fprofile-use and -fprofile-instr-generate are incompatible. 261 if ctx.DeviceConfig().ClangCoverageEnabled() { 262 return 263 } 264 265 if !ctx.Config().IsEnvTrue("ANDROID_PGO_NO_PROFILE_USE") && 266 proptools.BoolDefault(pgo.Properties.Pgo.Enable_profile_use, true) { 267 if profileFile := pgo.Properties.getPgoProfileFile(ctx); profileFile.Valid() { 268 pgo.Properties.PgoCompile = true 269 } 270 } 271} 272 273func (pgo *pgo) flags(ctx ModuleContext, flags Flags) Flags { 274 if ctx.Host() { 275 return flags 276 } 277 278 // Deduce PgoInstrLink property i.e. whether this module needs to be 279 // linked with profile-generation flags. Here, we're setting it if any 280 // dependency needs PGO instrumentation. It is initially set in 281 // begin() if PGO is directly enabled for this module. 282 if ctx.static() && !ctx.staticBinary() { 283 // For static libraries, check if any whole_static_libs are 284 // linked with profile generation 285 ctx.VisitDirectDeps(func(m android.Module) { 286 if depTag, ok := ctx.OtherModuleDependencyTag(m).(libraryDependencyTag); ok { 287 if depTag.static() && depTag.wholeStatic { 288 if cc, ok := m.(*Module); ok { 289 if cc.pgo.Properties.PgoInstrLink { 290 pgo.Properties.PgoInstrLink = true 291 } 292 } 293 } 294 } 295 }) 296 } else { 297 // For executables and shared libraries, check all static dependencies. 298 ctx.VisitDirectDeps(func(m android.Module) { 299 if depTag, ok := ctx.OtherModuleDependencyTag(m).(libraryDependencyTag); ok { 300 if depTag.static() { 301 if cc, ok := m.(*Module); ok { 302 if cc.pgo.Properties.PgoInstrLink { 303 pgo.Properties.PgoInstrLink = true 304 } 305 } 306 } 307 } 308 }) 309 } 310 311 props := pgo.Properties 312 // Add flags to profile this module based on its profile_kind 313 if (props.ShouldProfileModule && props.isInstrumentation()) || props.PgoInstrLink { 314 // Instrumentation PGO use and gather flags cannot coexist. 315 return props.addInstrumentationProfileGatherFlags(ctx, flags) 316 } else if props.ShouldProfileModule && props.isSampling() { 317 flags = props.addSamplingProfileGatherFlags(ctx, flags) 318 } else if ctx.DeviceConfig().SamplingPGO() { 319 flags = props.addSamplingProfileGatherFlags(ctx, flags) 320 } 321 322 if !ctx.Config().IsEnvTrue("ANDROID_PGO_NO_PROFILE_USE") { 323 flags = props.addProfileUseFlags(ctx, flags) 324 } 325 326 return flags 327} 328