Skip to content

Commit fb1de7e

Browse files
committed
Implement a new kind of Pass: dynamic pass pipeline
Instead of performing a transformation, such pass yields a new pass pipeline to run on the currently visited operation. This feature can be used for example to implement a sub-pipeline that would run only on an operation with specific attributes. Another example would be to compute a cost model and dynamic schedule a pipeline based on the result of this analysis. Discussion: https://llvm.discourse.group/t/rfc-dynamic-pass-pipeline/1637 Recommit after fixing an ASAN issue: the callback lambda needs to be allocated to a temporary to have its lifetime extended to the end of the current block instead of just the current call expression. Reviewed By: silvas Differential Revision: https://reviews.llvm.org/D86392
1 parent b289dc5 commit fb1de7e

File tree

9 files changed

+232
-6
lines changed

9 files changed

+232
-6
lines changed

mlir/include/mlir/Pass/Pass.h

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@ class OpToOpPassAdaptor;
2424
/// The state for a single execution of a pass. This provides a unified
2525
/// interface for accessing and initializing necessary state for pass execution.
2626
struct PassExecutionState {
27-
PassExecutionState(Operation *ir, AnalysisManager analysisManager)
28-
: irAndPassFailed(ir, false), analysisManager(analysisManager) {}
27+
PassExecutionState(Operation *ir, AnalysisManager analysisManager,
28+
function_ref<LogicalResult(OpPassManager &, Operation *)>
29+
pipelineExecutor)
30+
: irAndPassFailed(ir, false), analysisManager(analysisManager),
31+
pipelineExecutor(pipelineExecutor) {}
2932

3033
/// The current operation being transformed and a bool for if the pass
3134
/// signaled a failure.
@@ -36,6 +39,10 @@ struct PassExecutionState {
3639

3740
/// The set of preserved analyses for the current execution.
3841
detail::PreservedAnalyses preservedAnalyses;
42+
43+
/// This is a callback in the PassManager that allows to schedule dynamic
44+
/// pipelines that will be rooted at the provided operation.
45+
function_ref<LogicalResult(OpPassManager &, Operation *)> pipelineExecutor;
3946
};
4047
} // namespace detail
4148

