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