Introduce FdoHelper

Fdo related logic is complicated enough to have it in a separate class.

https://github.com/bazelbuild/bazel/issues/6516

RELNOTES: None
PiperOrigin-RevId: 240376113
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainProviderHelper.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainProviderHelper.java
index bcce9c6..6a34688 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainProviderHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainProviderHelper.java
@@ -22,11 +22,7 @@
 import com.google.devtools.build.lib.analysis.FileProvider;
 import com.google.devtools.build.lib.analysis.RuleContext;
 import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
-import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
-import com.google.devtools.build.lib.analysis.actions.SpawnAction;
-import com.google.devtools.build.lib.analysis.actions.SymlinkAction;
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
-import com.google.devtools.build.lib.analysis.config.CompilationMode;
 import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
@@ -37,12 +33,8 @@
 import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException;
 import com.google.devtools.build.lib.rules.cpp.CcSkyframeCrosstoolSupportFunction.CcSkyframeCrosstoolSupportException;
 import com.google.devtools.build.lib.rules.cpp.CppConfiguration.Tool;
-import com.google.devtools.build.lib.rules.cpp.FdoContext.BranchFdoMode;
 import com.google.devtools.build.lib.syntax.EvalException;
-import com.google.devtools.build.lib.util.FileType;
-import com.google.devtools.build.lib.util.Pair;
 import com.google.devtools.build.lib.util.StringUtil;
-import com.google.devtools.build.lib.vfs.FileSystemUtils;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.CToolchain;
 import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.CrosstoolRelease;
@@ -72,291 +64,7 @@
   private static final String PACKAGE_START = "%package(";
   private static final String PACKAGE_END = ")%";
 
