Build support for enabling cross binary FDO optimization.

Crossbinary FDO optimization is a special form of AutoFDO which uses a synthetic profile to optimize targets without any profile. The synthetic profile will often be used as a default profile and will use .xfdo as suffix. It will be passed though option -fdo_optimize just like Autofdo profile. If .xfdo file is passed through -fdo_optimize in the same command line with other types of profiles, .xfdo file will be neglected.

RELNOTES: Build support for enabling cross binary FDO optimization.
PiperOrigin-RevId: 199501260
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCommon.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCommon.java
index 71a05ab..e169c4f 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCommon.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCommon.java
@@ -897,6 +897,7 @@
     boolean isFdo = fdoMode != FdoMode.OFF && toolchain.getCompilationMode() == CompilationMode.OPT;
     if (isFdo
         && fdoMode != FdoMode.AUTO_FDO
+        && fdoMode != FdoMode.XBINARY_FDO
         && !allUnsupportedFeatures.contains(CppRuleClasses.FDO_OPTIMIZE)) {
       allFeatures.add(CppRuleClasses.FDO_OPTIMIZE);
       // For LLVM, support implicit enabling of ThinLTO for FDO unless it has been
@@ -913,6 +914,9 @@
         allFeatures.add(CppRuleClasses.ENABLE_AFDO_THINLTO);
       }
     }
+    if (isFdo && fdoMode == FdoMode.XBINARY_FDO) {
+      allFeatures.add(CppRuleClasses.XBINARYFDO);
+    }
     if (cppConfiguration.getFdoPrefetchHintsLabel() != null) {
       allRequestedFeaturesBuilder.add(CppRuleClasses.FDO_PREFETCH_HINTS);
     }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchain.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchain.java
index 624c847..e2d29fc 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchain.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchain.java
@@ -383,6 +383,7 @@
     FileTypeSet validExtensions =
         FileTypeSet.of(
             CppFileTypes.GCC_AUTO_PROFILE,
+            CppFileTypes.XBINARY_PROFILE,
             CppFileTypes.LLVM_PROFILE,
             CppFileTypes.LLVM_PROFILE_RAW,
             FileType.of(".zip"));
@@ -398,6 +399,8 @@
       fdoMode = FdoMode.AUTO_FDO;
     } else if (isLLVMOptimizedFdo(toolchainInfo.isLLVMCompiler(), fdoZip)) {
       fdoMode = FdoMode.LLVM_FDO;
+    } else if (CppFileTypes.XBINARY_PROFILE.matches(fdoZip.getBaseName())) {
+      fdoMode = FdoMode.XBINARY_FDO;
     } else {
       fdoMode = FdoMode.VANILLA;
     }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppActionConfigs.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppActionConfigs.java