@@ -156,6 +163,13 @@ class Pass {
156163
/// The polymorphic API that runs the pass over the currently held operation.
157164
virtual void runOnOperation() = 0;
158165

166+
/// Schedule an arbitrary pass pipeline on the provided operation.
167+
/// This can be invoke any time in a pass to dynamic schedule more passes.
168+
/// The provided operation must be the current one or one nested below.
169+
LogicalResult runPipeline(OpPassManager &pipeline, Operation *op) {
170+
return passState->pipelineExecutor(pipeline, op);
171+
}
172+
159173
/// A clone method to create a copy of this pass.
160174
std::unique_ptr<Pass> clone() const {
161175
auto newInst = clonePass();

mlir/include/mlir/Pass/PassManager.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class PassInstrumentor;
3636

3737
namespace detail {
3838
struct OpPassManagerImpl;
39+
struct PassExecutionState;
3940
} // end namespace detail
4041

4142
//===----------------------------------------------------------------------===//
@@ -119,6 +120,7 @@ class OpPassManager {
119120

120121
/// Allow access to the constructor.
121122
friend class PassManager;
123+
friend class Pass;
122124

123125
/// Allow access.
124126
friend detail::OpPassManagerImpl;

mlir/lib/Pass/Pass.cpp

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -357,8 +357,22 @@ LogicalResult OpToOpPassAdaptor::run(Pass *pass, Operation *op,
357357
return op->emitOpError() << "trying to schedule a pass on an operation not "
358358
"marked as 'IsolatedFromAbove'";
359359

360-
pass->passState.emplace(op, am);
361-
360+
// Initialize the pass state with a callback for the pass to dynamically
361+
// execute a pipeline on the currently visited operation.
362+
auto dynamic_pipeline_callback =
363+
[op, &am](OpPassManager &pipeline, Operation *root) {
364+
if (!op->isAncestor(root)) {
365+
root->emitOpError()
366+
<< "Trying to schedule a dynamic pipeline on an "
367+
"operation that isn't "
368+
"nested under the current operation the pass is processing";
369+
return failure();
370+
}
371+
AnalysisManager nestedAm = am.nest(root);
372+
return OpToOpPassAdaptor::runPipeline(pipeline.getPasses(), root,
373+
nestedAm);
374+
};
375+
pass->passState.emplace(op, am, dynamic_pipeline_callback);
362376
// Instrument before the pass has run.
363377
PassInstrumentor *pi = am.getPassInstrumentor();
364378
if (pi)
@@ -839,8 +853,6 @@ PassInstrumentor *AnalysisManager::getPassInstrumentor() const {
839853

840854
/// Get an analysis manager for the given child operation.
841855
AnalysisManager AnalysisManager::nest(Operation *op) {
842-
assert(op->getParentOp() == impl->getOperation() &&
843-
"'op' has a different parent operation");
844856
auto it = impl->childAnalyses.find(op);
845857
if (it == impl->childAnalyses.end())
846858
it = impl->childAnalyses
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// RUN: mlir-opt %s -pass-pipeline='module(test-dynamic-pipeline{op-name=inner_mod1 run-on-parent=1 dynamic-pipeline=test-patterns})' -split-input-file -verify-diagnostics
2+
3+
// Verify that we fail to schedule a dynamic pipeline on the parent operation.
4+
5+
// expected-error @+1 {{'module' op Trying to schedule a dynamic pipeline on an operation that isn't nested under the current operation}}
6+
module {
7+
module @inner_mod1 {
8+
"test.symbol"() {sym_name = "foo"} : () -> ()
9+
func @bar()
10+
}
11+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// RUN: mlir-opt %s -pass-pipeline='module(test-dynamic-pipeline{op-name=inner_mod1 dynamic-pipeline=cse})' --mlir-disable-threading -print-ir-before-all 2>&1 | FileCheck %s --check-prefix=NOTNESTED --check-prefix=CHECK
2+
// RUN: mlir-opt %s -pass-pipeline='module(test-dynamic-pipeline{op-name=inner_mod1 run-on-nested-operations=1 dynamic-pipeline=cse})' --mlir-disable-threading -print-ir-before-all 2>&1 | FileCheck %s --check-prefix=NESTED --check-prefix=CHECK
3+
4+
5+
// Verify that we can schedule a dynamic pipeline on a nested operation
6+
7+
func @f() {
8+
return
9+
}
10+
11+
// CHECK: IR Dump Before
12+
// CHECK-SAME: TestDynamicPipelinePass
13+
// CHECK-NEXT: module @inner_mod1
14+
module @inner_mod1 {
15+
// We use the print-ir-after-all dumps to check the granularity of the
16+
// scheduling: if we are nesting we expect to see to individual "Dump Before
17+
// CSE" output: one for each of the function. If we don't nest, then we expect
18+
// the CSE pass to run on the `inner_mod1` module directly.
19+
20+
// CHECK: Dump Before CSE
21+
// NOTNESTED-NEXT: @inner_mod1
22+
// NESTED-NEXT: @foo
23+
func @foo()
24+
// Only in the nested case we have a second run of the pass here.
25+
// NESTED: Dump Before CSE
26+
// NESTED-NEXT: @baz
27+
func @baz()
28+
}

mlir/test/Pass/dynamic-pipeline.mlir

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// RUN: mlir-opt %s -pass-pipeline='module(test-dynamic-pipeline{op-name=inner_mod1, dynamic-pipeline=func(cse,canonicalize)})' --mlir-disable-threading -print-ir-before-all 2>&1 | FileCheck %s --check-prefix=MOD1 --check-prefix=MOD1-ONLY --check-prefix=CHECK
2+
// RUN: mlir-opt %s -pass-pipeline='module(test-dynamic-pipeline{op-name=inner_mod2, dynamic-pipeline=func(cse,canonicalize)})' --mlir-disable-threading -print-ir-before-all 2>&1 | FileCheck %s --check-prefix=MOD2 --check-prefix=MOD2-ONLY --check-prefix=CHECK
3+
// RUN: mlir-opt %s -pass-pipeline='module(test-dynamic-pipeline{op-name=inner_mod1,inner_mod2, dynamic-pipeline=func(cse,canonicalize)})' --mlir-disable-threading -print-ir-before-all 2>&1 | FileCheck %s --check-prefix=MOD1 --check-prefix=MOD2 --check-prefix=CHECK
4+
// RUN: mlir-opt %s -pass-pipeline='module(test-dynamic-pipeline{dynamic-pipeline=func(cse,canonicalize)})' --mlir-disable-threading -print-ir-before-all 2>&1 | FileCheck %s --check-prefix=MOD1 --check-prefix=MOD2 --check-prefix=CHECK
5+
6+
7+
func @f() {
8+
return
9+
}
10+
11+
// CHECK: IR Dump Before
12+
// CHECK-SAME: TestDynamicPipelinePass
13+
// CHECK-NEXT: module @inner_mod1
14+
// MOD2-ONLY: dynamic-pipeline skip op name: inner_mod1
15+
module @inner_mod1 {
16+
// MOD1: Dump Before CSE
17+
// MOD1-NEXT: @foo
18+
// MOD1: Dump Before Canonicalizer
19+
// MOD1-NEXT: @foo
20+
func @foo() {
21+
return
22+
}
23+
// MOD1: Dump Before CSE
24+
// MOD1-NEXT: @baz
25+
// MOD1: Dump Before Canonicalizer
26+
// MOD1-NEXT: @baz
27+
func @baz() {
28+
return
29+
}
30+
}
31+
32+
// CHECK: IR Dump Before
33+
// CHECK-SAME: TestDynamicPipelinePass
34+
// CHECK-NEXT: module @inner_mod2
35+
// MOD1-ONLY: dynamic-pipeline skip op name: inner_mod2
36+
module @inner_mod2 {
37+
// MOD2: Dump Before CSE
38+
// MOD2-NEXT: @foo
39+
// MOD2: Dump Before Canonicalizer
40+
// MOD2-NEXT: @foo
41+
func @foo() {
42+
return
43+
}
44+
}

mlir/test/lib/Transforms/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ add_mlir_library(MLIRTestTransforms
1111
TestConvertGPUKernelToCubin.cpp
1212
TestConvertGPUKernelToHsaco.cpp
1313
TestDominance.cpp
14+
TestDynamicPipeline.cpp
1415
TestLoopFusion.cpp
1516
TestGpuMemoryPromotion.cpp
1617
TestGpuParallelLoopMapping.cpp
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
//===------ TestDynamicPipeline.cpp --- dynamic pipeline test pass --------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This file implements a pass to test the dynamic pipeline feature.
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#include "mlir/Dialect/SCF/SCF.h"
14+
#include "mlir/IR/Builders.h"
15+
#include "mlir/Pass/Pass.h"
16+
#include "mlir/Pass/PassManager.h"
17+
#include "mlir/Transforms/LoopUtils.h"
18+
#include "mlir/Transforms/Passes.h"
19+
20+
using namespace mlir;
21+
22+
namespace {
23+
24+
class TestDynamicPipelinePass
25+
: public PassWrapper<TestDynamicPipelinePass, OperationPass<>> {
26+
public:
27+
void getDependentDialects(DialectRegistry &registry) const override {
28+
OpPassManager pm(ModuleOp::getOperationName(), false);
29+
parsePassPipeline(pipeline, pm, llvm::errs());
30+
pm.getDependentDialects(registry);
31+
}
32+
33+
TestDynamicPipelinePass(){};
34+
TestDynamicPipelinePass(const TestDynamicPipelinePass &) {}
35+
36+
void runOnOperation() override {
37+
llvm::errs() << "Dynamic execute '" << pipeline << "' on "
38+
<< getOperation()->getName() << "\n";
39+
if (pipeline.empty()) {
40+
llvm::errs() << "Empty pipeline\n";
41+
return;
42+
}
43+
auto symbolOp = dyn_cast<SymbolOpInterface>(getOperation());
44+
if (!symbolOp) {
45+
getOperation()->emitWarning()
46+
<< "Ignoring because not implementing SymbolOpInterface\n";
47+
return;
48+
}
49+
50+
auto opName = symbolOp.getName();
51+
if (!opNames.empty() && !llvm::is_contained(opNames, opName)) {
52+
llvm::errs() << "dynamic-pipeline skip op name: " << opName << "\n";
53+
return;
54+
}
55+
if (!pm) {
56+
pm = std::make_unique<OpPassManager>(
57+
getOperation()->getName().getIdentifier(), false);
58+
parsePassPipeline(pipeline, *pm, llvm::errs());
59+
}
60+
61+
// Check that running on the parent operation always immediately fails.
62+
if (runOnParent) {
63+
if (getOperation()->getParentOp())
64+
if (!failed(runPipeline(*pm, getOperation()->getParentOp())))
65+
signalPassFailure();
66+
return;
67+
}
68+
69+
if (runOnNestedOp) {
70+
llvm::errs() << "Run on nested op\n";
71+
getOperation()->walk([&](Operation *op) {
72+
if (op == getOperation() || !op->isKnownIsolatedFromAbove())
73+
return;
74+
llvm::errs() << "Run on " << *op << "\n";
75+
// Run on the current operation
76+
if (failed(runPipeline(*pm, op)))
77+
signalPassFailure();
78+
});
79+
} else {
80+
// Run on the current operation
81+
if (failed(runPipeline(*pm, getOperation())))
82+
signalPassFailure();
83+
}
84+
}
85+
86+
std::unique_ptr<OpPassManager> pm;
87+
88+
Option<bool> runOnNestedOp{
89+
*this, "run-on-nested-operations",
90+
llvm::cl::desc("This will apply the pipeline on nested operations under "
91+
"the visited operation.")};
92+
Option<bool> runOnParent{
93+
*this, "run-on-parent",
94+
llvm::cl::desc("This will apply the pipeline on the parent operation if "
95+
"it exist, this is expected to fail.")};
96+
Option<std::string> pipeline{
97+
*this, "dynamic-pipeline",
98+
llvm::cl::desc("The pipeline description that "
99+
"will run on the filtered function.")};
100+
ListOption<std::string> opNames{
101+
*this, "op-name", llvm::cl::MiscFlags::CommaSeparated,
102+
llvm::cl::desc("List of function name to apply the pipeline to")};
103+
};
104+
} // end namespace
105+
106+
namespace mlir {
107+
void registerTestDynamicPipelinePass() {
108+
PassRegistration<TestDynamicPipelinePass>(
109+
"test-dynamic-pipeline", "Tests the dynamic pipeline feature by applying "
110+
"a pipeline on a selected set of functions");
111+
}
112+
} // namespace mlir

mlir/tools/mlir-opt/mlir-opt.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ void registerTestConvertGPUKernelToCubinPass();
5252
void registerTestConvertGPUKernelToHsacoPass();
5353
void registerTestDominancePass();
5454
void registerTestDialect(DialectRegistry &);
55+
void registerTestDynamicPipelinePass();
5556
void registerTestExpandTanhPass();
5657
void registerTestFunc();
5758
void registerTestGpuMemoryPromotionPass();
@@ -108,6 +109,7 @@ void registerTestPasses() {
108109
registerTestAffineLoopParametricTilingPass();
109110
registerTestBufferPlacementPreparationPass();
110111
registerTestDominancePass();
112+
registerTestDynamicPipelinePass();
111113
registerTestFunc();
112114
registerTestExpandTanhPass();
113115
registerTestGpuMemoryPromotionPass();

0 commit comments

Comments
 (0)