-  /**
-   * Returns the profile name with the same file name as fdoProfile and an extension that matches
-   * {@link FileType}.
-   */
-  private static String getLLVMProfileFileName(FdoInputFile fdoProfile, FileType type) {
-    if (type.matches(fdoProfile)) {
-      return fdoProfile.getBasename();
-    } else {
-      return FileSystemUtils.removeExtension(fdoProfile.getBasename())
-          + type.getExtensions().get(0);
-    }
-  }
-
-  /**
-   * Resolve the given include directory.
-   *
-   * <p>If it starts with %sysroot%/, that part is replaced with the actual sysroot.
-   *
-   * <p>If it starts with %workspace%/, that part is replaced with the empty string (essentially
-   * making it relative to the build directory).
-   *
-   * <p>If it starts with %crosstool_top%/ or is any relative path, it is interpreted relative to
-   * the crosstool top. The use of assumed-crosstool-relative specifications is considered
-   * deprecated, and all such uses should eventually be replaced by "%crosstool_top%/".
-   *
-   * <p>If it is of the form %package(@repository//my/package)%/folder, then it is interpreted as
-   * the named folder in the appropriate package. All of the normal package syntax is supported. The
-   * /folder part is optional.
-   *
-   * <p>It is illegal if it starts with a % and does not match any of the above forms to avoid
-   * accidentally silently ignoring misspelled prefixes.
-   *
-   * <p>If it is absolute, it remains unchanged.
-   */
-  static PathFragment resolveIncludeDir(
-      String s, PathFragment sysroot, PathFragment crosstoolTopPathFragment)
-      throws InvalidConfigurationException {
-    PathFragment pathPrefix;
-    String pathString;
-    int packageEndIndex = s.indexOf(PACKAGE_END);
-    if (packageEndIndex != -1 && s.startsWith(PACKAGE_START)) {
-      String packageString = s.substring(PACKAGE_START.length(), packageEndIndex);
-      try {
-        pathPrefix = PackageIdentifier.parse(packageString).getSourceRoot();
-      } catch (LabelSyntaxException e) {
-        throw new InvalidConfigurationException("The package '" + packageString + "' is not valid");
-      }
-      int pathStartIndex = packageEndIndex + PACKAGE_END.length();
-      if (pathStartIndex + 1 < s.length()) {
-        if (s.charAt(pathStartIndex) != '/') {
-          throw new InvalidConfigurationException(
-              "The path in the package for '" + s + "' is not valid");
-        }
-        pathString = s.substring(pathStartIndex + 1, s.length());
-      } else {
-        pathString = "";
-      }
-    } else if (s.startsWith(SYSROOT_START)) {
-      if (sysroot == null) {
-        throw new InvalidConfigurationException(
-            "A %sysroot% prefix is only allowed if the " + "default_sysroot option is set");
-      }
-      pathPrefix = sysroot;
-      pathString = s.substring(SYSROOT_START.length(), s.length());
-    } else if (s.startsWith(WORKSPACE_START)) {
-      pathPrefix = PathFragment.EMPTY_FRAGMENT;
-      pathString = s.substring(WORKSPACE_START.length(), s.length());
-    } else {
-      pathPrefix = crosstoolTopPathFragment;
-      if (s.startsWith(CROSSTOOL_START)) {
-        pathString = s.substring(CROSSTOOL_START.length(), s.length());
-      } else if (s.startsWith("%")) {
-        throw new InvalidConfigurationException(
-            "The include path '" + s + "' has an " + "unrecognized %prefix%");
-      } else {
-        pathString = s;
-      }
-    }
-
-    if (!PathFragment.isNormalized(pathString)) {
-      throw new InvalidConfigurationException("The include path '" + s + "' is not normalized.");
-    }
-    PathFragment path = PathFragment.create(pathString);
-    return pathPrefix.getRelative(path);
-  }
-
-  private static String getSkylarkValueForTool(Tool tool, CppToolchainInfo cppToolchainInfo) {
-    PathFragment toolPath = cppToolchainInfo.getToolPathFragment(tool);
-    return toolPath != null ? toolPath.getPathString() : "";
-  }
-
-  private static ImmutableMap<String, Object> getToolchainForSkylark(
-      CppToolchainInfo cppToolchainInfo) {
-    return ImmutableMap.<String, Object>builder()
-        .put("objcopy_executable", getSkylarkValueForTool(Tool.OBJCOPY, cppToolchainInfo))
-        .put("compiler_executable", getSkylarkValueForTool(Tool.GCC, cppToolchainInfo))
-        .put("preprocessor_executable", getSkylarkValueForTool(Tool.CPP, cppToolchainInfo))
-        .put("nm_executable", getSkylarkValueForTool(Tool.NM, cppToolchainInfo))
-        .put("objdump_executable", getSkylarkValueForTool(Tool.OBJDUMP, cppToolchainInfo))
-        .put("ar_executable", getSkylarkValueForTool(Tool.AR, cppToolchainInfo))
-        .put("strip_executable", getSkylarkValueForTool(Tool.STRIP, cppToolchainInfo))
-        .put("ld_executable", getSkylarkValueForTool(Tool.LD, cppToolchainInfo))
-        .build();
-  }
-
-  private static PathFragment calculateSysroot(
-      CcToolchainAttributesProvider attributes, PathFragment defaultSysroot) {
-    TransitiveInfoCollection sysrootTarget = attributes.getLibcTop();
-    if (sysrootTarget == null) {
-      return defaultSysroot;
-    }
-
-    return sysrootTarget.getLabel().getPackageFragment();
-  }
-
-  private static Artifact getPrefetchHintsArtifact(
-      FdoInputFile prefetchHintsFile, RuleContext ruleContext) {
-    if (prefetchHintsFile == null) {
-      return null;
-    }
-    Artifact prefetchHintsArtifact = prefetchHintsFile.getArtifact();
-    if (prefetchHintsArtifact != null) {
-      return prefetchHintsArtifact;
-    }
-
-    prefetchHintsArtifact =
-        ruleContext.getUniqueDirectoryArtifact(
-            "fdo",
-            prefetchHintsFile.getAbsolutePath().getBaseName(),
-            ruleContext.getBinOrGenfilesDirectory());
-    ruleContext.registerAction(
-        SymlinkAction.toAbsolutePath(
-            ruleContext.getActionOwner(),
-            PathFragment.create(prefetchHintsFile.getAbsolutePath().getPathString()),
-            prefetchHintsArtifact,
-            "Symlinking LLVM Cache Prefetch Hints Profile "
-                + prefetchHintsFile.getAbsolutePath().getPathString()));
-    return prefetchHintsArtifact;
-  }
-
-  private static void symlinkTo(
-      RuleContext ruleContext,
-      Artifact symlink,
-      FdoInputFile fdoInputFile,
-      String progressMessage) {
-    if (fdoInputFile.getArtifact() != null) {
-      ruleContext.registerAction(
-          SymlinkAction.toArtifact(
-              ruleContext.getActionOwner(), fdoInputFile.getArtifact(), symlink, progressMessage));
-    } else {
-      ruleContext.registerAction(
-          SymlinkAction.toAbsolutePath(
-              ruleContext.getActionOwner(),
-              fdoInputFile.getAbsolutePath(),
-              symlink,
-              progressMessage));
-    }
-  }
-
-  /*
-   * This function checks the format of the input profile data and converts it to
-   * the indexed format (.profdata) if necessary.
-   */
-  private static Artifact convertLLVMRawProfileToIndexed(
-      CcToolchainAttributesProvider attributes,
-      FdoInputFile fdoProfile,
-      CppToolchainInfo toolchainInfo,
-      RuleContext ruleContext) {
-
-    Artifact profileArtifact =
-        ruleContext.getUniqueDirectoryArtifact(
-            "fdo",
-            getLLVMProfileFileName(fdoProfile, CppFileTypes.LLVM_PROFILE),
-            ruleContext.getBinOrGenfilesDirectory());
-
-    // If the profile file is already in the desired format, symlink to it and return.
-    if (CppFileTypes.LLVM_PROFILE.matches(fdoProfile)) {
-      symlinkTo(
-          ruleContext,
-          profileArtifact,
-          fdoProfile,
-          "Symlinking LLVM Profile " + fdoProfile.getBasename());
-      return profileArtifact;
-    }
-
-    Artifact rawProfileArtifact;
-
-    if (CppFileTypes.LLVM_PROFILE_ZIP.matches(fdoProfile)) {
-      // Get the zipper binary for unzipping the profile.
-      Artifact zipperBinaryArtifact = attributes.getZipper();
-      if (zipperBinaryArtifact == null) {
-        ruleContext.ruleError("Cannot find zipper binary to unzip the profile");
-        return null;
-      }
-
-      // TODO(zhayu): find a way to avoid hard-coding cpu architecture here (b/65582760)
-      String rawProfileFileName = "fdocontrolz_profile.profraw";
-      String cpu = toolchainInfo.getTargetCpu();
-      if (!"k8".equals(cpu)) {
-        rawProfileFileName = "fdocontrolz_profile-" + cpu + ".profraw";
-      }
-      rawProfileArtifact =
-          ruleContext.getUniqueDirectoryArtifact(
-              "fdo", rawProfileFileName, ruleContext.getBinOrGenfilesDirectory());
-
-      // Symlink to the zipped profile file to extract the contents.
-      Artifact zipProfileArtifact =
-          ruleContext.getUniqueDirectoryArtifact(
-              "fdo", fdoProfile.getBasename(), ruleContext.getBinOrGenfilesDirectory());
-      symlinkTo(
-          ruleContext,
-          zipProfileArtifact,
-          fdoProfile,
-          "Symlinking LLVM ZIP Profile " + fdoProfile.getBasename());
-
-      // Unzip the profile.
-      ruleContext.registerAction(
-          new SpawnAction.Builder()
-              .addInput(zipProfileArtifact)
-              .addInput(zipperBinaryArtifact)
-              .addOutput(rawProfileArtifact)
-              .useDefaultShellEnvironment()
-              .setExecutable(zipperBinaryArtifact)
-              .setProgressMessage(
-                  "LLVMUnzipProfileAction: Generating %s", rawProfileArtifact.prettyPrint())
-              .setMnemonic("LLVMUnzipProfileAction")
-              .addCommandLine(
-                  CustomCommandLine.builder()
-                      .addExecPath("xf", zipProfileArtifact)
-                      .add(
-                          "-d",
-                          rawProfileArtifact.getExecPath().getParentDirectory().getSafePathString())
-                      .build())
-              .build(ruleContext));
-    } else {
-      rawProfileArtifact =
-          ruleContext.getUniqueDirectoryArtifact(
-              "fdo",
-              getLLVMProfileFileName(fdoProfile, CppFileTypes.LLVM_PROFILE_RAW),
-              ruleContext.getBinOrGenfilesDirectory());
-      symlinkTo(
-          ruleContext,
-          rawProfileArtifact,
-          fdoProfile,
-          "Symlinking LLVM Raw Profile " + fdoProfile.getBasename());
-    }
-
-    if (toolchainInfo.getToolPathFragment(Tool.LLVM_PROFDATA) == null) {
-      ruleContext.ruleError(
-          "llvm-profdata not available with this crosstool, needed for profile conversion");
-      return null;
-    }
-
-    // Convert LLVM raw profile to indexed format.
-    ruleContext.registerAction(
-        new SpawnAction.Builder()
-            .addInput(rawProfileArtifact)
-            .addTransitiveInputs(attributes.getAllFilesMiddleman())
-            .addOutput(profileArtifact)
-            .useDefaultShellEnvironment()
-            .setExecutable(toolchainInfo.getToolPathFragment(Tool.LLVM_PROFDATA))
-            .setProgressMessage("LLVMProfDataAction: Generating %s", profileArtifact.prettyPrint())
-            .setMnemonic("LLVMProfDataAction")
-            .addCommandLine(
-                CustomCommandLine.builder()
-                    .add("merge")
-                    .add("-o")
-                    .addExecPath(profileArtifact)
-                    .addExecPath(rawProfileArtifact)
-                    .build())
-            .build(ruleContext));
-
-    return profileArtifact;
-  }
-
-  static Pair<FdoInputFile, Artifact> getFdoInputs(
-      RuleContext ruleContext, FdoProfileProvider fdoProfileProvider) {
-    if (fdoProfileProvider == null) {
-      ruleContext.ruleError("--fdo_profile/--xbinary_fdo input needs to be an fdo_profile rule");
-      return null;
-    }
-    return Pair.of(fdoProfileProvider.getInputFile(), fdoProfileProvider.getProtoProfileArtifact());
-  }
-
-  static CcToolchainProvider getCcToolchainProvider(
+  public static CcToolchainProvider getCcToolchainProvider(
       RuleContext ruleContext,
       CcToolchainAttributesProvider attributes,
       CrosstoolRelease crosstoolFromCcToolchainSuiteProtoAttribute)
@@ -365,56 +73,6 @@
     CppConfiguration cppConfiguration =
         Preconditions.checkNotNull(configuration.getFragment(CppConfiguration.class));
 
-    PathFragment fdoZip = null;
-    FdoInputFile fdoInputFile = null;
-    FdoInputFile prefetchHints = null;
-    Artifact protoProfileArtifact = null;
-    Pair<FdoInputFile, Artifact> fdoInputs = null;
-    if (configuration.getCompilationMode() == CompilationMode.OPT) {
-      if (cppConfiguration.getFdoPrefetchHintsLabel() != null) {
-        FdoPrefetchHintsProvider provider = attributes.getFdoPrefetch();
-        prefetchHints = provider.getInputFile();
-      }
-      if (cppConfiguration.getFdoPath() != null) {
-        fdoZip = cppConfiguration.getFdoPath();
-      } else if (cppConfiguration.getFdoOptimizeLabel() != null) {
-        FdoProfileProvider fdoProfileProvider = attributes.getFdoOptimizeProvider();
-        if (fdoProfileProvider != null) {
-          fdoInputs = getFdoInputs(ruleContext, fdoProfileProvider);
-        } else {
-          fdoInputFile = fdoInputFileFromArtifacts(ruleContext, attributes);
-        }
-      } else if (cppConfiguration.getFdoProfileLabel() != null) {
-        fdoInputs = getFdoInputs(ruleContext, attributes.getFdoProfileProvider());
-      } else if (cppConfiguration.getXFdoProfileLabel() != null) {
-        fdoInputs = getFdoInputs(ruleContext, attributes.getXFdoProfileProvider());
-      }
-    }
-
-    if (ruleContext.hasErrors()) {
-      return null;
-    }
-
-    if (fdoInputs != null) {
-      fdoInputFile = fdoInputs.getFirst();
-      protoProfileArtifact = fdoInputs.getSecond();
-    }
-
-    if (fdoZip != null) {
-      SkyKey fdoKey = CcSkyframeFdoSupportValue.key(fdoZip);
-      SkyFunction.Environment skyframeEnv = ruleContext.getAnalysisEnvironment().getSkyframeEnv();
-      CcSkyframeFdoSupportValue ccSkyframeFdoSupportValue =
-          (CcSkyframeFdoSupportValue) skyframeEnv.getValue(fdoKey);
-      if (skyframeEnv.valuesMissing()) {
-        return null;
-      }
-      // fdoZip should be set if the profile is a path, fdoInputFile if it is an artifact, but
-      // never both
-      Preconditions.checkState(fdoInputFile == null);
-      fdoInputFile =
-          FdoInputFile.fromAbsolutePath(ccSkyframeFdoSupportValue.getFdoZipPath().asFragment());
-    }
-
     CToolchain toolchain = null;
     CrosstoolRelease crosstoolFromCrosstoolFile = null;
 
@@ -461,53 +119,11 @@
             toolchain,
             crosstoolFromCcToolchainSuiteProtoAttribute);
 
