blob: e10ca70cd014683942ca8075c6ecf0b42edc817e [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 static java.nio.charset.StandardCharsets.ISO_8859_1;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.io.ByteSource;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.ArtifactRoot;
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.skyframe.serialization.ObjectCodec;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization;
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.Root;
import com.google.devtools.build.lib.vfs.RootedPath;
import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.LipoMode;
import com.google.devtools.build.skyframe.SkyFunction;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
/**
* 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>Note that the LIPO context can be different from the actual binary we are compiling because
* it's beneficial to compile sources in a test in the exact same way as they would be compiled for
* a particular {@code cc_binary} so that the code tested is the same as the one being run in
* production. Thus, the {@code --lipo_context} command line flag, which takes the label of a {@code
* cc_binary} rule as an argument which will be used as the LIPO context.
*
* <p>In this case, it can happen that files are needed for the compilation (because code in them is
* inlined) that are not in the transitive closure of the tests being run. To cover this case, we
* have the otherwise unused {@code :lipo_context} attribute, which depends on the LIPO context
* without any configuration transition. Its purpose is to give a chance for the configured targets
* containing the inlined code to run and thus create generating actions for the artifacts {@link
* LipoContextProvider} contains. That is, configured targets in the LIPO context collector
* configuration collect these artifacts but do not generate actions for them, and configured
* targets under {@code :lipo_context} generate actions, but the artifacts they create are
* discarded. This works because {@link Artifact} is a value object and the artifacts in {@link
* LipoContextProvider} are {@code #equals()} to the ones created under {@code :lipo_context}.
*
* <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 {@code CcCompilationContext} for LIPO compilation actions is pieced
* together from the {@code CcCompilationContext} in LipoContextProvider and that of the rule being
* compiled. (see {@link CcCompilationContext#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
@AutoCodec
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.
*/
@VisibleForSerialization
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,
}
/**
* Returns true if the given fdoFile represents an AutoFdo profile.
*/
public static final boolean isAutoFdo(String fdoFile) {
return CppFileTypes.GCC_AUTO_PROFILE.matches(fdoFile);
}
/**
* Coverage information output directory passed to {@code --fdo_instrument},
* or {@code null} if FDO instrumentation is disabled.
*/
private final String 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.
*/
// TODO(lberki): this should be a PathFragment
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 ArtifactRoot 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
*/
@VisibleForSerialization
@AutoCodec.Instantiator
FdoSupport(
FdoMode fdoMode,
LipoMode lipoMode,
ArtifactRoot fdoRoot,
PathFragment fdoRootExecPath,
String fdoInstrument,
Path fdoProfile,
FdoZipContents fdoZipContents) {
this.fdoInstrument = fdoInstrument;
this.fdoProfile = fdoProfile;
this.fdoRoot = fdoRoot;
this.fdoRootExecPath = fdoRootExecPath;
this.fdoPath = fdoProfile == null
? null
: FileSystemUtils.removeExtension(PathFragment.create("_fdo").getChild(
fdoProfile.getBaseName()));
this.lipoMode = lipoMode;
this.fdoMode = fdoMode;
if (fdoZipContents != null) {
this.gcdaFiles = fdoZipContents.gcdaFiles;
this.imports = fdoZipContents.imports;
} else {
this.gcdaFiles = null;
this.imports = null;
}
}
public ArtifactRoot getFdoRoot() {
return fdoRoot;
}
public Path getFdoProfile() {
return fdoProfile;
}
@VisibleForSerialization
// This method only exists for serialization.
FdoZipContents getFdoZipContents() {
return gcdaFiles == null ? null : new FdoZipContents(gcdaFiles, imports);
}
/** Creates an initialized {@link FdoSupport} instance. */
static FdoSupport create(
SkyFunction.Environment env,
String fdoInstrument,
Path fdoProfile,
LipoMode lipoMode,
Path execRoot,
String productName,
FdoMode fdoMode)
throws IOException, FdoException, InterruptedException {
if (fdoProfile == null) {
lipoMode = LipoMode.OFF;
}
ArtifactRoot fdoRoot =
(fdoProfile == null)
? null
: ArtifactRoot.asDerivedRoot(execRoot, execRoot.getRelative(productName + "-fdo"));
PathFragment fdoRootExecPath = fdoProfile == null
? null
: fdoRoot.getExecPath().getRelative(FileSystemUtils.removeExtension(
PathFragment.create("_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(Root.fromPath(execRoot)))));
}
}
if (env.valuesMissing()) {
return null;
}
if (fdoMode == FdoMode.LLVM_FDO) {
return new FdoSupport(
fdoMode, LipoMode.OFF, fdoRoot, fdoRootExecPath, fdoInstrument, fdoProfile, null);
}
FdoZipContents fdoZipContents =
extractFdoZip(fdoMode, lipoMode, execRoot, fdoProfile, fdoRootExecPath, productName);
return new FdoSupport(
fdoMode, lipoMode, fdoRoot, fdoRootExecPath, fdoInstrument, fdoProfile, fdoZipContents);
}
@Immutable
@AutoCodec
static class FdoZipContents {
public static final ObjectCodec<FdoZipContents> CODEC =
new FdoSupport_FdoZipContents_AutoCodec();
private final ImmutableSet<PathFragment> gcdaFiles;
private final ImmutableMultimap<PathFragment, PathFragment> imports;
@VisibleForSerialization
@AutoCodec.Instantiator
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 {
// Path objects referring to inside the zip file are only valid within this try block.
// FdoZipContents doesn't reference any of them, so we are fine.
if (!fdoProfile.exists()) {
throw new FileNotFoundException(String.format("File '%s' does not exist", fdoProfile));
}
File zipIoFile = fdoProfile.getPathFile();
File tempFile = null;
try {
// If it doesn't exist, we're dealing with an in-memory fs for testing
// Copy the file and delete later
if (!zipIoFile.exists()) {
tempFile = File.createTempFile("bazel.test.", ".tmp");
zipIoFile = tempFile;
byte[] contents = FileSystemUtils.readContent(fdoProfile);
try (OutputStream os = new FileOutputStream(tempFile)) {
os.write(contents);
}
}
try (ZipFile zipFile = new ZipFile(zipIoFile)) {
String outputSymlinkName = productName + "-out";
ImmutableSet.Builder<PathFragment> gcdaFilesBuilder = ImmutableSet.builder();
ImmutableMultimap.Builder<PathFragment, PathFragment> importsBuilder =
ImmutableMultimap.builder();
extractFdoZipDirectory(
zipFile, fdoDirPath, outputSymlinkName, gcdaFilesBuilder, importsBuilder);
gcdaFiles = gcdaFilesBuilder.build();
imports = importsBuilder.build();
}
} finally {
if (tempFile != null) {
tempFile.delete();
}
}
}
}
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(
ZipFile zipFile,
Path targetDir,
String outputSymlinkName,
ImmutableSet.Builder<PathFragment> gcdaFilesBuilder,
ImmutableMultimap.Builder<PathFragment, PathFragment> importsBuilder)
throws IOException, FdoException {
Enumeration<? extends ZipEntry> zipEntries = zipFile.entries();
while (zipEntries.hasMoreElements()) {
ZipEntry zipEntry = zipEntries.nextElement();
if (zipEntry.isDirectory()) {
continue;
}
String name = zipEntry.getName();
if (!name.startsWith(outputSymlinkName)) {
throw new ZipException(
"FDO zip files must be zipped directly above '"
+ outputSymlinkName
+ "' for the compiler to find the profile");
}
Path targetFile = targetDir.getRelative(name);
FileSystemUtils.createDirectoryAndParents(targetFile.getParentDirectory());
if (CppFileTypes.COVERAGE_DATA.matches(name)) {
try (OutputStream out = targetFile.getOutputStream()) {
new ZipByteSource(zipFile, zipEntry).copyTo(out);
}
targetFile.setLastModifiedTime(zipEntry.getTime());
targetFile.setWritable(false);
targetFile.setExecutable(false);
gcdaFilesBuilder.add(PathFragment.create(name));
} else if (CppFileTypes.COVERAGE_DATA_IMPORTS.matches(name)) {
readCoverageImports(zipFile, zipEntry, importsBuilder);
}
}
}
private static class ZipByteSource extends ByteSource {
final ZipFile zipFile;
final ZipEntry zipEntry;
ZipByteSource(ZipFile zipFile, ZipEntry zipEntry) {
this.zipFile = zipFile;
this.zipEntry = zipEntry;
}
@Override
public InputStream openStream() throws IOException {
return zipFile.getInputStream(zipEntry);
}
}
/**
* Reads a .gcda.imports file and stores the imports information.
*
* @throws FdoException if an auxiliary LIPO input was not found
*/
private static void readCoverageImports(
ZipFile zipFile,
ZipEntry importsFile,
ImmutableMultimap.Builder<PathFragment, PathFragment> importsBuilder)
throws IOException, FdoException {
PathFragment key = PathFragment.create(importsFile.getName());
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 :
new ZipByteSource(zipFile, importsFile).asCharSource(ISO_8859_1).readLines()) {
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 = PathFragment.create(line);
if (execPath.isAbsolute()) {
throw new FdoException(
"Absolute paths not allowed in gcda imports file " + importsFile + ": " + execPath);
}
importsBuilder.put(key, PathFragment.create(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)) {
line = line.trim();
if (line.isEmpty()) {
continue;
}
int colonIndex = line.indexOf(':');
if (colonIndex < 0) {
continue;
}
PathFragment key = PathFragment.create(line.substring(0, colonIndex));
if (key.isAbsolute()) {
throw new FdoException("Absolute paths not allowed in afdo imports file " + importsFile
+ ": " + key);
}
for (String auxFile : line.substring(colonIndex + 1).split(" ")) {
if (auxFile.length() == 0) {
continue;
}
importBuilder.put(key, PathFragment.create(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 ImmutableMap<String, String> configureCompilation(
CppCompileActionBuilder builder,
RuleContext ruleContext,
PathFragment sourceName,
PathFragment sourceExecPath,
PathFragment outputName,
boolean usePic,
FeatureConfiguration featureConfiguration,
FdoSupportProvider fdoSupportProvider) {
// FDO is disabled -> do nothing.
if ((fdoInstrument == null) && (fdoRoot == null)) {
return ImmutableMap.of();
}
ImmutableMap.Builder<String, String> variablesBuilder = ImmutableMap.builder();
if (featureConfiguration.isEnabled(CppRuleClasses.FDO_INSTRUMENT)) {
variablesBuilder.put(
CompileBuildVariables.FDO_INSTRUMENT_PATH.getVariableName(), fdoInstrument);
}
// Optimization phase
if (fdoRoot != null) {
AnalysisEnvironment env = ruleContext.getAnalysisEnvironment();
// Declare dependency on contents of zip file.
if (env.getSkyframeEnv().valuesMissing()) {
return ImmutableMap.of();
}
Iterable<Artifact> auxiliaryInputs =
getAuxiliaryInputs(
ruleContext, sourceName, sourceExecPath, outputName, usePic, fdoSupportProvider);
builder.addMandatoryInputs(auxiliaryInputs);
if (!Iterables.isEmpty(auxiliaryInputs)) {
if (featureConfiguration.isEnabled(CppRuleClasses.AUTOFDO)) {
variablesBuilder.put(
CompileBuildVariables.FDO_PROFILE_PATH.getVariableName(),
getAutoProfilePath(fdoProfile, fdoRootExecPath).getPathString());
}
if (featureConfiguration.isEnabled(CppRuleClasses.FDO_OPTIMIZE)) {
if (fdoMode == FdoMode.LLVM_FDO) {
variablesBuilder.put(
CompileBuildVariables.FDO_PROFILE_PATH.getVariableName(),
fdoSupportProvider.getProfileArtifact().getExecPathString());
} else {
variablesBuilder.put(
CompileBuildVariables.FDO_PROFILE_PATH.getVariableName(),
fdoRootExecPath.getPathString());
}
}
}
}
return variablesBuilder.build();
}
/** Returns the auxiliary files that need to be added to the {@link CppCompileAction}. */
private Iterable<Artifact> getAuxiliaryInputs(
RuleContext ruleContext,
PathFragment sourceName,
PathFragment sourceExecPath,
PathFragment outputName,
boolean usePic,
FdoSupportProvider fdoSupportProvider) {
CcToolchainProvider toolchain =
CppHelper.getToolchainUsingDefaultCcToolchainAttribute(ruleContext);
LipoContextProvider lipoContextProvider =
toolchain.isLLVMCompiler() ? null : CppHelper.getLipoContextProvider(ruleContext);
// If --fdo_optimize was not specified, we don't have any additional inputs.
if (fdoProfile == null) {
return ImmutableSet.of();
} else if (fdoMode == FdoMode.LLVM_FDO || fdoMode == FdoMode.AUTO_FDO) {
ImmutableSet.Builder<Artifact> auxiliaryInputs = ImmutableSet.builder();
auxiliaryInputs.add(fdoSupportProvider.getProfileArtifact());
if (lipoContextProvider != null) {
auxiliaryInputs.addAll(getAutoFdoImports(ruleContext, sourceExecPath, lipoContextProvider));
}
return auxiliaryInputs.build();
} else {
ImmutableSet.Builder<Artifact> auxiliaryInputs = ImmutableSet.builder();
PathFragment objectName =
FileSystemUtils.appendExtension(outputName, usePic ? ".pic.o" : ".o");
Label lipoLabel = ruleContext.getLabel();
auxiliaryInputs.addAll(
getGcdaArtifactsForObjectFileName(
ruleContext, fdoSupportProvider, objectName, lipoLabel));
if (lipoContextProvider != null) {
for (PathFragment importedFile : getImports(
getNonLipoObjDir(ruleContext, lipoLabel), objectName)) {
if (CppFileTypes.COVERAGE_DATA.matches(importedFile.getBaseName())) {
Artifact gcdaArtifact =
getGcdaArtifactsForGcdaPath(fdoSupportProvider, importedFile);
if (gcdaArtifact == null) {
ruleContext.ruleError(String.format(
".gcda file %s is not in the FDO zip (referenced by source file %s). Check if "
+ "your profile is generated from the same sources you are building the "
+ "optimized binary from",
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' by LIPO inclusion. Check if "
+ "your profile is generated from the same sources you are building the "
+ "optimized binary from",
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(FdoSupportProvider fdoSupportProvider,
PathFragment gcdaPath) {
if (!gcdaFiles.contains(gcdaPath)) {
return null;
}
return fdoSupportProvider.getGcdaArtifacts().get(gcdaPath);
}
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,
FdoSupportProvider fdoSupportProvider, 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();
}
}
return ImmutableList.of(fdoSupportProvider.getGcdaArtifacts().get(gcdaFile));
}
private static PathFragment getAutoProfilePath(Path fdoProfile, PathFragment fdoRootExecPath) {
return fdoRootExecPath.getRelative(getAutoProfileRootRelativePath(fdoProfile));
}
private static PathFragment getAutoProfileRootRelativePath(Path fdoProfile) {
return PathFragment.create(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, CcToolchainVariables.Builder buildVariables) {
if (featureConfiguration.isEnabled(CppRuleClasses.FDO_INSTRUMENT)) {
buildVariables.addStringVariable("fdo_instrument_path", fdoInstrument);
}
}
/**
* Adds the AutoFDO profile path to the variable builder and returns the profile artifact. If
* AutoFDO is disabled, no build variable is added and returns null.
*/
@ThreadSafe
public Artifact buildProfileForLtoBackend(
FdoSupportProvider fdoSupportProvider,
FeatureConfiguration featureConfiguration,
CcToolchainVariables.Builder buildVariables,
RuleContext ruleContext) {
if (!featureConfiguration.isEnabled(CppRuleClasses.AUTOFDO)) {
return null;
}
Artifact profile = fdoSupportProvider.getProfileArtifact();
buildVariables.addStringVariable("fdo_profile_path", profile.getExecPathString());
return profile;
}
/**
* 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;
}
public FdoSupportProvider createFdoSupportProvider(
RuleContext ruleContext, Artifact profileArtifact) {
if (fdoRoot == null) {
return new FdoSupportProvider(this, null, null);
}
if (fdoMode == FdoMode.LLVM_FDO) {
Preconditions.checkState(profileArtifact != null);
return new FdoSupportProvider(this, profileArtifact, null);
}
Preconditions.checkState(fdoPath != null);
PathFragment profileRootRelativePath = getAutoProfileRootRelativePath(fdoProfile);
profileArtifact =
ruleContext
.getAnalysisEnvironment()
.getDerivedArtifact(fdoPath.getRelative(profileRootRelativePath), fdoRoot);
ruleContext.registerAction(new FdoStubAction(ruleContext.getActionOwner(), profileArtifact));
Preconditions.checkState(fdoPath != null);
ImmutableMap.Builder<PathFragment, Artifact> gcdaArtifacts = ImmutableMap.builder();
for (PathFragment path : gcdaFiles) {
Artifact gcdaArtifact = ruleContext.getAnalysisEnvironment().getDerivedArtifact(
fdoPath.getRelative(path), fdoRoot);
ruleContext.registerAction(new FdoStubAction(ruleContext.getActionOwner(), gcdaArtifact));
gcdaArtifacts.put(path, gcdaArtifact);
}
return new FdoSupportProvider(this, profileArtifact, gcdaArtifacts.build());
}
/**
* An exception indicating an issue with FDO coverage files.
*/
public static final class FdoException extends Exception {
FdoException(String message) {
super(message);
}
}
}