[llvm] [SampleFDO] Stale profile call-graph matching (PR #92151)

Lei Wang via llvm-commits llvm-commits at lists.llvm.org
Mon Jun 3 18:23:38 PDT 2024


================
@@ -590,14 +617,318 @@ void SampleProfileMatcher::computeAndReportProfileStaleness() {
   }
 }
 
-void SampleProfileMatcher::runOnModule() {
-  ProfileConverter::flattenProfile(Reader.getProfiles(), FlattenedProfiles,
-                                   FunctionSamples::ProfileIsCS);
+void SampleProfileMatcher::findNewIRFunctions(
+    StringMap<Function *> &NewIRFunctions) {
+  // TODO: Support MD5 profile.
+  if (FunctionSamples::UseMD5)
+    return;
+  StringSet<> NamesInProfile;
+  if (auto NameTable = Reader.getNameTable()) {
+    for (auto Name : *NameTable)
+      NamesInProfile.insert(Name.stringRef());
+  }
+
   for (auto &F : M) {
-    if (skipProfileForFunction(F))
+    // Skip declarations, as even if the function can be recognized renamed, we
+    // have nothing to do with it.
+    if (F.isDeclaration())
       continue;
-    runOnFunction(F);
+
+    StringRef CanonFName = FunctionSamples::getCanonicalFnName(F.getName());
+    const auto *FS = getFlattenedSamplesFor(F);
+    if (FS)
+      continue;
+
+    // For extended binary, functions are fully inlined may not be loaded in the
+    // top-level profile, so check the NameTable which has the all symbol names
+    // in profile.
+    if (NamesInProfile.count(CanonFName))
+      continue;
+
+    // For extended binary, non-profiled function symbols are in the profile
+    // symbol list table.
+    if (PSL && PSL->contains(CanonFName))
+      continue;
+
+    LLVM_DEBUG(dbgs() << "Function " << CanonFName
+                      << " is not in profile or symbol list table.\n");
+    NewIRFunctions[CanonFName] = &F;
   }
+}
+
+void SampleProfileMatcher::findNewIRCallees(
+    Function &Caller, const StringMap<Function *> &NewIRFunctions,
+    std::vector<Function *> &NewIRCallees) {
+  for (auto &BB : Caller) {
+    for (auto &I : BB) {
+      const auto *CB = dyn_cast<CallBase>(&I);
+      if (!CB || isa<IntrinsicInst>(&I))
+        continue;
+      Function *Callee = CB->getCalledFunction();
+      if (!Callee || Callee->isDeclaration())
+        continue;
+      StringRef CalleeName =
+          FunctionSamples::getCanonicalFnName(Callee->getName());
+      if (NewIRFunctions.count(CalleeName))
+        NewIRCallees.push_back(Callee);
+    }
+  }
+}
+
+std::pair<Function *, SampleProfileMatcher::MatchState>
+SampleProfileMatcher::findFunction(
+    const FunctionId &ProfFunc,
+    const FunctionMap &OldProfToNewSymbolMap) const {
+  auto F = SymbolMap->find(ProfFunc);
+  if (F != SymbolMap->end())
+    return {F->second, MatchState::InitialMatch};
+
+  // Existing matched function is found.
+  auto NewF = OldProfToNewSymbolMap.find(ProfFunc);
+  if (NewF != OldProfToNewSymbolMap.end())
+    return {NewF->second, MatchState::RecoveredMismatch};
+  return {nullptr, MatchState::Unknown};
+}
+
+std::pair<Function *, SampleProfileMatcher::MatchState>
+SampleProfileMatcher::findOrMatchFunction(
+    const FunctionId &ProfFunc, FunctionMap &OldProfToNewSymbolMap,
+    const std::vector<Function *> &NewIRCallees) {
+  auto R = findFunction(ProfFunc, OldProfToNewSymbolMap);
+  // We need to check the match state instead of nullptr function because the
+  // returned function can be nullptr even if it's found in the symbol map.
+  if (R.second != MatchState::Unknown)
+    return R;
+
+  for (auto *IRCallee : NewIRCallees)
+    if (functionMatchesProfile(*IRCallee, ProfFunc)) {
+      OldProfToNewSymbolMap[ProfFunc] = IRCallee;
+      return {IRCallee, MatchState::RecoveredMismatch};
+    }
+  return {nullptr, MatchState::Unknown};
+}
+
+bool SampleProfileMatcher::functionMatchesProfileHelper(
+    const Function &IRFunc, const FunctionId &ProfFunc) {
+  // The value is in the range [0, 1]. The bigger the value is, the more similar
+  // two sequences are.
+  float Similarity = 0.0;
+
+  const auto *FSFlattened = getFlattenedSamplesFor(ProfFunc);
+  assert(FSFlattened && "Flattened profile sample is null");
+  // Similarity check may not be reliable if the function is tiny, we use the
+  // number of basic block as a proxy for the function complexity and skip the
+  // matching if it's too small.
+  if (IRFunc.size() < MinBBForCGMatching ||
+      FSFlattened->getBodySamples().size() < MinBBForCGMatching)
+    return false;
+
+  // For probe-based function, we first trust the checksum info. If the checksum
+  // doesn't match, we continue checking for similarity.
+  if (FunctionSamples::ProfileIsProbeBased) {
+    const auto *FuncDesc = ProbeManager->getDesc(IRFunc);
+    if (FuncDesc &&
+        !ProbeManager->profileIsHashMismatched(*FuncDesc, *FSFlattened)) {
+      LLVM_DEBUG(dbgs() << "The checksums for " << IRFunc.getName()
+                        << "(IR) and " << ProfFunc << "(Profile) match.\n");
+
+      return true;
+    }
+  }
+
+  AnchorMap IRAnchors;
+  findIRAnchors(IRFunc, IRAnchors);
+  AnchorMap ProfileAnchors;
+  findProfileAnchors(*FSFlattened, ProfileAnchors);
+
+  AnchorList FilteredIRAnchorsList;
+  AnchorList FilteredProfileAnchorList;
+  getFilteredAnchorList(IRAnchors, ProfileAnchors, FilteredIRAnchorsList,
+                        FilteredProfileAnchorList);
+
+  // Similarly skip the matching if the num of anchors is not enough.
+  if (FilteredIRAnchorsList.size() < MinCallAnchorForCGMatching ||
+      FilteredProfileAnchorList.size() < MinCallAnchorForCGMatching)
+    return false;
+
+  // Use the diff algorithm to find the LCS between IR and profile.
+  LocToLocMap MatchedAnchors =
+      longestCommonSequence(FilteredIRAnchorsList, FilteredProfileAnchorList);
+
+  Similarity =
+      static_cast<float>(MatchedAnchors.size()) * 2 /
+      (FilteredIRAnchorsList.size() + FilteredProfileAnchorList.size());
+
+  LLVM_DEBUG(dbgs() << "The similarity between " << IRFunc.getName()
+                    << "(IR) and " << ProfFunc << "(profile) is "
+                    << format("%.2f", Similarity) << "\n");
+  assert((Similarity >= 0 && Similarity <= 1.0) &&
+         "Similarity value should be in [0, 1]");
+  return Similarity * 100 > RenamedFuncSimilarityThreshold;
+}
+
+bool SampleProfileMatcher::functionMatchesProfile(const Function &IRFunc,
+                                                  const FunctionId &ProfFunc) {
+  auto R = FunctionProfileNameMap.find({&IRFunc, ProfFunc});
+  if (R != FunctionProfileNameMap.end())
+    return R->second;
+
+  bool Matched = functionMatchesProfileHelper(IRFunc, ProfFunc);
+  FunctionProfileNameMap[{&IRFunc, ProfFunc}] = Matched;
+  return Matched;
+}
+
+// Match profile for new function on the profiled call-graph edge to limit the
+// matching scope.
+void SampleProfileMatcher::matchProfileForNewFunctions(
+    const StringMap<Function *> &NewIRFunctions, FunctionSamples &CallerFS,
+    FunctionMap &OldProfToNewSymbolMap) {
+  // Find the new candidate callees from IR in the current caller scope.
+  std::vector<Function *> NewIRCallees;
+  if (auto *IRCaller =
+          findFunction(CallerFS.getFunction(), OldProfToNewSymbolMap).first) {
+    // No callees for external function, skip the rename matching.
+    if (IRCaller->isDeclaration())
+      return;
+    findNewIRCallees(*IRCaller, NewIRFunctions, NewIRCallees);
+  }
+
+  // Match non-inline callees.
+  for (auto &BS : const_cast<BodySampleMap &>(CallerFS.getBodySamples())) {
+    if (NewIRCallees.empty())
+      break;
+    // New function to old function pairs used to update the CallTargetMap.
+    std::vector<std::pair<FunctionId, FunctionId>> CallTargetsToUpdate;
+    SampleRecord::CallTargetMap &CTM =
+        const_cast<SampleRecord::CallTargetMap &>(BS.second.getCallTargets());
+    for (const auto &TS : CTM) {
+      const FunctionId &ProfCallee = TS.first;
+      auto MatchRes =
+          findOrMatchFunction(ProfCallee, OldProfToNewSymbolMap, NewIRCallees);
+      if (MatchRes.second != MatchState::RecoveredMismatch)
+        continue;
+      FunctionId NewIRCalleeName(MatchRes.first->getName());
+      assert(NewIRCalleeName != ProfCallee &&
+             "New callee symbol is not a new function");
+      LLVM_DEBUG(dbgs() << "In function " << CallerFS.getFunction()
+                        << ", changing profile name from " << ProfCallee
+                        << " to " << NewIRCalleeName << "\n");
+      CallTargetsToUpdate.emplace_back(NewIRCalleeName, ProfCallee);
+    }
+
+    for (const auto &P : CallTargetsToUpdate) {
+      CTM[P.first] = CTM[P.second];
+      CTM.erase(P.second);
+    }
+  }
+
+  // Match inline callees.
+  for (auto &CM :
+       const_cast<CallsiteSampleMap &>(CallerFS.getCallsiteSamples())) {
+    auto &CalleeMap = CM.second;
+    // New function to old FunctionSamples pairs used to update the
+    // CallsiteSampleMap.
+    std::vector<std::pair<FunctionId, FunctionSamples *>> FSamplesToUpdate;
+    for (auto &CS : CalleeMap) {
+      FunctionSamples &CalleeFS = CS.second;
+      FunctionId ProfCallee = CalleeFS.getFunction();
+      auto MatchRes =
+          findOrMatchFunction(ProfCallee, OldProfToNewSymbolMap, NewIRCallees);
+      if (MatchRes.second == MatchState::RecoveredMismatch) {
+        FunctionId NewIRCalleeName(MatchRes.first->getName());
+        assert(NewIRCalleeName != ProfCallee &&
+               "New callee symbol is not a new function");
+        LLVM_DEBUG(dbgs() << "In function " << CallerFS.getFunction()
+                          << ", changing profile name from " << ProfCallee
+                          << " to " << NewIRCalleeName << "\n");
+        FSamplesToUpdate.emplace_back(NewIRCalleeName, &CalleeFS);
+      }
+      // Note that even there is no renaming in the current scope, there could
+      // be renaming in deeper callee scope, we need to traverse all the callee
+      // profiles.
+      matchProfileForNewFunctions(NewIRFunctions, CalleeFS,
+                                  OldProfToNewSymbolMap);
+    }
+
+    // Update the CalleeMap using the new name and remove the old entry.
+    for (auto &P : FSamplesToUpdate) {
+      const FunctionId &OldFunction = P.second->getFunction();
+      assert(P.first != OldFunction &&
+             "Renamed function name should be different from the old map key");
+      P.second->setFunction(P.first);
+      CalleeMap[P.first] = *P.second;
+      CalleeMap.erase(OldFunction);
+    }
+  }
+}
+
+std::vector<FunctionSamples *>
+SampleProfileMatcher::sortFuncProfiles(SampleProfileMap &ProfileMap) {
+  std::vector<FunctionSamples *> SortedProfiles;
+  for (auto &I : ProfileMap)
+    SortedProfiles.push_back(&I.second);
+
+  llvm::stable_sort(SortedProfiles,
+                    [](const FunctionSamples *A, const FunctionSamples *B) {
+                      if (A->getTotalSamples() == B->getTotalSamples())
+                        return A->getContext() < B->getContext();
+                      return A->getTotalSamples() > B->getTotalSamples();
+                    });
+  return SortedProfiles;
+}
+
+void SampleProfileMatcher::runCallGraphMatching() {
+  if (!SalvageRenamedProfile)
+    return;
+  assert(SymbolMap && "SymbolMap is null");
+  assert(FunctionProfileNameMap.empty() &&
+         "FunctionProfileNameMap is not empty before the call graph matching");
+
+  StringMap<Function *> NewIRFunctions;
+  findNewIRFunctions(NewIRFunctions);
+  if (NewIRFunctions.empty())
+    return;
+
+  // The new functions found by the renaming matching. Save them into a map
+  // whose key is the old(profile) function name and value is the new(renamed)
+  // function.
+  FunctionMap OldProfToNewSymbolMap;
+  // Sort the profiles to make the matching order deterministic.
+  for (auto *P : sortFuncProfiles(Reader.getProfiles()))
+    matchProfileForNewFunctions(NewIRFunctions, *P, OldProfToNewSymbolMap);
+
+  FunctionProfileNameMap.clear();
+  if (OldProfToNewSymbolMap.empty())
+    return;
+  // Update all the data generated by the old profile.
+  // Add the new function to the SymbolMap, which will be used in
+  // SampleLoader.
+  for (auto &I : OldProfToNewSymbolMap) {
+    assert(I.second && "New function is null");
+    SymbolMap->emplace(FunctionId(I.second->getName()), I.second);
+  }
+
+  // Re-flatten the profiles after the renaming.
+  FlattenedProfiles.clear();
+  ProfileConverter::flattenProfile(Reader.getProfiles(), FlattenedProfiles,
+                                   FunctionSamples::ProfileIsCS);
+}
+
+void SampleProfileMatcher::runOnFunction(Function &F) {
+  if (skipProfileForFunction(F))
+    return;
+  runCFGMatching(F);
+}
+
+void SampleProfileMatcher::runOnModule(FunctionMap &SymMap) {
+  ProfileConverter::flattenProfile(Reader.getProfiles(), FlattenedProfiles,
+                                   FunctionSamples::ProfileIsCS);
+  SymbolMap = &SymMap;
----------------
wlei-llvm wrote:

This is because of the dependence of `SymbolMap `, in SampleLoader, we don't have the `SymbolMap` populated when `SampleProfileMatcher` is constructed, so this is to explicitly show `runOnModule` should be run after `SymbolMap` population.

https://github.com/llvm/llvm-project/pull/92151


More information about the llvm-commits mailing list