-    FdoContext.BranchFdoProfile branchFdoProfile = null;
-    if (fdoInputFile != null) {
-      BranchFdoMode branchFdoMode;
-      if (CppFileTypes.GCC_AUTO_PROFILE.matches(fdoInputFile)) {
-        branchFdoMode = BranchFdoMode.AUTO_FDO;
-      } else if (CppFileTypes.XBINARY_PROFILE.matches(fdoInputFile)) {
-        branchFdoMode = BranchFdoMode.XBINARY_FDO;
-      } else if (CppFileTypes.LLVM_PROFILE.matches(fdoInputFile)) {
-        branchFdoMode = BranchFdoMode.LLVM_FDO;
-      } else if (CppFileTypes.LLVM_PROFILE_RAW.matches(fdoInputFile)) {
-        branchFdoMode = BranchFdoMode.LLVM_FDO;
-      } else if (CppFileTypes.LLVM_PROFILE_ZIP.matches(fdoInputFile)) {
-        branchFdoMode = BranchFdoMode.LLVM_FDO;
-      } else {
-        ruleContext.ruleError("invalid extension for FDO profile file.");
-        return null;
-      }
-      if (branchFdoMode != BranchFdoMode.XBINARY_FDO
-          && cppConfiguration.getXFdoProfileLabel() != null) {
-        ruleContext.throwWithRuleError(
-            "--xbinary_fdo cannot accept profile input other than *.xfdo");
-      }
-
-      if (configuration.isCodeCoverageEnabled()) {
-        ruleContext.throwWithRuleError("coverage mode is not compatible with FDO optimization");
-      }
-      // This tries to convert LLVM profiles to the indexed format if necessary.
-      Artifact profileArtifact = null;
-      if (branchFdoMode == BranchFdoMode.LLVM_FDO) {
-        profileArtifact =
-            convertLLVMRawProfileToIndexed(attributes, fdoInputFile, toolchainInfo, ruleContext);
-        if (ruleContext.hasErrors()) {
-          return null;
-        }
-      } else if (branchFdoMode == BranchFdoMode.AUTO_FDO
-          || branchFdoMode == BranchFdoMode.XBINARY_FDO) {
-        profileArtifact =
-            ruleContext.getUniqueDirectoryArtifact(
-                "fdo", fdoInputFile.getBasename(), ruleContext.getBinOrGenfilesDirectory());
-        symlinkTo(
-            ruleContext,
-            profileArtifact,
-            fdoInputFile,
-            "Symlinking FDO profile " + fdoInputFile.getBasename());
-      }
-      branchFdoProfile =
-          new FdoContext.BranchFdoProfile(branchFdoMode, profileArtifact, protoProfileArtifact);
+    FdoContext fdoContext =
+        FdoHelper.getFdoContext(
+            ruleContext, attributes, configuration, cppConfiguration, toolchainInfo);
+    if (fdoContext == null) {
+      return null;
     }
 
     String purposePrefix = attributes.getPurposePrefix();
@@ -610,8 +226,6 @@
     ImmutableList<PathFragment> builtInIncludeDirectories =
         builtInIncludeDirectoriesBuilder.build();
 
-    Artifact prefetchHintsArtifact = getPrefetchHintsArtifact(prefetchHints, ruleContext);
-
     return new CcToolchainProvider(
         getToolchainForSkylark(toolchainInfo),
         cppConfiguration,
@@ -647,36 +261,111 @@
         attributes.getLinkDynamicLibraryTool(),
         builtInIncludeDirectories,
         sysroot,
-        new FdoContext(branchFdoProfile, prefetchHintsArtifact),
+        fdoContext,
         configuration.isHostConfiguration(),
         attributes.getLicensesProvider());
   }
 