index 36d754e..55be416 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppActionConfigs.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppActionConfigs.java
@@ -419,6 +419,21 @@
                     "  }",
                     "}"),
                 ifTrue(
+                    !existingFeatureNames.contains(CppRuleClasses.XBINARYFDO),
+                    "feature {",
+                    "  name: 'xbinaryfdo'",
+                    "  provides: 'profile'",
+                    "  flag_set {",
+                    "    action: 'c-compile'",
+                    "    action: 'c++-compile'",
+                    "    expand_if_all_available: 'fdo_profile_path'",
+                    "    flag_group {",
+                    "      flag: '-fauto-profile=%{fdo_profile_path}'",
+                    "      flag: '-fprofile-correction'",
+                    "    }",
+                    "  }",
+                    "}"),
+                ifTrue(
                     !existingFeatureNames.contains(CppRuleClasses.AUTOFDO),
                     "feature {",
                     "  name: 'autofdo'",
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppFileTypes.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppFileTypes.java
index 6c31c3a..3c71393 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppFileTypes.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppFileTypes.java
@@ -183,6 +183,7 @@
   public static final FileType COVERAGE_DATA = FileType.of(".gcda");
   public static final FileType COVERAGE_DATA_IMPORTS = FileType.of(".gcda.imports");
   public static final FileType GCC_AUTO_PROFILE = FileType.of(".afdo");
+  public static final FileType XBINARY_PROFILE = FileType.of(".xfdo");
   public static final FileType LLVM_PROFILE = FileType.of(".profdata");
   public static final FileType LLVM_PROFILE_RAW = FileType.of(".profraw");
 
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppHelper.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppHelper.java
index fa2531f..d5617e6 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppHelper.java
@@ -860,6 +860,9 @@
     if (cppConfiguration.isFdo()) {
       return (cppConfiguration.getLipoMode() == LipoMode.BINARY) ? "LIPO" : "FDO";
     }
+    if (fdoSupport.isXBinaryFdoEnabled()) {
+      return "XFDO";
+    }
     return null;
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppOptions.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppOptions.java
index 68009b8b..c5adab6 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppOptions.java
@@ -37,6 +37,7 @@
 import com.google.devtools.common.options.OptionsParsingException;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
 import javax.annotation.Nullable;
@@ -451,14 +452,19 @@
 
   @Option(
     name = "fdo_optimize",
+    allowMultiple = true,
     defaultValue = "null",
     documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS,
     effectTags = {OptionEffectTag.AFFECTS_OUTPUTS},
     help =
         "Use FDO profile information to optimize compilation. Specify the name "
-            + "of the zip file containing the .gcda file tree or an afdo file containing "
-            + "an auto profile. This flag also accepts files specified as labels, for "
-            + "example //foo/bar:file.afdo. Such labels must refer to input files; you may "
+            + "of the zip file containing the .gcda file tree, an afdo file containing "
+            + "an auto profile or an xfdo file containing a default cross binary profile. "
+            + "If the multiple profiles passed through the option include xfdo file and "
+            + "other types of profiles, the last profile other than xfdo file will prevail. "
+            + "If the multiple profiles include only xfdo files or don't include any xfdo file, "
+            + "the last profile will prevail. This flag also accepts files specified as labels, "
+            + "for example //foo/bar:file.afdo. Such labels must refer to input files; you may "
             + "need to add an exports_files directive to the corresponding package to make "
             + "the file visible to Bazel. It also accepts a raw or an indexed LLVM profile file. "
             + "This flag will be superseded by fdo_profile rule."
@@ -468,14 +474,38 @@
    * determines whether these options are actually "active" for this configuration. Instead, use the
    * equivalent getter method, which takes that into account.
    */
-  public String fdoOptimizeForBuild;
+  public List<String> fdoProfiles;
 
   /**
-   * Returns the --fdo_optimize value if FDO is specified and active for this configuration,
-   * the default value otherwise.
+   * Select profile from the list of profiles passed through multiple -fdo_optimize options.
+   */
+  private String selectProfile() {
+    if (fdoProfiles == null) {
+      return null;
+    }
+
+    // Return the last profile in the list that is not a crossbinary profile.
+    String lastXBinaryProfile = null;
+    ListIterator<String> iter = fdoProfiles.listIterator(fdoProfiles.size());
+    while (iter.hasPrevious()) {
+      String profile = iter.previous();
+      if (CppFileTypes.XBINARY_PROFILE.matches(profile)) {
+        lastXBinaryProfile = profile;
+        continue;
+      }
+      return profile;
+    }
+
+    // If crossbinary profile is the only kind of profile in the list, return the last one.
+    return lastXBinaryProfile;
+  }
+
+  /**
+   * Returns the --fdo_optimize value if FDO is specified and active for this
+   * configuration, the default value otherwise.
    */
   public String getFdoOptimize() {
-    return enableLipoSettings() ? fdoOptimizeForBuild : null;
+    return enableLipoSettings() ? selectProfile() : null;
   }
 
   @Option(
@@ -505,9 +535,10 @@
       return false;
     }
 
+    String fdoProfile = selectProfile();
     return lipoModeForBuild != LipoMode.OFF
-        && fdoOptimizeForBuild != null
-        && FdoSupport.isAutoFdo(fdoOptimizeForBuild);
+        && fdoProfile != null
+        && FdoSupport.isAutoFdo(fdoProfile);
   }
 
   @Option(
@@ -972,7 +1003,7 @@
 
     host.useStartEndLib = useStartEndLib;
     host.stripBinaries = StripMode.ALWAYS;
-    host.fdoOptimizeForBuild = null;
+    host.fdoProfiles = null;
     host.fdoProfileLabel = null;
     host.lipoModeForBuild = LipoMode.OFF;
     host.inmemoryDotdFiles = inmemoryDotdFiles;
@@ -1011,7 +1042,7 @@
    * not necessarily active).
    */
   private boolean hasLipoOptimizationState() {
-    return lipoModeForBuild == LipoMode.BINARY && fdoOptimizeForBuild != null
+    return lipoModeForBuild == LipoMode.BINARY && selectProfile() != null
         && lipoContextForBuild != null;
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppRuleClasses.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppRuleClasses.java
index b15ffb5..6aa0c22 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppRuleClasses.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppRuleClasses.java
@@ -386,6 +386,9 @@
    */
   public static final String AUTOFDO = "autofdo";
 
+  /** A string constant for the xbinaryfdo feature. */
+  public static final String XBINARYFDO = "xbinaryfdo";
+
   /**
    * A string constant for the lipo feature.
    */
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/FdoProfileRule.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/FdoProfileRule.java
index 9b213e7..a1437f7 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/FdoProfileRule.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/FdoProfileRule.java
@@ -33,7 +33,8 @@
         /* <!-- #BLAZE_RULE(fdo_profile).ATTRIBUTE(profile) -->
         Label of the FDO profile. The FDO file can have one of the following extensions:
         .profraw for unindexed LLVM profile, .profdata for indexed LLVM profile, .zip
-        that holds GCC gcda profile or LLVM profraw profile, or .afdo for AutoFDO profile.
+        that holds GCC gcda profile or LLVM profraw profile, .afdo for AutoFDO profile,
+        or .xfdo for XBinary profile.
         The label can also point to an fdo_absolute_path_profile rule.
         <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
         .add(
@@ -43,6 +44,7 @@
                         CppFileTypes.LLVM_PROFILE_RAW,
                         CppFileTypes.LLVM_PROFILE,
                         CppFileTypes.GCC_AUTO_PROFILE,
+                        CppFileTypes.XBINARY_PROFILE,
                         FileType.of(".zip")))
                 .singleArtifact())
         /* <!-- #BLAZE_RULE(fdo_profile).ATTRIBUTE(absolute_path_profile) -->
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
index f657c07..ddbe96e 100644
--- 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
@@ -152,6 +152,9 @@
     /** FDO based on automatically collected data. */
     AUTO_FDO,
 
+    /** FDO based on cross binary collected data. */
+    XBINARY_FDO,
+
     /** Instrumentation-based FDO implemented on LLVM. */
     LLVM_FDO,
   }
@@ -371,7 +374,7 @@
       FileSystemUtils.deleteTreesBelow(fdoDirPath);
       FileSystemUtils.createDirectoryAndParents(fdoDirPath);
 
-      if (fdoMode == FdoMode.AUTO_FDO) {
+      if (fdoMode == FdoMode.AUTO_FDO || fdoMode == FdoMode.XBINARY_FDO) {
         if (lipoMode != LipoMode.OFF) {
           imports = readAutoFdoImports(getAutoFdoImportsPath(fdoProfile));
         }
@@ -636,7 +639,8 @@
               ruleContext, sourceName, sourceExecPath, outputName, usePic, fdoSupportProvider);
       builder.addMandatoryInputs(auxiliaryInputs);
       if (!Iterables.isEmpty(auxiliaryInputs)) {
-        if (featureConfiguration.isEnabled(CppRuleClasses.AUTOFDO)) {
+        if (featureConfiguration.isEnabled(CppRuleClasses.AUTOFDO)
+            || featureConfiguration.isEnabled(CppRuleClasses.XBINARYFDO)) {
           variablesBuilder.put(
               CompileBuildVariables.FDO_PROFILE_PATH.getVariableName(),
               getAutoProfilePath(fdoProfile, fdoRootExecPath).getPathString());
@@ -678,7 +682,9 @@
     // If --fdo_optimize was not specified, we don't have any additional inputs.
     if (fdoProfile == null) {
       return auxiliaryInputs.build();
-    } else if (fdoMode == FdoMode.LLVM_FDO || fdoMode == FdoMode.AUTO_FDO) {
+    } else if (fdoMode == FdoMode.LLVM_FDO
+        || fdoMode == FdoMode.AUTO_FDO
+        || fdoMode == FdoMode.XBINARY_FDO) {
       auxiliaryInputs.add(fdoSupportProvider.getProfileArtifact());
       if (lipoContextProvider != null) {
         auxiliaryInputs.addAll(getAutoFdoImports(ruleContext, sourceExecPath, lipoContextProvider));
@@ -795,6 +801,12 @@
     return fdoMode == FdoMode.AUTO_FDO;
   }
 
+  /** Returns whether crossbinary FDO is enabled. */
+  @ThreadSafe
+  public boolean isXBinaryFdoEnabled() {
+    return fdoMode == FdoMode.XBINARY_FDO;
+  }
+
   /**
    * Adds the FDO profile output path to the variable builder. If FDO is disabled, no build variable
    * is added.
@@ -821,7 +833,8 @@
     if (prefetch != null) {
       buildVariables.addStringVariable("fdo_prefetch_hints_path", prefetch.getExecPathString());
     }
-    if (!featureConfiguration.isEnabled(CppRuleClasses.AUTOFDO)) {
+    if (!featureConfiguration.isEnabled(CppRuleClasses.AUTOFDO)
+        && !featureConfiguration.isEnabled(CppRuleClasses.XBINARYFDO)) {
       return new ProfileArtifacts(null, prefetch);
     }
 
diff --git a/src/test/java/com/google/devtools/build/lib/packages/util/MockCcSupport.java b/src/test/java/com/google/devtools/build/lib/packages/util/MockCcSupport.java
index 1b18643..a072dd9 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/util/MockCcSupport.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/util/MockCcSupport.java
@@ -407,6 +407,23 @@
           + "  }"
           + "}";
 
+  public static final String XBINARY_FDO_CONFIGURATION =
+      ""
+          + "feature {"
+          + "  name: 'xbinaryfdo'"
+          + "  provides: 'profile'"
+          + "  flag_set {"
+          + "    action: 'c-compile'"
+          + "    action: 'c++-compile'"
+          + "    action: 'lto-backend'"
+          + "    expand_if_all_available: 'fdo_profile_path'"
+          + "    flag_group {"
+          + "      flag: '-fauto-profile=%{fdo_profile_path}'"
+          + "      flag: '-fprofile-correction'"
+          + "    }"
+          + "  }"
+          + "}";
+
   public static final String FDO_OPTIMIZE_CONFIGURATION =
       ""
           + "feature {"