blob: 9e09b0643ccaf932725435142f3eb8b53a3b42ab [file] [log] [blame]
// Copyright 2014 The Bazel Authors. 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 com.google.devtools.build.lib.rules.cpp;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.Root;
import com.google.devtools.build.lib.analysis.AnalysisEnvironment;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
import com.google.devtools.build.lib.skyframe.FileValue;
import com.google.devtools.build.lib.skyframe.PrecomputedValue;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.RootedPath;
import com.google.devtools.build.lib.vfs.ZipFileSystem;
import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.LipoMode;
import com.google.devtools.build.skyframe.SkyFunction;
import java.io.IOException;
import java.util.Collection;
import java.util.zip.ZipException;
/**
* Support class for FDO (feedback directed optimization) and LIPO (lightweight inter-procedural
* optimization).
*
* <p>Here follows a quick run-down of how FDO/LIPO builds work (for non-FDO/LIPO builds, none
* of this applies):
*
* <p>{@link FdoSupport#create} is called from {@link FdoSupportFunction} (a {@link SkyFunction}),
* which is requested from Skyframe by the {@code cc_toolchain} rule. It extracts the FDO .zip (in
* case we work with an explicitly generated FDO profile file) or analyzes the .afdo.imports file
* next to the .afdo file (if AutoFDO is in effect).
*
* <p>.afdo.imports files contain one import a line. A line is two paths separated by a colon,
* with functions in the second path being referenced by functions in the first path. These are
* then put into the imports map. If we do AutoFDO, we don't handle individual .gcda files, so
* gcdaFiles will be empty.
*
* <p>Regular .fdo zip files contain .gcda files (which are added to gcdaFiles) and
* .gcda.imports files. There is one .gcda.imports file for every source file and it contains one
* path in every line, which can either be a path to a source file that contains a function
* referenced by the original source file or the .gcda file for such a referenced file. They
* both are added to the imports map.
*
* <p>If we do LIPO, we create an extra configuration that is called the "LIPO context collector",
* whose job it is to collect information that every configured target compiled with LIPO needs.
* The top-level target of this configuration is the LIPO context (always a cc_binary) and is an
* implicit dependency of every cc_* rule through their :lipo_context_collector attribute. The
* collected information is encapsulated in {@link LipoContextProvider}.
*
* <p>For each C++ compile action in the target configuration, {@link #configureCompilation} is
* called, which adds command line options and input files required for the build. There are
* three cases:
*
* <ul>
* <li>If we do AutoFDO, the .afdo file and the source files containing the functions imported
* by the original source file (as determined from the inputs map) are added.
* <li>If we do FDO, the .gcda file corresponding to the source file is added.
* <li>If we do LIPO, in addition to the .gcda file corresponding to the source file
* (like for FDO) the source files that contain the functions referenced by the source file and
* their .gcda files are added, too.
* </ul>
*
* <p>If we do LIPO, the actual C++ compilation context for LIPO compilation actions is pieced
* together from the CppCompileContext in LipoContextProvider and that of the rule being compiled.
* (see {@link CppCompilationContext#mergeForLipo}) This is so that the include files for the
* extra LIPO sources are found and is, strictly speaking, incorrect, since it also changes the
* declared include directories of the main source file, which in theory can result in the
* compilation passing even though it should fail with undeclared inclusion errors.
*
* <p>During the actual execution of the C++ compile action, the extra sources also need to be
* include scanned, which is the reason why they are {@link IncludeScannable} objects and not
* simple artifacts. We currently create these {@link IncludeScannable} objects by creating actual
* C++ compile actions in the LIPO context collector configuration which are then never executed.
* In fact, these C++ compile actions are never even registered with Skyframe. For this we
* propagate a bit from {@code BuildConfiguration.isActionsEnabled} to
* {@code CachingAnalysisEnvironment.allowRegisteringActions}, which causes actions to be silently
* discarded after configured targets are created.
*/
@Immutable
public class FdoSupport {
/**
* The FDO mode we are operating in.
*
* LIPO can only be active if this is not <code>OFF</code>, but all of the modes below can work
* with LIPO either off or on.
*/
private enum FdoMode {
/** FDO is turned off. */
OFF,
/** Profiling-based FDO using an explicitly recorded profile. */
VANILLA,
/** FDO based on automatically collected data. */
AUTO_FDO,
/** Instrumentation-based FDO implemented on LLVM. */
LLVM_FDO,
}
/**
* Path within profile data .zip files that is considered the root of the
* profile information directory tree.
*/
private static final PathFragment ZIP_ROOT = new PathFragment("/");
/**
* Returns true if the given fdoFile represents an AutoFdo profile.
*/
public static final boolean isAutoFdo(String fdoFile) {
return CppFileTypes.GCC_AUTO_PROFILE.matches(fdoFile);
}
/**
* Returns true if the given fdoFile represents an LLVM profile.
*/
public static final boolean isLLVMFdo(String fdoFile) {
return CppFileTypes.LLVM_PROFILE.matches(fdoFile);
}
/**
* Coverage information output directory passed to {@code --fdo_instrument},
* or {@code null} if FDO instrumentation is disabled.
*/
private final PathFragment fdoInstrument;
/**
* Path of the profile file passed to {@code --fdo_optimize}, or
* {@code null} if FDO optimization is disabled. The profile file
* can be a coverage ZIP or an AutoFDO feedback file.
*/
private final Path fdoProfile;
/**
* Temporary directory to which the coverage ZIP file is extracted to
* (relative to the exec root), or {@code null} if FDO optimization is
* disabled. This is used to create artifacts for the extracted files.
*
* <p>Note that this root is intentionally not registered with the artifact
* factory.
*/
private final Root fdoRoot;
/**
* The relative path of the FDO root to the exec root.
*/
private final PathFragment fdoRootExecPath;
/**
* Path of FDO files under the FDO root.
*/
private final PathFragment fdoPath;
/**
* LIPO mode passed to {@code --lipo}. This is only used if
* {@code fdoProfile != null}.
*/
private final LipoMode lipoMode;
/**
* FDO mode.
*/
private final FdoMode fdoMode;
/**
* The {@code .gcda} files that have been extracted from the ZIP file,
* relative to the root of the ZIP file.
*/
private final ImmutableSet<PathFragment> gcdaFiles;
/**
* Multimap from .gcda file base names to auxiliary input files.
*
* <p>The keys of the multimap are the exec root relative paths of .gcda files
* with the extension removed. The values are the lines from the accompanying
* .gcda.imports file.
*
* <p>The contents of the multimap are copied verbatim from the .gcda.imports
* files and not yet checked for validity.
*/
private final ImmutableMultimap<PathFragment, PathFragment> imports;
/**
* Creates an FDO support object.
*
* @param fdoInstrument value of the --fdo_instrument option
* @param fdoProfile path to the profile file passed to --fdo_optimize option
* @param lipoMode value of the --lipo_mode option
*/
private FdoSupport(FdoMode fdoMode, LipoMode lipoMode, Root fdoRoot, PathFragment fdoRootExecPath,
PathFragment fdoInstrument, Path fdoProfile, FdoZipContents fdoZipContents) {
this.fdoInstrument = fdoInstrument;
this.fdoProfile = fdoProfile;
this.fdoRoot = fdoRoot;
this.fdoRootExecPath = fdoRootExecPath;
this.fdoPath = fdoProfile == null
? null
: FileSystemUtils.removeExtension(new PathFragment("_fdo").getChild(
fdoProfile.getBaseName()));
this.lipoMode = lipoMode;
this.fdoMode = fdoMode;
this.gcdaFiles = fdoZipContents.gcdaFiles;
this.imports = fdoZipContents.imports;
}
public Root getFdoRoot() {
return fdoRoot;
}
/** Creates an initialized {@link FdoSupport} instance. */
static FdoSupport create(
SkyFunction.Environment env,
PathFragment fdoInstrument,
Path fdoProfile,
LipoMode lipoMode,
Path execRoot)
throws IOException, FdoException, InterruptedException {
FdoMode fdoMode;
if (fdoProfile != null && isAutoFdo(fdoProfile.getBaseName())) {
fdoMode = FdoMode.AUTO_FDO;
} else if (fdoProfile != null && isLLVMFdo(fdoProfile.getBaseName())) {
fdoMode = FdoMode.LLVM_FDO;
} else if (fdoProfile != null) {
fdoMode = FdoMode.VANILLA;
} else {
fdoMode = FdoMode.OFF;
}
if (fdoProfile == null) {
lipoMode = LipoMode.OFF;
}
Root fdoRoot =
(fdoProfile == null)
? null
: Root.asDerivedRoot(execRoot, execRoot.getRelative(
PrecomputedValue.PRODUCT_NAME.get(env) + "-fdo"), true);
PathFragment fdoRootExecPath = fdoProfile == null
? null
: fdoRoot.getExecPath().getRelative(FileSystemUtils.removeExtension(
new PathFragment("_fdo").getChild(fdoProfile.getBaseName())));
if (fdoProfile != null) {
if (lipoMode != LipoMode.OFF) {
// Incrementality is not supported for LIPO builds, see FdoSupport#scannables.
// Ensure that the Skyframe value containing the configuration will not be reused to avoid
// incrementality issues.
PrecomputedValue.dependOnBuildId(env);
} else {
Path path = fdoMode == FdoMode.AUTO_FDO ? getAutoFdoImportsPath(fdoProfile) : fdoProfile;
env.getValue(FileValue.key(RootedPath.toRootedPathMaybeUnderRoot(path,
ImmutableList.of(execRoot))));
}
}
if (env.valuesMissing()) {
return null;
}
FdoZipContents fdoZipContents = extractFdoZip(
fdoMode, lipoMode, execRoot, fdoProfile, fdoRootExecPath,
PrecomputedValue.PRODUCT_NAME.get(env));
return new FdoSupport(
fdoMode, lipoMode, fdoRoot, fdoRootExecPath, fdoInstrument, fdoProfile, fdoZipContents);
}
@Immutable
private static class FdoZipContents {
private final ImmutableSet<PathFragment> gcdaFiles;
private final ImmutableMultimap<PathFragment, PathFragment> imports;
private FdoZipContents(ImmutableSet<PathFragment> gcdaFiles,
ImmutableMultimap<PathFragment, PathFragment> imports) {
this.gcdaFiles = gcdaFiles;
this.imports = imports;
}
}
/**
* Extracts the FDO zip file and collects data from it that's needed during analysis.
*
* <p>When an {@code --fdo_optimize} compile is requested, unpacks the given
* FDO gcda zip file into a clean working directory under execRoot.
*
* @throws FdoException if the FDO ZIP contains a file of unknown type
*/
private static FdoZipContents extractFdoZip(FdoMode fdoMode, LipoMode lipoMode, Path execRoot,
Path fdoProfile, PathFragment fdoRootExecPath, String productName)
throws IOException, FdoException {
// The execRoot != null case is only there for testing. We cannot provide a real ZIP file in
// tests because ZipFileSystem does not work with a ZIP on an in-memory file system.
// IMPORTANT: Keep in sync with #declareSkyframeDependencies to avoid incrementality issues.
ImmutableSet<PathFragment> gcdaFiles = ImmutableSet.of();
ImmutableMultimap<PathFragment, PathFragment> imports = ImmutableMultimap.of();
if (fdoProfile != null && execRoot != null) {
Path fdoDirPath = execRoot.getRelative(fdoRootExecPath);
FileSystemUtils.deleteTreesBelow(fdoDirPath);
FileSystemUtils.createDirectoryAndParents(fdoDirPath);
if (fdoMode == FdoMode.AUTO_FDO) {
if (lipoMode != LipoMode.OFF) {
imports = readAutoFdoImports(getAutoFdoImportsPath(fdoProfile));
}
FileSystemUtils.ensureSymbolicLink(
execRoot.getRelative(getAutoProfilePath(fdoProfile, fdoRootExecPath)), fdoProfile);
} else if (fdoMode == FdoMode.LLVM_FDO) {
FileSystemUtils.ensureSymbolicLink(
execRoot.getRelative(getLLVMProfilePath(fdoProfile, fdoRootExecPath)), fdoProfile);
} else {
Path zipFilePath = new ZipFileSystem(fdoProfile).getRootDirectory();
String outputSymlinkName = productName + "-out";
if (!zipFilePath.getRelative(outputSymlinkName).isDirectory()) {
throw new ZipException(
"FDO zip files must be zipped directly above '"
+ outputSymlinkName
+ "' for the compiler to find the profile");
}
ImmutableSet.Builder<PathFragment> gcdaFilesBuilder = ImmutableSet.builder();
ImmutableMultimap.Builder<PathFragment, PathFragment> importsBuilder =
ImmutableMultimap.builder();
extractFdoZipDirectory(zipFilePath, fdoDirPath, gcdaFilesBuilder, importsBuilder);
gcdaFiles = gcdaFilesBuilder.build();
imports = importsBuilder.build();
}
}
return new FdoZipContents(gcdaFiles, imports);
}
/**
* Recursively extracts a directory from the GCDA ZIP file into a target
* directory.
*
* <p>Imports files are not written to disk. Their content is directly added
* to an internal data structure.
*
* <p>The files are written at $EXECROOT/blaze-fdo/_fdo/(base name of profile zip), and the
* {@code _fdo} directory there is symlinked to from the exec root, so that the file are also
* available at $EXECROOT/_fdo/..., which is their exec path. We need to jump through these
* hoops because the FDO root 1. needs to be a source root, thus the exec path of its root is
* ".", 2. it must not be equal to the exec root so that the artifact factory does not get
* confused, 3. the files under it must be reachable by their exec path from the exec root.
*
* @throws IOException if any of the I/O operations failed
* @throws FdoException if the FDO ZIP contains a file of unknown type
*/
private static void extractFdoZipDirectory(Path sourceDir, Path targetDir,
ImmutableSet.Builder<PathFragment> gcdaFilesBuilder,
ImmutableMultimap.Builder<PathFragment, PathFragment> importsBuilder)
throws IOException, FdoException {
for (Path sourceFile : sourceDir.getDirectoryEntries()) {
Path targetFile = targetDir.getRelative(sourceFile.getBaseName());
if (sourceFile.isDirectory()) {
targetFile.createDirectory();
extractFdoZipDirectory(sourceFile, targetFile, gcdaFilesBuilder, importsBuilder);
} else {
if (CppFileTypes.COVERAGE_DATA.matches(sourceFile)) {
FileSystemUtils.copyFile(sourceFile, targetFile);
gcdaFilesBuilder.add(
sourceFile.relativeTo(sourceFile.getFileSystem().getRootDirectory()));
} else if (CppFileTypes.COVERAGE_DATA_IMPORTS.matches(sourceFile)) {
readCoverageImports(sourceFile, importsBuilder);
} else {
throw new FdoException("FDO ZIP file contained a file of unknown type: " + sourceFile);
}
}
}
}
/**
* Reads a .gcda.imports file and stores the imports information.
*
* @throws FdoException if an auxiliary LIPO input was not found
*/
private static void readCoverageImports(Path importsFile,
ImmutableMultimap.Builder<PathFragment, PathFragment> importsBuilder)
throws IOException, FdoException {
PathFragment key = importsFile.asFragment().relativeTo(ZIP_ROOT);
String baseName = key.getBaseName();
String ext = Iterables.getOnlyElement(CppFileTypes.COVERAGE_DATA_IMPORTS.getExtensions());
key = key.replaceName(baseName.substring(0, baseName.length() - ext.length()));
for (String line : FileSystemUtils.iterateLinesAsLatin1(importsFile)) {
if (!line.isEmpty()) {
// We can't yet fully check the validity of a line. this is done later
// when we actually parse the contained paths.
PathFragment execPath = new PathFragment(line);
if (execPath.isAbsolute()) {
throw new FdoException("Absolute paths not allowed in gcda imports file " + importsFile
+ ": " + execPath);
}
importsBuilder.put(key, new PathFragment(line));
}
}
}
/**
* Reads a .afdo.imports file and stores the imports information.
*/
private static ImmutableMultimap<PathFragment, PathFragment> readAutoFdoImports(Path importsFile)
throws IOException, FdoException {
ImmutableMultimap.Builder<PathFragment, PathFragment> importBuilder =
ImmutableMultimap.builder();
for (String line : FileSystemUtils.iterateLinesAsLatin1(importsFile)) {
if (!line.isEmpty()) {
PathFragment key = new PathFragment(line.substring(0, line.indexOf(':')));
if (key.isAbsolute()) {
throw new FdoException("Absolute paths not allowed in afdo imports file " + importsFile
+ ": " + key);
}
for (String auxFile : line.substring(line.indexOf(':') + 1).split(" ")) {
if (auxFile.length() == 0) {
continue;
}
importBuilder.put(key, new PathFragment(auxFile));
}
}
}
return importBuilder.build();
}
private static Path getAutoFdoImportsPath(Path fdoProfile) {
return fdoProfile.getParentDirectory().getRelative(fdoProfile.getBaseName() + ".imports");
}
/**
* Returns the imports from the .afdo.imports file of a source file.
*
* @param sourceExecPath the source file
*/
private Collection<Artifact> getAutoFdoImports(RuleContext ruleContext,
PathFragment sourceExecPath, LipoContextProvider lipoContextProvider) {
Preconditions.checkState(lipoMode != LipoMode.OFF);
ImmutableCollection<PathFragment> afdoImports = imports.get(sourceExecPath);
Preconditions.checkState(afdoImports != null,
"AutoFDO import data missing for %s", sourceExecPath);
ImmutableList.Builder<Artifact> result = ImmutableList.builder();
for (PathFragment afdoImport : afdoImports) {
Artifact afdoArtifact = lipoContextProvider.getSourceArtifactMap().get(afdoImport);
if (afdoArtifact != null) {
result.add(afdoArtifact);
} else {
ruleContext.ruleError(String.format(
"cannot find source file '%s' referenced from '%s'", afdoImport, sourceExecPath));
}
}
return result.build();
}
/**
* Returns the imports from the .gcda.imports file of an object file.
*
* @param objDirectory the object directory of the object file's target
* @param objectName the object file
*/
private Iterable<PathFragment> getImports(PathFragment objDirectory, PathFragment objectName) {
Preconditions.checkState(lipoMode != LipoMode.OFF);
Preconditions.checkState(imports != null,
"Tried to look up imports of uninitialized FDOSupport");
PathFragment key = objDirectory.getRelative(FileSystemUtils.removeExtension(objectName));
ImmutableCollection<PathFragment> importsForObject = imports.get(key);
Preconditions.checkState(importsForObject != null, "Import data missing for %s", key);
return importsForObject;
}
/**
* Configures a compile action builder by setting up command line options and
* auxiliary inputs according to the FDO configuration. This method does
* nothing If FDO is disabled.
*/
@ThreadSafe
public void configureCompilation(CppCompileActionBuilder builder,
CcToolchainFeatures.Variables.Builder buildVariables, RuleContext ruleContext,
PathFragment sourceName, PathFragment sourceExecPath, boolean usePic,
FeatureConfiguration featureConfiguration) {
// It is a bug if this method is called with useLipo if lipo is disabled. However, it is legal
// if is is called with !useLipo, even though lipo is enabled.
LipoContextProvider lipoInputProvider = CppHelper.getLipoContextProvider(ruleContext);
Preconditions.checkArgument(lipoInputProvider == null || lipoMode != LipoMode.OFF);
// FDO is disabled -> do nothing.
if ((fdoInstrument == null) && (fdoRoot == null)) {
return;
}
if (featureConfiguration.isEnabled(CppRuleClasses.FDO_INSTRUMENT)) {
buildVariables.addVariable("fdo_instrument_path",
fdoInstrument.getPathString());
}
// Optimization phase
if (fdoRoot != null) {
AnalysisEnvironment env = ruleContext.getAnalysisEnvironment();
// Declare dependency on contents of zip file.
if (env.getSkyframeEnv().valuesMissing()) {
return;
}
Iterable<Artifact> auxiliaryInputs = getAuxiliaryInputs(
ruleContext, env, sourceName, sourceExecPath, usePic, lipoInputProvider);
builder.addMandatoryInputs(auxiliaryInputs);
if (!Iterables.isEmpty(auxiliaryInputs)) {
if (featureConfiguration.isEnabled(CppRuleClasses.AUTOFDO)) {
buildVariables.addVariable("fdo_profile_path",
getAutoProfilePath(fdoProfile, fdoRootExecPath).getPathString());
}
if (featureConfiguration.isEnabled(CppRuleClasses.FDO_OPTIMIZE)) {
if (fdoMode == FdoMode.LLVM_FDO) {
buildVariables.addVariable("fdo_profile_path",
getLLVMProfilePath(fdoProfile, fdoRootExecPath).getPathString());
} else {
buildVariables.addVariable("fdo_profile_path",
fdoRootExecPath.getPathString());
}
}
}
}
}
/**
* Returns the auxiliary files that need to be added to the {@link CppCompileAction}.
*/
private Iterable<Artifact> getAuxiliaryInputs(
RuleContext ruleContext, AnalysisEnvironment env, PathFragment sourceName,
PathFragment sourceExecPath, boolean usePic, LipoContextProvider lipoContextProvider) {
// If --fdo_optimize was not specified, we don't have any additional inputs.
if (fdoProfile == null) {
return ImmutableSet.of();
} else if (fdoMode == FdoMode.AUTO_FDO || fdoMode == FdoMode.LLVM_FDO) {
ImmutableSet.Builder<Artifact> auxiliaryInputs = ImmutableSet.builder();
PathFragment profileRootRelativePath = fdoMode == FdoMode.LLVM_FDO
? getLLVMProfileRootRelativePath(fdoProfile)
: getAutoProfileRootRelativePath(fdoProfile);
Artifact artifact = env.getDerivedArtifact(
fdoPath.getRelative(profileRootRelativePath), fdoRoot);
env.registerAction(new FdoStubAction(ruleContext.getActionOwner(), artifact));
auxiliaryInputs.add(artifact);
if (lipoContextProvider != null) {
auxiliaryInputs.addAll(getAutoFdoImports(ruleContext, sourceExecPath, lipoContextProvider));
}
return auxiliaryInputs.build();
} else {
ImmutableSet.Builder<Artifact> auxiliaryInputs = ImmutableSet.builder();
PathFragment objectName =
FileSystemUtils.replaceExtension(sourceName, usePic ? ".pic.o" : ".o");
Label lipoLabel = ruleContext.getLabel();
auxiliaryInputs.addAll(
getGcdaArtifactsForObjectFileName(ruleContext, env, objectName, lipoLabel));
if (lipoContextProvider != null) {
for (PathFragment importedFile : getImports(
getNonLipoObjDir(ruleContext, lipoLabel), objectName)) {
if (CppFileTypes.COVERAGE_DATA.matches(importedFile.getBaseName())) {
Artifact gcdaArtifact = getGcdaArtifactsForGcdaPath(ruleContext, env, importedFile);
if (gcdaArtifact == null) {
ruleContext.ruleError(String.format(
".gcda file %s is not in the FDO zip (referenced by source file %s)",
importedFile, sourceName));
} else {
auxiliaryInputs.add(gcdaArtifact);
}
} else {
Artifact importedArtifact = lipoContextProvider.getSourceArtifactMap()
.get(importedFile);
if (importedArtifact != null) {
auxiliaryInputs.add(importedArtifact);
} else {
ruleContext.ruleError(String.format(
"cannot find source file '%s' referenced from '%s'", importedFile, objectName));
}
}
}
}
return auxiliaryInputs.build();
}
}
/**
* Returns the .gcda file artifacts for a .gcda path from the .gcda.imports file or null if the
* referenced .gcda file is not in the FDO zip.
*/
private Artifact getGcdaArtifactsForGcdaPath(RuleContext ruleContext,
AnalysisEnvironment env, PathFragment gcdaPath) {
if (!gcdaFiles.contains(gcdaPath)) {
return null;
}
Artifact artifact = env.getDerivedArtifact(fdoPath.getRelative(gcdaPath), fdoRoot);
env.registerAction(new FdoStubAction(ruleContext.getActionOwner(), artifact));
return artifact;
}
private PathFragment getNonLipoObjDir(RuleContext ruleContext, Label label) {
return ruleContext.getConfiguration().getBinFragment()
.getRelative(CppHelper.getObjDirectory(label));
}
/**
* Returns a list of .gcda file artifacts for an object file path.
*
* <p>The resulting set is either empty (because no .gcda file exists for the
* given object file) or contains one or two artifacts (the file itself and a
* symlink to it).
*/
private ImmutableList<Artifact> getGcdaArtifactsForObjectFileName(RuleContext ruleContext,
AnalysisEnvironment env, PathFragment objectFileName, Label lipoLabel) {
// We put the .gcda files relative to the location of the .o file in the instrumentation run.
String gcdaExt = Iterables.getOnlyElement(CppFileTypes.COVERAGE_DATA.getExtensions());
PathFragment baseName = FileSystemUtils.replaceExtension(objectFileName, gcdaExt);
PathFragment gcdaFile = getNonLipoObjDir(ruleContext, lipoLabel).getRelative(baseName);
if (!gcdaFiles.contains(gcdaFile)) {
// If the object is a .pic.o file and .pic.gcda is not found, we should try finding .gcda too
String picoExt = Iterables.getOnlyElement(CppFileTypes.PIC_OBJECT_FILE.getExtensions());
baseName = FileSystemUtils.replaceExtension(objectFileName, gcdaExt, picoExt);
if (baseName == null) {
// Object file is not .pic.o
return ImmutableList.of();
}
gcdaFile = getNonLipoObjDir(ruleContext, lipoLabel).getRelative(baseName);
if (!gcdaFiles.contains(gcdaFile)) {
// .gcda file not found
return ImmutableList.of();
}
}
final Artifact artifact = env.getDerivedArtifact(fdoPath.getRelative(gcdaFile), fdoRoot);
env.registerAction(new FdoStubAction(ruleContext.getActionOwner(), artifact));
return ImmutableList.of(artifact);
}
private static PathFragment getAutoProfilePath(Path fdoProfile, PathFragment fdoRootExecPath) {
return fdoRootExecPath.getRelative(getAutoProfileRootRelativePath(fdoProfile));
}
private static PathFragment getAutoProfileRootRelativePath(Path fdoProfile) {
return new PathFragment(fdoProfile.getBaseName());
}
private static PathFragment getLLVMProfilePath(Path fdoProfile, PathFragment fdoRootExecPath) {
return fdoRootExecPath.getRelative(getLLVMProfileRootRelativePath(fdoProfile));
}
private static PathFragment getLLVMProfileRootRelativePath(Path fdoProfile) {
return new PathFragment(fdoProfile.getBaseName());
}
/**
* Returns whether AutoFDO is enabled.
*/
@ThreadSafe
public boolean isAutoFdoEnabled() {
return fdoMode == FdoMode.AUTO_FDO;
}
/**
* Adds the FDO profile output path to the variable builder.
* If FDO is disabled, no build variable is added.
*/
@ThreadSafe
public void getLinkOptions(FeatureConfiguration featureConfiguration,
CcToolchainFeatures.Variables.Builder buildVariables
) {
if (featureConfiguration.isEnabled(CppRuleClasses.FDO_INSTRUMENT)) {
buildVariables.addVariable("fdo_instrument_path",
fdoInstrument.getPathString());
}
}
/**
* Returns the path of the FDO output tree (relative to the execution root)
* containing the .gcda profile files, or null if FDO is not enabled.
*/
@VisibleForTesting
public PathFragment getFdoOptimizeDir() {
return fdoRootExecPath;
}
/**
* An exception indicating an issue with FDO coverage files.
*/
public static final class FdoException extends Exception {
FdoException(String message) {
super(message);
}
}
}