Home | History | Annotate | Download | only in cc
      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 
     15 package cc
     16 
     17 import (
     18 	"fmt"
     19 	"path/filepath"
     20 	"strings"
     21 
     22 	"android/soong/android"
     23 	"android/soong/cc/config"
     24 )
     25 
     26 var (
     27 	// Add flags to ignore warnings that profiles are old or missing for
     28 	// some functions
     29 	profileUseOtherFlags = []string{"-Wno-backend-plugin"}
     30 
     31 	globalPgoProfileProjects = []string{
     32 		"toolchain/pgo-profiles",
     33 		"vendor/google_data/pgo-profiles",
     34 	}
     35 )
     36 
     37 const pgoProfileProjectsConfigKey = "PgoProfileProjects"
     38 const profileInstrumentFlag = "-fprofile-generate=/data/local/tmp"
     39 const profileSamplingFlag = "-gline-tables-only"
     40 const profileUseInstrumentFormat = "-fprofile-use=%s"
     41 const profileUseSamplingFormat = "-fprofile-sample-use=%s"
     42 
     43 func getPgoProfileProjects(config android.DeviceConfig) []string {
     44 	return config.OnceStringSlice(pgoProfileProjectsConfigKey, func() []string {
     45 		return append(globalPgoProfileProjects, config.PgoAdditionalProfileDirs()...)
     46 	})
     47 }
     48 
     49 func recordMissingProfileFile(ctx BaseModuleContext, missing string) {
     50 	getNamedMapForConfig(ctx.Config(), modulesMissingProfileFile).Store(missing, true)
     51 }
     52 
     53 type PgoProperties struct {
     54 	Pgo struct {
     55 		Instrumentation    *bool
     56 		Sampling           *bool
     57 		Profile_file       *string `android:"arch_variant"`
     58 		Benchmarks         []string
     59 		Enable_profile_use *bool `android:"arch_variant"`
     60 		// Additional compiler flags to use when building this module
     61 		// for profiling (either instrumentation or sampling).
     62 		Cflags []string `android:"arch_variant"`
     63 	} `android:"arch_variant"`
     64 
     65 	PgoPresent          bool `blueprint:"mutated"`
     66 	ShouldProfileModule bool `blueprint:"mutated"`
     67 	PgoCompile          bool `blueprint:"mutated"`
     68 }
     69 
     70 type pgo struct {
     71 	Properties PgoProperties
     72 }
     73 
     74 func (props *PgoProperties) isInstrumentation() bool {
     75 	return props.Pgo.Instrumentation != nil && *props.Pgo.Instrumentation == true
     76 }
     77 
     78 func (props *PgoProperties) isSampling() bool {
     79 	return props.Pgo.Sampling != nil && *props.Pgo.Sampling == true
     80 }
     81 
     82 func (pgo *pgo) props() []interface{} {
     83 	return []interface{}{&pgo.Properties}
     84 }
     85 
     86 func (props *PgoProperties) addProfileGatherFlags(ctx ModuleContext, flags Flags) Flags {
     87 	flags.CFlags = append(flags.CFlags, props.Pgo.Cflags...)
     88 
     89 	if props.isInstrumentation() {
     90 		flags.CFlags = append(flags.CFlags, profileInstrumentFlag)
     91 		// The profile runtime is added below in deps().  Add the below
     92 		// flag, which is the only other link-time action performed by
     93 		// the Clang driver during link.
     94 		flags.LdFlags = append(flags.LdFlags, "-u__llvm_profile_runtime")
     95 	}
     96 	if props.isSampling() {
     97 		flags.CFlags = append(flags.CFlags, profileSamplingFlag)
     98 		flags.LdFlags = append(flags.LdFlags, profileSamplingFlag)
     99 	}
    100 	return flags
    101 }
    102 
    103 func (props *PgoProperties) getPgoProfileFile(ctx BaseModuleContext) android.OptionalPath {
    104 	profile_file := *props.Pgo.Profile_file
    105 
    106 	// Test if the profile_file is present in any of the PGO profile projects
    107 	for _, profileProject := range getPgoProfileProjects(ctx.DeviceConfig()) {
    108 		// Bug: http://b/74395273 If the profile_file is unavailable,
    109 		// use a versioned file named
    110 		// <profile_file>.<arbitrary-version> when available.  This
    111 		// works around an issue where ccache serves stale cache
    112 		// entries when the profile file has changed.
    113 		globPattern := filepath.Join(profileProject, profile_file+".*")
    114 		versioned_profiles, err := ctx.GlobWithDeps(globPattern, nil)
    115 		if err != nil {
    116 			ctx.ModuleErrorf("glob: %s", err.Error())
    117 		}
    118 
    119 		path := android.ExistentPathForSource(ctx, profileProject, profile_file)
    120 		if path.Valid() {
    121 			if len(versioned_profiles) != 0 {
    122 				ctx.PropertyErrorf("pgo.profile_file", "Profile_file has multiple versions: "+filepath.Join(profileProject, profile_file)+", "+strings.Join(versioned_profiles, ", "))
    123 			}
    124 			return path
    125 		}
    126 
    127 		if len(versioned_profiles) > 1 {
    128 			ctx.PropertyErrorf("pgo.profile_file", "Profile_file has multiple versions: "+strings.Join(versioned_profiles, ", "))
    129 		} else if len(versioned_profiles) == 1 {
    130 			return android.OptionalPathForPath(android.PathForSource(ctx, versioned_profiles[0]))
    131 		}
    132 	}
    133 
    134 	// Record that this module's profile file is absent
    135 	missing := *props.Pgo.Profile_file + ":" + ctx.ModuleDir() + "/Android.bp:" + ctx.ModuleName()
    136 	recordMissingProfileFile(ctx, missing)
    137 
    138 	return android.OptionalPathForPath(nil)
    139 }
    140 
    141 func (props *PgoProperties) profileUseFlag(ctx ModuleContext, file string) string {
    142 	if props.isInstrumentation() {
    143 		return fmt.Sprintf(profileUseInstrumentFormat, file)
    144 	}
    145 	if props.isSampling() {
    146 		return fmt.Sprintf(profileUseSamplingFormat, file)
    147 	}
    148 	return ""
    149 }
    150 
    151 func (props *PgoProperties) profileUseFlags(ctx ModuleContext, file string) []string {
    152 	flags := []string{props.profileUseFlag(ctx, file)}
    153 	flags = append(flags, profileUseOtherFlags...)
    154 	return flags
    155 }
    156 
    157 func (props *PgoProperties) addProfileUseFlags(ctx ModuleContext, flags Flags) Flags {
    158 	// Return if 'pgo' property is not present in this module.
    159 	if !props.PgoPresent {
    160 		return flags
    161 	}
    162 
    163 	// Skip -fprofile-use if 'enable_profile_use' property is set
    164 	if props.Pgo.Enable_profile_use != nil && *props.Pgo.Enable_profile_use == false {
    165 		return flags
    166 	}
    167 
    168 	// If the profile file is found, add flags to use the profile
    169 	if profileFile := props.getPgoProfileFile(ctx); profileFile.Valid() {
    170 		profileFilePath := profileFile.Path()
    171 		profileUseFlags := props.profileUseFlags(ctx, profileFilePath.String())
    172 
    173 		flags.CFlags = append(flags.CFlags, profileUseFlags...)
    174 		flags.LdFlags = append(flags.LdFlags, profileUseFlags...)
    175 
    176 		// Update CFlagsDeps and LdFlagsDeps so the module is rebuilt
    177 		// if profileFile gets updated
    178 		flags.CFlagsDeps = append(flags.CFlagsDeps, profileFilePath)
    179 		flags.LdFlagsDeps = append(flags.LdFlagsDeps, profileFilePath)
    180 	}
    181 	return flags
    182 }
    183 
    184 func (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 	// If at least one property exists, validate that all properties exist
    198 	if !profileKindPresent || !filePresent || !benchmarksPresent {
    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 		if !benchmarksPresent {
    207 			missing = append(missing, "non-empty benchmarks property")
    208 		}
    209 		missingProps := strings.Join(missing, ", ")
    210 		ctx.ModuleErrorf("PGO specification is missing properties: " + missingProps)
    211 	}
    212 
    213 	// Sampling not supported yet
    214 	if isSampling {
    215 		ctx.PropertyErrorf("pgo.sampling", "\"sampling\" is not supported yet)")
    216 	}
    217 
    218 	if isSampling && isInstrumentation {
    219 		ctx.PropertyErrorf("pgo", "Exactly one of \"instrumentation\" and \"sampling\" properties must be set")
    220 	}
    221 
    222 	return true
    223 }
    224 
    225 func (pgo *pgo) begin(ctx BaseModuleContext) {
    226 	// TODO Evaluate if we need to support PGO for host modules
    227 	if ctx.Host() {
    228 		return
    229 	}
    230 
    231 	// Check if PGO is needed for this module
    232 	pgo.Properties.PgoPresent = pgo.Properties.isPGO(ctx)
    233 
    234 	if !pgo.Properties.PgoPresent {
    235 		return
    236 	}
    237 
    238 	// This module should be instrumented if ANDROID_PGO_INSTRUMENT is set
    239 	// and includes 'all', 'ALL' or a benchmark listed for this module.
    240 	//
    241 	// TODO Validate that each benchmark instruments at least one module
    242 	pgo.Properties.ShouldProfileModule = false
    243 	pgoBenchmarks := ctx.Config().Getenv("ANDROID_PGO_INSTRUMENT")
    244 	pgoBenchmarksMap := make(map[string]bool)
    245 	for _, b := range strings.Split(pgoBenchmarks, ",") {
    246 		pgoBenchmarksMap[b] = true
    247 	}
    248 
    249 	if pgoBenchmarksMap["all"] == true || pgoBenchmarksMap["ALL"] == true {
    250 		pgo.Properties.ShouldProfileModule = true
    251 	} else {
    252 		for _, b := range pgo.Properties.Pgo.Benchmarks {
    253 			if pgoBenchmarksMap[b] == true {
    254 				pgo.Properties.ShouldProfileModule = true
    255 				break
    256 			}
    257 		}
    258 	}
    259 
    260 	if !ctx.Config().IsEnvTrue("ANDROID_PGO_NO_PROFILE_USE") {
    261 		if profileFile := pgo.Properties.getPgoProfileFile(ctx); profileFile.Valid() {
    262 			pgo.Properties.PgoCompile = true
    263 		}
    264 	}
    265 }
    266 
    267 func (pgo *pgo) deps(ctx BaseModuleContext, deps Deps) Deps {
    268 	if pgo.Properties.ShouldProfileModule {
    269 		runtimeLibrary := config.ProfileRuntimeLibrary(ctx.toolchain())
    270 		deps.LateStaticLibs = append(deps.LateStaticLibs, runtimeLibrary)
    271 	}
    272 	return deps
    273 }
    274 
    275 func (pgo *pgo) flags(ctx ModuleContext, flags Flags) Flags {
    276 	if ctx.Host() {
    277 		return flags
    278 	}
    279 
    280 	props := pgo.Properties
    281 
    282 	// Add flags to profile this module based on its profile_kind
    283 	if props.ShouldProfileModule {
    284 		return props.addProfileGatherFlags(ctx, flags)
    285 	}
    286 
    287 	if !ctx.Config().IsEnvTrue("ANDROID_PGO_NO_PROFILE_USE") {
    288 		return props.addProfileUseFlags(ctx, flags)
    289 	}
    290 
    291 	return flags
    292 }
    293