-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[Analysis] Avoid running transform passes that have just been run #112092
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8e913ff
07babf6
46efb9b
f6e04b4
9f8b10d
461de55
e9ccb01
a12fd8f
1baab71
8b7e97d
783b6ea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
//===- LastRunTrackingAnalysis.h - Avoid running redundant pass -*- C++ -*-===// | ||
// | ||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
// See https://llvm.org/LICENSE.txt for license information. | ||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
// | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This is an analysis pass to track a set of passes that have been run, so that | ||
// we can avoid running a pass again if there is no change since the last run of | ||
// the pass. | ||
// | ||
// In this analysis we track a set of passes S for each function with the | ||
// following transition rules: | ||
// 1. If pass P makes changes, set S = {P}. | ||
// 2. If pass P doesn't make changes, set S = S + {P}. | ||
// | ||
// Before running a pass P which satisfies P(P(x)) == P(x), we check if P is in | ||
// S. If so, we skip this pass since we know that there will be no change. | ||
// | ||
// Notes: | ||
// 1. Some transform passes have parameters that may vary in the optimization | ||
// pipeline. We should check if parameters in current run is compatible with | ||
// that in the last run. | ||
// 2. This pass only tracks at the module/function level. Loop passes are not | ||
// supported for now. | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
#ifndef LLVM_ANALYSIS_LASTRUNTRACKINGANALYSIS_H | ||
#define LLVM_ANALYSIS_LASTRUNTRACKINGANALYSIS_H | ||
|
||
#include "llvm/ADT/DenseMap.h" | ||
#include "llvm/IR/PassManager.h" | ||
#include <functional> | ||
|
||
namespace llvm { | ||
|
||
/// This class is used to track the last run of a set of module/function passes. | ||
/// Invalidation are conservatively handled by the pass manager if a pass | ||
/// doesn't explicitly preserve the result. | ||
/// If we want to skip a pass, we should define a unique ID \p PassID to | ||
/// identify the pass, which is usually a pointer to a static member. If a pass | ||
/// has parameters, they should be stored in a struct \p OptionT with a method | ||
/// bool isCompatibleWith(const OptionT& LastOpt) const to check compatibility. | ||
class LastRunTrackingInfo { | ||
public: | ||
using PassID = const void *; | ||
using OptionPtr = const void *; | ||
// CompatibilityCheckFn is a closure that stores the parameters of last run. | ||
using CompatibilityCheckFn = std::function<bool(OptionPtr)>; | ||
|
||
/// Check if we should skip a pass. | ||
/// \param ID The unique ID of the pass. | ||
/// \param Opt The parameters of the pass. If the pass has no parameters, use | ||
/// shouldSkip(PassID ID) instead. | ||
/// \return True if we should skip the pass. | ||
/// \sa shouldSkip(PassID ID) | ||
template <typename OptionT> | ||
bool shouldSkip(PassID ID, const OptionT &Opt) const { | ||
return shouldSkipImpl(ID, &Opt); | ||
} | ||
bool shouldSkip(PassID ID) const { return shouldSkipImpl(ID, nullptr); } | ||
|
||
/// Update the tracking info. | ||
/// \param ID The unique ID of the pass. | ||
/// \param Changed Whether the pass makes changes. | ||
/// \param Opt The parameters of the pass. It must have the same type as the | ||
/// parameters of the last run. If the pass has no parameters, use | ||
/// update(PassID ID, bool Changed) instead. | ||
/// \sa update(PassID ID, bool Changed) | ||
template <typename OptionT> | ||
void update(PassID ID, bool Changed, const OptionT &Opt) { | ||
updateImpl(ID, Changed, [Opt](OptionPtr Ptr) { | ||
return static_cast<const OptionT *>(Ptr)->isCompatibleWith(Opt); | ||
}); | ||
} | ||
void update(PassID ID, bool Changed) { | ||
updateImpl(ID, Changed, CompatibilityCheckFn{}); | ||
} | ||
|
||
private: | ||
bool shouldSkipImpl(PassID ID, OptionPtr Ptr) const; | ||
void updateImpl(PassID ID, bool Changed, CompatibilityCheckFn CheckFn); | ||
|
||
DenseMap<PassID, CompatibilityCheckFn> TrackedPasses; | ||
}; | ||
|
||
/// A function/module analysis which provides an empty \c LastRunTrackingInfo. | ||
class LastRunTrackingAnalysis final | ||
: public AnalysisInfoMixin<LastRunTrackingAnalysis> { | ||
friend AnalysisInfoMixin<LastRunTrackingAnalysis>; | ||
static AnalysisKey Key; | ||
|
||
public: | ||
using Result = LastRunTrackingInfo; | ||
LastRunTrackingInfo run(Function &F, FunctionAnalysisManager &) { | ||
return LastRunTrackingInfo(); | ||
} | ||
LastRunTrackingInfo run(Module &M, ModuleAnalysisManager &) { | ||
return LastRunTrackingInfo(); | ||
} | ||
}; | ||
|
||
} // namespace llvm | ||
|
||
#endif // LLVM_ANALYSIS_LASTRUNTRACKINGANALYSIS_H |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,51 @@ | ||||||
//===- LastRunTrackingAnalysis.cpp - Avoid running redundant pass -*- C++ -*-=// | ||||||
// | ||||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||||||
// See https://llvm.org/LICENSE.txt for license information. | ||||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||||||
// | ||||||
//===----------------------------------------------------------------------===// | ||||||
// | ||||||
// This is an analysis pass to track a set of passes that have been run, so that | ||||||
// we can avoid running a pass again if there is no change since the last run of | ||||||
// the pass. | ||||||
// | ||||||
//===----------------------------------------------------------------------===// | ||||||
|
||||||
#include "llvm/Analysis/LastRunTrackingAnalysis.h" | ||||||
#include "llvm/ADT/Statistic.h" | ||||||
#include "llvm/Support/CommandLine.h" | ||||||
|
||||||
using namespace llvm; | ||||||
|
||||||
#define DEBUG_TYPE "last-run-tracking" | ||||||
STATISTIC(NumSkippedPasses, "Number of skipped passes"); | ||||||
STATISTIC(NumLRTQueries, "Number of LastRunTracking queries"); | ||||||
|
||||||
static cl::opt<bool> | ||||||
DisableLastRunTracking("disable-last-run-tracking", cl::Hidden, | ||||||
cl::desc("Disable last run tracking"), | ||||||
cl::init(false)); | ||||||
|
||||||
bool LastRunTrackingInfo::shouldSkipImpl(PassID ID, OptionPtr Ptr) const { | ||||||
if (DisableLastRunTracking) | ||||||
return false; | ||||||
++NumLRTQueries; | ||||||
auto Iter = TrackedPasses.find(ID); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this doesn't handle if we have multiple SimplifyCFGs with different pass params. I think it would be nice to support that given how many different SimplifyCFG runs with different params there are in the pipeline There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SimplifyCFGPass runs with params in the
I cannot imagine we can benefit from a more fine-grained caching scheme. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sgtm. I do think what I suggested is a little nicer logically, but you've convinced me it's unnecessary |
||||||
if (Iter == TrackedPasses.end()) | ||||||
return false; | ||||||
if (!Iter->second || Iter->second(Ptr)) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I might hit a bug with
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is expected behavior that we avoid run the same pass regardless of whether the last run of this pass modifies IR. llvm-project/llvm/unittests/Analysis/LastRunTrackingAnalysisTest.cpp Lines 96 to 97 in 3a3517c
For InstCombine, we expect that it reaches a fixpoint in the first run. Thus it is safe to avoid the second run. See also the discussion in https://discourse.llvm.org/t/rfc-pipeline-avoid-running-transform-passes-that-have-just-been-run/82467/8. If not, InstCombine should report something like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it, thanks! |
||||||
++NumSkippedPasses; | ||||||
return true; | ||||||
} | ||||||
return false; | ||||||
} | ||||||
|
||||||
void LastRunTrackingInfo::updateImpl(PassID ID, bool Changed, | ||||||
CompatibilityCheckFn CheckFn) { | ||||||
if (Changed) | ||||||
TrackedPasses.clear(); | ||||||
TrackedPasses[ID] = std::move(CheckFn); | ||||||
} | ||||||
|
||||||
AnalysisKey LastRunTrackingAnalysis::Key; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,6 +47,7 @@ | |
#include "llvm/Analysis/ConstantFolding.h" | ||
#include "llvm/Analysis/GlobalsModRef.h" | ||
#include "llvm/Analysis/InstructionSimplify.h" | ||
#include "llvm/Analysis/LastRunTrackingAnalysis.h" | ||
#include "llvm/Analysis/LazyBlockFrequencyInfo.h" | ||
#include "llvm/Analysis/MemoryBuiltins.h" | ||
#include "llvm/Analysis/OptimizationRemarkEmitter.h" | ||
|
@@ -5545,8 +5546,15 @@ void InstCombinePass::printPipeline( | |
OS << '>'; | ||
} | ||
|
||
char InstCombinePass::ID = 0; | ||
dtcxzyw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
PreservedAnalyses InstCombinePass::run(Function &F, | ||
FunctionAnalysisManager &AM) { | ||
auto &LRT = AM.getResult<LastRunTrackingAnalysis>(F); | ||
// No changes since last InstCombine pass, exit early. | ||
if (LRT.shouldSkip(&ID)) | ||
return PreservedAnalyses::all(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a thought, but could we instead bake this into the passmanager itself? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't agree. Only some canonicalization/cleanup passes benefit from this optimization. Moving the logic into PassManager may introduce higher overhead. BTW, we cannot do this since InstCombine/SimplifyCFG have pass parameters. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. agreed about keeping this out of the pass manager, the pass manager shouldn't know which passes are supposed to be idempotent There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know much about the new pass manager, but conceptually there is a lot of overlap between:
In the old pass manager you could almost implement this by making a pass P depend on itself:
and then in its
Is there any way to do something similar in the new pass manager? I.e. have the pass manager be responsible for tracking which passes are still "available", even if it does not know which ones are idempotent? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in the legacy pass manager, analyses were special passes. in the new pass manager, analyses and passes are separate concepts, so that sort of hack doesn't really work. and that doesn't solve the pass parameter issue. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there no way to get the parameters going into a pass from the new manager? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
no, I'm not sure how that would work. pass parameters are specified when constructing the pass, none of the pass manager infrastructure can access pass params because that's not really part of the public API of a pass |
||
|
||
auto &AC = AM.getResult<AssumptionAnalysis>(F); | ||
auto &DT = AM.getResult<DominatorTreeAnalysis>(F); | ||
auto &TLI = AM.getResult<TargetLibraryAnalysis>(F); | ||
|
@@ -5562,12 +5570,16 @@ PreservedAnalyses InstCombinePass::run(Function &F, | |
auto *BPI = AM.getCachedResult<BranchProbabilityAnalysis>(F); | ||
|
||
if (!combineInstructionsOverFunction(F, Worklist, AA, AC, TLI, TTI, DT, ORE, | ||
BFI, BPI, PSI, Options)) | ||
BFI, BPI, PSI, Options)) { | ||
// No changes, all analyses are preserved. | ||
LRT.update(&ID, /*Changed=*/false); | ||
return PreservedAnalyses::all(); | ||
} | ||
|
||
// Mark all the analyses that instcombine updates as preserved. | ||
PreservedAnalyses PA; | ||
LRT.update(&ID, /*Changed=*/true); | ||
PA.preserve<LastRunTrackingAnalysis>(); | ||
PA.preserveSet<CFGAnalyses>(); | ||
return PA; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
treating each pass+pass parameter separately seems logically nicer, rather than grouping runs of a pass with different parameters together
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain it further? Did you mean make
LastRunTrackingAnalysis
template class to avoid usingDenseMap
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't have a specific implementation in mind, I meant that we should treat
simplifycfg<params1>
andsimplifycfg<params2>
separately just likesimplifycfg<params1>
andinstcombine
.