diff options
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/rules/cpp/FdoSupport.java')
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/rules/cpp/FdoSupport.java | 679 |
1 files changed, 679 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/FdoSupport.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/FdoSupport.java new file mode 100644 index 0000000000..911d888952 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/FdoSupport.java @@ -0,0 +1,679 @@ +// Copyright 2014 Google Inc. 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.base.Preconditions; +import com.google.common.base.Predicate; +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.ArtifactFactory; +import com.google.devtools.build.lib.actions.PackageRootResolver; +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.concurrent.ThreadSafety.ThreadHostile; +import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; +import com.google.devtools.build.lib.skyframe.FileValue; +import com.google.devtools.build.lib.skyframe.PrecomputedValue; +import com.google.devtools.build.lib.syntax.Label; +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.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.regex.Pattern; +import java.util.zip.ZipException; + +/** + * Support class for FDO (feedback directed optimization) and LIPO (lightweight inter-procedural + * optimization). + * + * <p>There is a 1:1 relationship between {@link CppConfiguration} objects and {@code FdoSupport} + * objects. The FDO support of a build configuration can be retrieved using {@link + * CppConfiguration#getFdoSupport()}. + * + * <p>With respect to thread-safety, the {@link #prepareToBuild} method is not thread-safe, and must + * not be called concurrently with other methods on this class. + * + * <p>Here follows a quick run-down of how FDO/LIPO builds work (for non-FDO/LIPO builds, none + * of this applies): + * + * <p>{@link CppConfiguration#prepareHook} is called before the analysis phase, which calls + * {@link #prepareToBuild}, which 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. + */ +public class FdoSupport implements Serializable { + + /** + * 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 give 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 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; + + /** + * Flag indicating whether to use AutoFDO (as opposed to + * instrumentation-based FDO). + */ + private final boolean useAutoFdo; + + /** + * The {@code .gcda} files that have been extracted from the ZIP file, + * relative to the root of the ZIP file. + * + * <p>Set only in {@link #prepareToBuild}. + */ + private ImmutableSet<PathFragment> gcdaFiles = ImmutableSet.of(); + + /** + * 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. + * + * <p>Set only in {@link #prepareToBuild}. + */ + private ImmutableMultimap<PathFragment, Artifact> 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 + */ + public FdoSupport(PathFragment fdoInstrument, Path fdoProfile, LipoMode lipoMode, Path execRoot) { + this.fdoInstrument = fdoInstrument; + this.fdoProfile = fdoProfile; + this.fdoRoot = (fdoProfile == null) + ? null + : Root.asDerivedRoot(execRoot, execRoot.getRelative("blaze-fdo")); + this.fdoRootExecPath = fdoProfile == null + ? null + : fdoRoot.getExecPath().getRelative(new PathFragment("_fdo").getChild( + FileSystemUtils.removeExtension(fdoProfile.getBaseName()))); + this.fdoPath = fdoProfile == null + ? null + : new PathFragment("_fdo").getChild( + FileSystemUtils.removeExtension(fdoProfile.getBaseName())); + this.lipoMode = lipoMode; + this.useAutoFdo = fdoProfile != null && isAutoFdo(fdoProfile.getBaseName()); + } + + public Root getFdoRoot() { + return fdoRoot; + } + + public void declareSkyframeDependencies(SkyFunction.Environment env, Path execRoot) { + if (fdoProfile != null) { + if (isLipoEnabled()) { + // 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); + return; + } + + // IMPORTANT: Keep the following in sync with #prepareToBuild. + Path path; + if (useAutoFdo) { + path = fdoProfile.getParentDirectory().getRelative( + fdoProfile.getBaseName() + ".imports"); + } else { + path = fdoProfile; + } + env.getValue(FileValue.key(RootedPath.toRootedPathMaybeUnderRoot(path, + ImmutableList.of(execRoot)))); + } + } + + /** + * Prepares the FDO support for building. + * + * <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 + */ + @ThreadHostile // must be called before starting the build + public void prepareToBuild(Path execRoot, PathFragment genfilesPath, + ArtifactFactory artifactDeserializer, PackageRootResolver resolver) + 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. + if (fdoProfile != null && execRoot != null) { + Path fdoDirPath = execRoot.getRelative(fdoRootExecPath); + + FileSystemUtils.deleteTreesBelow(fdoDirPath); + FileSystemUtils.createDirectoryAndParents(fdoDirPath); + + if (useAutoFdo) { + Path fdoImports = fdoProfile.getParentDirectory().getRelative( + fdoProfile.getBaseName() + ".imports"); + if (isLipoEnabled()) { + imports = readAutoFdoImports(artifactDeserializer, fdoImports, genfilesPath, resolver); + } + FileSystemUtils.ensureSymbolicLink( + execRoot.getRelative(getAutoProfilePath()), fdoProfile); + } else { + Path zipFilePath = new ZipFileSystem(fdoProfile).getRootDirectory(); + if (!zipFilePath.getRelative("blaze-out").isDirectory()) { + throw new ZipException("FDO zip files must be zipped directly above 'blaze-out' " + + "for the compiler to find the profile"); + } + ImmutableSet.Builder<PathFragment> gcdaFilesBuilder = ImmutableSet.builder(); + ImmutableMultimap.Builder<PathFragment, Artifact> importsBuilder = + ImmutableMultimap.builder(); + extractFdoZip(artifactDeserializer, zipFilePath, fdoDirPath, + gcdaFilesBuilder, importsBuilder, resolver); + gcdaFiles = gcdaFilesBuilder.build(); + imports = importsBuilder.build(); + } + } + } + + /** + * 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 void extractFdoZip(ArtifactFactory artifactFactory, Path sourceDir, + Path targetDir, ImmutableSet.Builder<PathFragment> gcdaFilesBuilder, + ImmutableMultimap.Builder<PathFragment, Artifact> importsBuilder, + PackageRootResolver resolver) throws IOException, FdoException { + for (Path sourceFile : sourceDir.getDirectoryEntries()) { + Path targetFile = targetDir.getRelative(sourceFile.getBaseName()); + if (sourceFile.isDirectory()) { + targetFile.createDirectory(); + extractFdoZip(artifactFactory, sourceFile, targetFile, gcdaFilesBuilder, importsBuilder, + resolver); + } 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(artifactFactory, sourceFile, importsBuilder, resolver); + } 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 void readCoverageImports(ArtifactFactory artifactFactory, Path importsFile, + ImmutableMultimap.Builder<PathFragment, Artifact> importsBuilder, + PackageRootResolver resolver) 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); + } + Artifact artifact = artifactFactory.deserializeArtifact(new PathFragment(line), resolver); + if (artifact == null) { + throw new FdoException("Auxiliary LIPO input not found: " + line); + } + + importsBuilder.put(key, artifact); + } + } + } + + /** + * Reads a .afdo.imports file and stores the imports information. + */ + private ImmutableMultimap<PathFragment, Artifact> readAutoFdoImports( + ArtifactFactory artifactFactory, Path importsFile, PathFragment genFilePath, + PackageRootResolver resolver) + throws IOException, FdoException { + ImmutableMultimap.Builder<PathFragment, Artifact> importBuilder = ImmutableMultimap.builder(); + for (String line : FileSystemUtils.iterateLinesAsLatin1(importsFile)) { + if (!line.isEmpty()) { + PathFragment key = new PathFragment(line.substring(0, line.indexOf(':'))); + if (key.startsWith(genFilePath)) { + key = key.relativeTo(genFilePath); + } + if (key.isAbsolute()) { + throw new FdoException("Absolute paths not allowed in afdo imports file " + importsFile + + ": " + key); + } + key = FileSystemUtils.replaceSegments(key, "PROTECTED", "_protected", true); + for (String auxFile : line.substring(line.indexOf(':') + 1).split(" ")) { + if (auxFile.length() == 0) { + continue; + } + Artifact artifact = artifactFactory.deserializeArtifact(new PathFragment(auxFile), + resolver); + if (artifact == null) { + throw new FdoException("Auxiliary LIPO input not found: " + auxFile); + } + importBuilder.put(key, artifact); + } + } + } + return importBuilder.build(); + } + + /** + * Returns the imports from the .afdo.imports file of a source file. + * + * @param sourceName the source file + */ + private Collection<Artifact> getAutoFdoImports(PathFragment sourceName) { + Preconditions.checkState(isLipoEnabled()); + ImmutableCollection<Artifact> afdoImports = imports.get(sourceName); + Preconditions.checkState(afdoImports != null, + "AutoFDO import data missing for %s", sourceName); + return afdoImports; + } + + /** + * 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<Artifact> getImports(PathFragment objDirectory, PathFragment objectName) { + Preconditions.checkState(isLipoEnabled()); + Preconditions.checkState(imports != null, + "Tried to look up imports of uninitialized FDOSupport"); + PathFragment key = objDirectory.getRelative(FileSystemUtils.removeExtension(objectName)); + ImmutableCollection<Artifact> importsForObject = imports.get(key); + Preconditions.checkState(importsForObject != null, "Import data missing for %s", key); + return importsForObject; + } + + /** + * Configures a compile action builder by adding 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, RuleContext ruleContext, + AnalysisEnvironment env, Label lipoLabel, PathFragment sourceName, final Pattern nocopts, + boolean usePic, LipoContextProvider lipoInputProvider) { + // 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. + Preconditions.checkArgument(lipoInputProvider == null || isLipoEnabled()); + + // FDO is disabled -> do nothing. + if ((fdoInstrument == null) && (fdoRoot == null)) { + return; + } + + List<String> fdoCopts = new ArrayList<>(); + // Instrumentation phase + if (fdoInstrument != null) { + fdoCopts.add("-fprofile-generate=" + fdoInstrument.getPathString()); + if (lipoMode != LipoMode.OFF) { + fdoCopts.add("-fripa"); + } + } + + // Optimization phase + if (fdoRoot != null) { + // Declare dependency on contents of zip file. + if (env.getSkyframeEnv().valuesMissing()) { + return; + } + Iterable<Artifact> auxiliaryInputs = getAuxiliaryInputs( + ruleContext, env, lipoLabel, sourceName, usePic, lipoInputProvider); + builder.addMandatoryInputs(auxiliaryInputs); + if (!Iterables.isEmpty(auxiliaryInputs)) { + if (useAutoFdo) { + fdoCopts.add("-fauto-profile=" + getAutoProfilePath().getPathString()); + } else { + fdoCopts.add("-fprofile-use=" + fdoRootExecPath); + } + fdoCopts.add("-fprofile-correction"); + if (lipoInputProvider != null) { + fdoCopts.add("-fripa"); + } + } + } + Iterable<String> filteredCopts = fdoCopts; + if (nocopts != null) { + // Filter fdoCopts with nocopts if they exist. + filteredCopts = Iterables.filter(fdoCopts, new Predicate<String>() { + @Override + public boolean apply(String copt) { + return !nocopts.matcher(copt).matches(); + } + }); + } + builder.addCopts(0, filteredCopts); + } + + /** + * Returns the auxiliary files that need to be added to the {@link CppCompileAction}. + */ + private Iterable<Artifact> getAuxiliaryInputs( + RuleContext ruleContext, AnalysisEnvironment env, Label lipoLabel, PathFragment sourceName, + 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 (useAutoFdo) { + ImmutableSet.Builder<Artifact> auxiliaryInputs = ImmutableSet.builder(); + + Artifact artifact = env.getDerivedArtifact( + fdoPath.getRelative(getAutoProfileRootRelativePath()), fdoRoot); + env.registerAction(new FdoStubAction(ruleContext.getActionOwner(), artifact)); + auxiliaryInputs.add(artifact); + if (lipoContextProvider != null) { + auxiliaryInputs.addAll(getAutoFdoImports(sourceName)); + } + return auxiliaryInputs.build(); + } else { + ImmutableSet.Builder<Artifact> auxiliaryInputs = ImmutableSet.builder(); + + PathFragment objectName = + FileSystemUtils.replaceExtension(sourceName, usePic ? ".pic.o" : ".o"); + + auxiliaryInputs.addAll( + getGcdaArtifactsForObjectFileName(ruleContext, env, objectName, lipoLabel)); + + if (lipoContextProvider != null) { + for (Artifact importedFile : getImports( + getNonLipoObjDir(ruleContext, lipoLabel), objectName)) { + if (CppFileTypes.COVERAGE_DATA.matches(importedFile.getFilename())) { + Artifact gcdaArtifact = getGcdaArtifactsForGcdaPath( + ruleContext, env, importedFile.getExecPath()); + if (gcdaArtifact == null) { + ruleContext.ruleError(String.format( + ".gcda file %s is not in the FDO zip (referenced by source file %s)", + importedFile.getExecPath(), sourceName)); + } else { + auxiliaryInputs.add(gcdaArtifact); + } + } else { + auxiliaryInputs.add(importedFile); + } + } + } + + 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 PathFragment getAutoProfilePath() { + return fdoRootExecPath.getRelative(getAutoProfileRootRelativePath()); + } + + private PathFragment getAutoProfileRootRelativePath() { + return new PathFragment(fdoProfile.getBaseName()); + } + + /** + * Returns whether LIPO is enabled. + */ + @ThreadSafe + public boolean isLipoEnabled() { + return fdoProfile != null && lipoMode != LipoMode.OFF; + } + + /** + * Returns whether AutoFDO is enabled. + */ + @ThreadSafe + public boolean isAutoFdoEnabled() { + return useAutoFdo; + } + + /** + * Returns an immutable list of command line arguments to add to the linker + * command line. If FDO is disabled, and empty list is returned. + */ + @ThreadSafe + public ImmutableList<String> getLinkOptions() { + return fdoInstrument != null + ? ImmutableList.of("-fprofile-generate=" + fdoInstrument.getPathString()) + : ImmutableList.<String>of(); + } + + /** + * 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; + } + + /** + * Returns the path of the FDO zip containing the .gcda profile files, or null + * if FDO is not enabled. + */ + @VisibleForTesting + public Path getFdoOptimizeProfile() { + return fdoProfile; + } + + /** + * Returns the path fragment of the instrumentation output dir for gcc when + * FDO is enabled, or null if FDO is not enabled. + */ + @ThreadSafe + public PathFragment getFdoInstrument() { + return fdoInstrument; + } + + @VisibleForTesting + public void setGcdaFilesForTesting(ImmutableSet<PathFragment> gcdaFiles) { + this.gcdaFiles = gcdaFiles; + } + + /** + * An exception indicating an issue with FDO coverage files. + */ + public static final class FdoException extends Exception { + FdoException(String message) { + super(message); + } + } +} |