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