[llvm-mca] Refactor how execution is orchestrated by the Pipeline.
This patch changes how instruction execution is orchestrated by the Pipeline.
In particular, this patch makes it more explicit how instructions transition
through the various pipeline stages during execution.
The main goal is to simplify both the stage API and the Pipeline execution. At
the same time, this patch fixes some design issues which are currently latent,
but that are likely to cause problems in future if people start defining custom
pipelines.
The new design assumes that each pipeline stage knows the "next-in-sequence".
The Stage API has gained three new methods:
- isAvailable(IR)
- checkNextStage(IR)
- moveToTheNextStage(IR).
An instruction IR can be executed by a Stage if method `Stage::isAvailable(IR)`
returns true.
Instructions can move to next stages using method moveToTheNextStage(IR).
An instruction cannot be moved to the next stage if method checkNextStage(IR)
(called on the current stage) returns false.
Stages are now responsible for moving instructions to the next stage in sequence
if necessary.
Instructions are allowed to transition through multiple stages during a single
cycle (as long as stages are available, and as long as all the calls to
`checkNextStage(IR)` returns true).
Methods `Stage::preExecute()` and `Stage::postExecute()` have now become
redundant, and those are removed by this patch.
Method Pipeline::runCycle() is now simpler, and it correctly visits stages
on every begin/end of cycle.
Other changes:
- DispatchStage no longer requires a reference to the Scheduler.
- ExecuteStage no longer needs to directly interact with the
RetireControlUnit. Instead, executed instructions are now directly moved to the
next stage (i.e. the retire stage).
- RetireStage gained an execute method. This allowed us to remove the
dependency with the RCU in ExecuteStage.
- FecthStage now updates the "program counter" during cycleBegin() (i.e.
before we start executing new instructions).
- We no longer need Stage::Status to be returned by method execute(). It has
been dropped in favor of a more lightweight llvm::Error.
Overally, I measured a ~11% performance gain w.r.t. the previous design. I also
think that the Stage interface is probably easier to read now. That being said,
code comments have to be improved, and I plan to do it in a follow-up patch.
Differential revision: https://reviews.llvm.org/D50849
llvm-svn: 339923
diff --git a/llvm/tools/llvm-mca/Pipeline.cpp b/llvm/tools/llvm-mca/Pipeline.cpp
index 91c7a5d..a67ae98 100644
--- a/llvm/tools/llvm-mca/Pipeline.cpp
+++ b/llvm/tools/llvm-mca/Pipeline.cpp
@@ -37,29 +37,6 @@
});
}
-// This routine returns early if any stage returns 'false' after execute() is
-// called on it.
-Stage::Status Pipeline::executeStages(InstRef &IR) {
- for (const std::unique_ptr<Stage> &S : Stages) {
- Stage::Status StatusOrErr = S->execute(IR);
- if (!StatusOrErr)
- return StatusOrErr.takeError();
- else if (StatusOrErr.get() == Stage::Stop)
- return Stage::Stop;
- }
- return Stage::Continue;
-}
-
-void Pipeline::preExecuteStages() {
- for (const std::unique_ptr<Stage> &S : Stages)
- S->preExecute();
-}
-
-void Pipeline::postExecuteStages() {
- for (const std::unique_ptr<Stage> &S : Stages)
- S->postExecute();
-}
-
llvm::Error Pipeline::run() {
assert(!Stages.empty() && "Unexpected empty pipeline found!");
@@ -74,36 +51,38 @@
}
llvm::Error Pipeline::runCycle() {
- // Update stages before we start processing new instructions.
llvm::Error Err = llvm::ErrorSuccess();
- for (auto I = Stages.begin(), E = Stages.end(); I != E && !Err; ++I) {
+ // Update stages before we start processing new instructions.
+ for (auto I = Stages.rbegin(), E = Stages.rend(); I != E && !Err; ++I) {
const std::unique_ptr<Stage> &S = *I;
Err = S->cycleStart();
}
- if (Err)
- return Err;
-
// Now fetch and execute new instructions.
InstRef IR;
- while (true) {
- preExecuteStages();
- Stage::Status Val = executeStages(IR);
- if (!Val)
- return Val.takeError();
- if (Val.get() == Stage::Stop)
- break;
- postExecuteStages();
- }
+ Stage &FirstStage = *Stages[0];
+ while (!Err && FirstStage.isAvailable(IR))
+ Err = FirstStage.execute(IR);
// Update stages in preparation for a new cycle.
- for (auto I = Stages.begin(), E = Stages.end(); I != E && !Err; ++I) {
+ for (auto I = Stages.rbegin(), E = Stages.rend(); I != E && !Err; ++I) {
const std::unique_ptr<Stage> &S = *I;
Err = S->cycleEnd();
}
+
return Err;
}
+void Pipeline::appendStage(std::unique_ptr<Stage> S) {
+ assert(S && "Invalid null stage in input!");
+ if (!Stages.empty()) {
+ Stage *Last = Stages.back().get();
+ Last->setNextInSequence(S.get());
+ }
+
+ Stages.push_back(std::move(S));
+}
+
void Pipeline::notifyCycleBegin() {
LLVM_DEBUG(dbgs() << "[E] Cycle begin: " << Cycles << '\n');
for (HWEventListener *Listener : Listeners)