-  private static FdoInputFile fdoInputFileFromArtifacts(
-      RuleContext ruleContext, CcToolchainAttributesProvider attributes) {
-    ImmutableList<Artifact> fdoArtifacts = attributes.getFdoOptimizeArtifacts();
-    if (fdoArtifacts.size() != 1) {
-      ruleContext.ruleError("--fdo_optimize does not point to a single target");
-      return null;
+  /**
+   * Resolve the given include directory.
+   *
+   * <p>If it starts with %sysroot%/, that part is replaced with the actual sysroot.
+   *
+   * <p>If it starts with %workspace%/, that part is replaced with the empty string (essentially
+   * making it relative to the build directory).
+   *
+   * <p>If it starts with %crosstool_top%/ or is any relative path, it is interpreted relative to
+   * the crosstool top. The use of assumed-crosstool-relative specifications is considered
+   * deprecated, and all such uses should eventually be replaced by "%crosstool_top%/".
+   *
+   * <p>If it is of the form %package(@repository//my/package)%/folder, then it is interpreted as
+   * the named folder in the appropriate package. All of the normal package syntax is supported. The
+   * /folder part is optional.
+   *
+   * <p>It is illegal if it starts with a % and does not match any of the above forms to avoid
+   * accidentally silently ignoring misspelled prefixes.
+   *
+   * <p>If it is absolute, it remains unchanged.
+   */
+  static PathFragment resolveIncludeDir(
+      String s, PathFragment sysroot, PathFragment crosstoolTopPathFragment)
+      throws InvalidConfigurationException {
+    PathFragment pathPrefix;
+    String pathString;
+    int packageEndIndex = s.indexOf(PACKAGE_END);
+    if (packageEndIndex != -1 && s.startsWith(PACKAGE_START)) {
+      String packageString = s.substring(PACKAGE_START.length(), packageEndIndex);
+      try {
+        pathPrefix = PackageIdentifier.parse(packageString).getSourceRoot();
+      } catch (LabelSyntaxException e) {
+        throw new InvalidConfigurationException("The package '" + packageString + "' is not valid");
+      }
+      int pathStartIndex = packageEndIndex + PACKAGE_END.length();
+      if (pathStartIndex + 1 < s.length()) {
+        if (s.charAt(pathStartIndex) != '/') {
+          throw new InvalidConfigurationException(
+              "The path in the package for '" + s + "' is not valid");
+        }
+        pathString = s.substring(pathStartIndex + 1, s.length());
+      } else {
+        pathString = "";
+      }
+    } else if (s.startsWith(SYSROOT_START)) {
+      if (sysroot == null) {
+        throw new InvalidConfigurationException(
+            "A %sysroot% prefix is only allowed if the " + "default_sysroot option is set");
+      }
+      pathPrefix = sysroot;
+      pathString = s.substring(SYSROOT_START.length(), s.length());
+    } else if (s.startsWith(WORKSPACE_START)) {
+      pathPrefix = PathFragment.EMPTY_FRAGMENT;
+      pathString = s.substring(WORKSPACE_START.length(), s.length());
+    } else {
+      pathPrefix = crosstoolTopPathFragment;
+      if (s.startsWith(CROSSTOOL_START)) {
+        pathString = s.substring(CROSSTOOL_START.length(), s.length());
+      } else if (s.startsWith("%")) {
+        throw new InvalidConfigurationException(
+            "The include path '" + s + "' has an " + "unrecognized %prefix%");
+      } else {
+        pathString = s;
+      }
     }
 
-    Artifact fdoArtifact = fdoArtifacts.get(0);
-    if (!fdoArtifact.isSourceArtifact()) {
-      ruleContext.ruleError("--fdo_optimize points to a target that is not an input file");
-      return null;
+    if (!PathFragment.isNormalized(pathString)) {
+      throw new InvalidConfigurationException("The include path '" + s + "' is not normalized.");
+    }
+    PathFragment path = PathFragment.create(pathString);
+    return pathPrefix.getRelative(path);
+  }
+
+  private static String getSkylarkValueForTool(Tool tool, CppToolchainInfo cppToolchainInfo) {
+    PathFragment toolPath = cppToolchainInfo.getToolPathFragment(tool);
+    return toolPath != null ? toolPath.getPathString() : "";
+  }
+
+  private static ImmutableMap<String, Object> getToolchainForSkylark(
+      CppToolchainInfo cppToolchainInfo) {
+    return ImmutableMap.<String, Object>builder()
+        .put("objcopy_executable", getSkylarkValueForTool(Tool.OBJCOPY, cppToolchainInfo))
+        .put("compiler_executable", getSkylarkValueForTool(Tool.GCC, cppToolchainInfo))
+        .put("preprocessor_executable", getSkylarkValueForTool(Tool.CPP, cppToolchainInfo))
+        .put("nm_executable", getSkylarkValueForTool(Tool.NM, cppToolchainInfo))
+        .put("objdump_executable", getSkylarkValueForTool(Tool.OBJDUMP, cppToolchainInfo))
+        .put("ar_executable", getSkylarkValueForTool(Tool.AR, cppToolchainInfo))
+        .put("strip_executable", getSkylarkValueForTool(Tool.STRIP, cppToolchainInfo))
+        .put("ld_executable", getSkylarkValueForTool(Tool.LD, cppToolchainInfo))
+        .build();
+  }
+
+  private static PathFragment calculateSysroot(
+      CcToolchainAttributesProvider attributes, PathFragment defaultSysroot) {
+    TransitiveInfoCollection sysrootTarget = attributes.getLibcTop();
+    if (sysrootTarget == null) {
+      return defaultSysroot;
     }
 
-    Label fdoLabel = attributes.getFdoOptimize().getLabel();
-    if (!fdoLabel
-        .getPackageIdentifier()
-        .getPathUnderExecRoot()
-        .getRelative(fdoLabel.getName())
-        .equals(fdoArtifact.getExecPath())) {
-      ruleContext.ruleError("--fdo_optimize points to a target that is not an input file");
-      return null;
-    }
-
-    return FdoInputFile.fromArtifact(fdoArtifact);
+    return sysrootTarget.getLabel().getPackageFragment();
   }
 
   /** Finds an appropriate {@link CppToolchainInfo} for this target. */
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/FdoHelper.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/FdoHelper.java
new file mode 100644
index 0000000..9e5eec4
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/FdoHelper.java
@@ -0,0 +1,352 @@
+// Copyright 2018 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.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
+import com.google.devtools.build.lib.analysis.actions.SpawnAction;
+import com.google.devtools.build.lib.analysis.actions.SymlinkAction;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.config.CompilationMode;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException;
+import com.google.devtools.build.lib.rules.cpp.CppConfiguration.Tool;
+import com.google.devtools.build.lib.rules.cpp.FdoContext.BranchFdoMode;
+import com.google.devtools.build.lib.util.FileType;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.skyframe.SkyFunction;
+import com.google.devtools.build.skyframe.SkyKey;
+
+/** Helper responsible for creating {@link FdoContext} */
+public class FdoHelper {
+
+  public static FdoContext getFdoContext(
+      RuleContext ruleContext,
+      CcToolchainAttributesProvider attributes,
+      BuildConfiguration configuration,
+      CppConfiguration cppConfiguration,
+      CppToolchainInfo toolchainInfo)
+      throws InterruptedException, RuleErrorException {
+    FdoInputFile fdoInputFile = null;
+    FdoInputFile prefetchHints = null;
+    Artifact protoProfileArtifact = null;
+    Pair<FdoInputFile, Artifact> fdoInputs = null;
+    if (configuration.getCompilationMode() == CompilationMode.OPT) {
+      if (cppConfiguration.getFdoPrefetchHintsLabel() != null) {
+        FdoPrefetchHintsProvider provider = attributes.getFdoPrefetch();
+        prefetchHints = provider.getInputFile();
+      }
+      if (cppConfiguration.getFdoPath() != null) {
+        PathFragment fdoZip = cppConfiguration.getFdoPath();
+        SkyKey fdoKey = CcSkyframeFdoSupportValue.key(fdoZip);
+        SkyFunction.Environment skyframeEnv = ruleContext.getAnalysisEnvironment().getSkyframeEnv();
+        CcSkyframeFdoSupportValue ccSkyframeFdoSupportValue =
+            (CcSkyframeFdoSupportValue) skyframeEnv.getValue(fdoKey);
+        if (skyframeEnv.valuesMissing()) {
+          return null;
+        }
+        // fdoZip should be set if the profile is a path, fdoInputFile if it is an artifact, but
+        // never both
+        Preconditions.checkState(fdoInputFile == null);
+        fdoInputFile =
+            FdoInputFile.fromAbsolutePath(ccSkyframeFdoSupportValue.getFdoZipPath().asFragment());
+      } else if (cppConfiguration.getFdoOptimizeLabel() != null) {
+        FdoProfileProvider fdoProfileProvider = attributes.getFdoOptimizeProvider();
+        if (fdoProfileProvider != null) {
+          fdoInputs = getFdoInputs(ruleContext, fdoProfileProvider);
+        } else {
+          fdoInputFile = fdoInputFileFromArtifacts(ruleContext, attributes);
+        }
+      } else if (cppConfiguration.getFdoProfileLabel() != null) {
+        fdoInputs = getFdoInputs(ruleContext, attributes.getFdoProfileProvider());
+      } else if (cppConfiguration.getXFdoProfileLabel() != null) {
+        fdoInputs = getFdoInputs(ruleContext, attributes.getXFdoProfileProvider());
+      }
+    }
+
+    if (ruleContext.hasErrors()) {
+      return null;
+    }
+
+    if (fdoInputs != null) {
+      fdoInputFile = fdoInputs.getFirst();
+      protoProfileArtifact = fdoInputs.getSecond();
+    }
+
+    FdoContext.BranchFdoProfile branchFdoProfile = null;
+    if (fdoInputFile != null) {
+      BranchFdoMode branchFdoMode;
+      if (CppFileTypes.GCC_AUTO_PROFILE.matches(fdoInputFile)) {
+        branchFdoMode = BranchFdoMode.AUTO_FDO;
+      } else if (CppFileTypes.XBINARY_PROFILE.matches(fdoInputFile)) {
+        branchFdoMode = BranchFdoMode.XBINARY_FDO;
+      } else if (CppFileTypes.LLVM_PROFILE.matches(fdoInputFile)) {
+        branchFdoMode = BranchFdoMode.LLVM_FDO;
+      } else if (CppFileTypes.LLVM_PROFILE_RAW.matches(fdoInputFile)) {
+        branchFdoMode = BranchFdoMode.LLVM_FDO;
+      } else if (CppFileTypes.LLVM_PROFILE_ZIP.matches(fdoInputFile)) {
+        branchFdoMode = BranchFdoMode.LLVM_FDO;
+      } else {
+        ruleContext.ruleError("invalid extension for FDO profile file.");
+        return null;
+      }
+      if (branchFdoMode != BranchFdoMode.XBINARY_FDO
+          && cppConfiguration.getXFdoProfileLabel() != null) {
+        ruleContext.throwWithRuleError(
+            "--xbinary_fdo cannot accept profile input other than *.xfdo");
+      }
+
+      if (configuration.isCodeCoverageEnabled()) {
+        ruleContext.throwWithRuleError("coverage mode is not compatible with FDO optimization");
+      }
+      // This tries to convert LLVM profiles to the indexed format if necessary.
+      Artifact profileArtifact = null;
+      if (branchFdoMode == BranchFdoMode.LLVM_FDO) {
+        profileArtifact =
+            convertLLVMRawProfileToIndexed(attributes, fdoInputFile, toolchainInfo, ruleContext);
+        if (ruleContext.hasErrors()) {
+          return null;
+        }
+      } else if (branchFdoMode == BranchFdoMode.AUTO_FDO
+          || branchFdoMode == BranchFdoMode.XBINARY_FDO) {
+        profileArtifact =
+            ruleContext.getUniqueDirectoryArtifact(
+                "fdo", fdoInputFile.getBasename(), ruleContext.getBinOrGenfilesDirectory());
+        symlinkTo(
+            ruleContext,
+            profileArtifact,
+            fdoInputFile,
+            "Symlinking FDO profile " + fdoInputFile.getBasename());
+      }
+      branchFdoProfile =
+          new FdoContext.BranchFdoProfile(branchFdoMode, profileArtifact, protoProfileArtifact);
+    }
+    Artifact prefetchHintsArtifact = getPrefetchHintsArtifact(prefetchHints, ruleContext);
+    return new FdoContext(branchFdoProfile, prefetchHintsArtifact);
+  }
+
+  /**
+   * Returns the profile name with the same file name as fdoProfile and an extension that matches
+   * {@link FileType}.
+   */
+  private static String getLLVMProfileFileName(FdoInputFile fdoProfile, FileType type) {
+    if (type.matches(fdoProfile)) {
+      return fdoProfile.getBasename();
+    } else {
+      return FileSystemUtils.removeExtension(fdoProfile.getBasename())
+          + type.getExtensions().get(0);
+    }
+  }
+
+  private static Artifact getPrefetchHintsArtifact(
+      FdoInputFile prefetchHintsFile, RuleContext ruleContext) {
+    if (prefetchHintsFile == null) {
+      return null;
+    }
+    Artifact prefetchHintsArtifact = prefetchHintsFile.getArtifact();
+    if (prefetchHintsArtifact != null) {
+      return prefetchHintsArtifact;
+    }
+
+    prefetchHintsArtifact =
+        ruleContext.getUniqueDirectoryArtifact(
+            "fdo",
+            prefetchHintsFile.getAbsolutePath().getBaseName(),
+            ruleContext.getBinOrGenfilesDirectory());
+    ruleContext.registerAction(
+        SymlinkAction.toAbsolutePath(
+            ruleContext.getActionOwner(),
+            PathFragment.create(prefetchHintsFile.getAbsolutePath().getPathString()),
+            prefetchHintsArtifact,
+            "Symlinking LLVM Cache Prefetch Hints Profile "
+                + prefetchHintsFile.getAbsolutePath().getPathString()));
+    return prefetchHintsArtifact;
+  }
+
+  private static void symlinkTo(
+      RuleContext ruleContext,
+      Artifact symlink,
+      FdoInputFile fdoInputFile,
+      String progressMessage) {
+    if (fdoInputFile.getArtifact() != null) {
+      ruleContext.registerAction(
+          SymlinkAction.toArtifact(
+              ruleContext.getActionOwner(), fdoInputFile.getArtifact(), symlink, progressMessage));
+    } else {
+      ruleContext.registerAction(
+          SymlinkAction.toAbsolutePath(
+              ruleContext.getActionOwner(),
+              fdoInputFile.getAbsolutePath(),
+              symlink,
+              progressMessage));
+    }
+  }
+
+  /*
+   * This function checks the format of the input profile data and converts it to
+   * the indexed format (.profdata) if necessary.
+   */
+  private static Artifact convertLLVMRawProfileToIndexed(
+      CcToolchainAttributesProvider attributes,
+      FdoInputFile fdoProfile,
+      CppToolchainInfo toolchainInfo,
+      RuleContext ruleContext) {
+
+    Artifact profileArtifact =
+        ruleContext.getUniqueDirectoryArtifact(
+            "fdo",
+            getLLVMProfileFileName(fdoProfile, CppFileTypes.LLVM_PROFILE),
+            ruleContext.getBinOrGenfilesDirectory());
+
+    // If the profile file is already in the desired format, symlink to it and return.
+    if (CppFileTypes.LLVM_PROFILE.matches(fdoProfile)) {
+      symlinkTo(
+          ruleContext,
+          profileArtifact,
+          fdoProfile,
+          "Symlinking LLVM Profile " + fdoProfile.getBasename());
+      return profileArtifact;
+    }
+
+    Artifact rawProfileArtifact;
+
+    if (CppFileTypes.LLVM_PROFILE_ZIP.matches(fdoProfile)) {
+      // Get the zipper binary for unzipping the profile.
+      Artifact zipperBinaryArtifact = attributes.getZipper();
+      if (zipperBinaryArtifact == null) {
+        ruleContext.ruleError("Cannot find zipper binary to unzip the profile");
+        return null;
+      }
+
+      // TODO(zhayu): find a way to avoid hard-coding cpu architecture here (b/65582760)
+      String rawProfileFileName = "fdocontrolz_profile.profraw";
+      String cpu = toolchainInfo.getTargetCpu();
+      if (!"k8".equals(cpu)) {
+        rawProfileFileName = "fdocontrolz_profile-" + cpu + ".profraw";
+      }
+      rawProfileArtifact =
+          ruleContext.getUniqueDirectoryArtifact(
+              "fdo", rawProfileFileName, ruleContext.getBinOrGenfilesDirectory());
+
+      // Symlink to the zipped profile file to extract the contents.
+      Artifact zipProfileArtifact =
+          ruleContext.getUniqueDirectoryArtifact(
+              "fdo", fdoProfile.getBasename(), ruleContext.getBinOrGenfilesDirectory());
+      symlinkTo(
+          ruleContext,
+          zipProfileArtifact,
+          fdoProfile,
+          "Symlinking LLVM ZIP Profile " + fdoProfile.getBasename());
+
+      // Unzip the profile.
+      ruleContext.registerAction(
+          new SpawnAction.Builder()
+              .addInput(zipProfileArtifact)
+              .addInput(zipperBinaryArtifact)
+              .addOutput(rawProfileArtifact)
+              .useDefaultShellEnvironment()
+              .setExecutable(zipperBinaryArtifact)
+              .setProgressMessage(
+                  "LLVMUnzipProfileAction: Generating %s", rawProfileArtifact.prettyPrint())
+              .setMnemonic("LLVMUnzipProfileAction")
+              .addCommandLine(
+                  CustomCommandLine.builder()
+                      .addExecPath("xf", zipProfileArtifact)
+                      .add(
+                          "-d",
+                          rawProfileArtifact.getExecPath().getParentDirectory().getSafePathString())
+                      .build())
+              .build(ruleContext));
+    } else {
+      rawProfileArtifact =
+          ruleContext.getUniqueDirectoryArtifact(
+              "fdo",
+              getLLVMProfileFileName(fdoProfile, CppFileTypes.LLVM_PROFILE_RAW),
+              ruleContext.getBinOrGenfilesDirectory());
+      symlinkTo(
+          ruleContext,
+          rawProfileArtifact,
+          fdoProfile,
+          "Symlinking LLVM Raw Profile " + fdoProfile.getBasename());
+    }
+
+    if (toolchainInfo.getToolPathFragment(Tool.LLVM_PROFDATA) == null) {
+      ruleContext.ruleError(
+          "llvm-profdata not available with this crosstool, needed for profile conversion");
+      return null;
+    }
+
+    // Convert LLVM raw profile to indexed format.
+    ruleContext.registerAction(
+        new SpawnAction.Builder()
+            .addInput(rawProfileArtifact)
+            .addTransitiveInputs(attributes.getAllFilesMiddleman())
+            .addOutput(profileArtifact)
+            .useDefaultShellEnvironment()
+            .setExecutable(toolchainInfo.getToolPathFragment(Tool.LLVM_PROFDATA))
+            .setProgressMessage("LLVMProfDataAction: Generating %s", profileArtifact.prettyPrint())
+            .setMnemonic("LLVMProfDataAction")
+            .addCommandLine(
+                CustomCommandLine.builder()
+                    .add("merge")
+                    .add("-o")
+                    .addExecPath(profileArtifact)
+                    .addExecPath(rawProfileArtifact)
+                    .build())
+            .build(ruleContext));
+
+    return profileArtifact;
+  }
+
+  static Pair<FdoInputFile, Artifact> getFdoInputs(
+      RuleContext ruleContext, FdoProfileProvider fdoProfileProvider) {
+    if (fdoProfileProvider == null) {
+      ruleContext.ruleError("--fdo_profile/--xbinary_fdo input needs to be an fdo_profile rule");
+      return null;
+    }
+    return Pair.of(fdoProfileProvider.getInputFile(), fdoProfileProvider.getProtoProfileArtifact());
+  }
+
+  private static FdoInputFile fdoInputFileFromArtifacts(
+      RuleContext ruleContext, CcToolchainAttributesProvider attributes) {
+    ImmutableList<Artifact> fdoArtifacts = attributes.getFdoOptimizeArtifacts();
+    if (fdoArtifacts.size() != 1) {
+      ruleContext.ruleError("--fdo_optimize does not point to a single target");
+      return null;
+    }
+
+    Artifact fdoArtifact = fdoArtifacts.get(0);
+    if (!fdoArtifact.isSourceArtifact()) {
+      ruleContext.ruleError("--fdo_optimize points to a target that is not an input file");
+      return null;
+    }
+
+    Label fdoLabel = attributes.getFdoOptimize().getLabel();
+    if (!fdoLabel
+        .getPackageIdentifier()
+        .getPathUnderExecRoot()
+        .getRelative(fdoLabel.getName())
+        .equals(fdoArtifact.getExecPath())) {
+      ruleContext.ruleError("--fdo_optimize points to a target that is not an input file");
+      return null;
+    }
+
+    return FdoInputFile.fromArtifact(fdoArtifact);
+  }
+}