Skip to content

Commit 923264b

Browse files
authored
[CIR] Split cir-simplify into two passes (#868)
This PR splits the old `cir-simplify` pass into two new passes, namely `cir-canonicalize` and `cir-simplify` (the new `cir-simplify`). The `cir-canonicalize` pass runs transformations that do not affect CIR-to-source fidelity much, such as operation folding and redundant operation elimination. On the other hand, the new `cir-simplify` pass runs transformations that may significantly change the code and break high-level code analysis passes, such as more aggresive code optimizations. This PR also updates the CIR-to-CIR pipeline to fit these two new passes. The `cir-canonicalize` pass is moved to the very front of the pipeline, while the new `cir-simplify` pass is moved to the back of the pipeline (but still before lowering prepare of course). Additionally, the new `cir-simplify` now only runs when the user specifies a non-zero optimization level on the frontend. Also fixed some typos and resolved some `clang-tidy` complaints along the way. Resolves #827 .
1 parent 3358f42 commit 923264b

File tree

20 files changed

+250
-173
lines changed

20 files changed

+250
-173
lines changed

clang/include/clang/CIR/CIRToCIRPasses.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ mlir::LogicalResult runCIRToCIRPasses(
3434
llvm::StringRef lifetimeOpts, bool enableIdiomRecognizer,
3535
llvm::StringRef idiomRecognizerOpts, bool enableLibOpt,
3636
llvm::StringRef libOptOpts, std::string &passOptParsingFailure,
37-
bool flattenCIR, bool emitMLIR, bool enableCallConvLowering,
38-
bool enableMem2reg);
37+
bool enableCIRSimplify, bool flattenCIR, bool emitMLIR,
38+
bool enableCallConvLowering, bool enableMem2reg);
3939

4040
} // namespace cir
4141

clang/include/clang/CIR/Dialect/Passes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ std::unique_ptr<Pass> createLifetimeCheckPass(ArrayRef<StringRef> remark,
2626
ArrayRef<StringRef> hist,
2727
unsigned hist_limit,
2828
clang::ASTContext *astCtx);
29+
std::unique_ptr<Pass> createCIRCanonicalizePass();
2930
std::unique_ptr<Pass> createCIRSimplifyPass();
3031
std::unique_ptr<Pass> createDropASTPass();
3132
std::unique_ptr<Pass> createSCFPreparePass();

clang/include/clang/CIR/Dialect/Passes.td

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,33 @@
1111

1212
include "mlir/Pass/PassBase.td"
1313

14-
def CIRSimplify : Pass<"cir-simplify"> {
15-
let summary = "Performs CIR simplification";
14+
def CIRCanonicalize : Pass<"cir-canonicalize"> {
15+
let summary = "Performs CIR canonicalization";
1616
let description = [{
17-
The pass rewrites CIR and removes some redundant operations.
17+
Perform canonicalizations on CIR and removes some redundant operations.
18+
19+
This pass performs basic cleanup and canonicalization transformations that
20+
hopefully do not affect CIR-to-source fidelity and high-level code analysis
21+
passes too much. Example transformations performed in this pass include
22+
empty scope cleanup, trivial try cleanup, redundant branch cleanup, etc.
23+
Those more "heavyweight" transformations and those transformations that
24+
could significantly affect CIR-to-source fidelity are performed in the
25+
`cir-simplify` pass.
26+
}];
1827

19-
For example, due to canonicalize pass is too aggressive for CIR when
20-
the pipeline is used for C/C++ analysis, this pass runs some rewrites
21-
for scopes, merging some blocks and eliminating unnecessary control-flow.
28+
let constructor = "mlir::createCIRCanonicalizePass()";
29+
let dependentDialects = ["cir::CIRDialect"];
30+
}
2231

23-
Also, the pass removes redundant and/or unneccessary cast and unary not
24-
operation e.g.
25-
```mlir
26-
%1 = cir.cast(bool_to_int, %0 : !cir.bool), !s32i
27-
%2 = cir.cast(int_to_bool, %1 : !s32i), !cir.bool
28-
```
32+
def CIRSimplify : Pass<"cir-simplify"> {
33+
let summary = "Performs CIR simplification and code optimization";
34+
let description = [{
35+
The pass performs code simplification and optimization on CIR.
2936

37+
Unlike the `cir-canonicalize` pass, this pass contains more aggresive code
38+
transformations that could significantly affect CIR-to-source fidelity.
39+
Example transformations performed in this pass include ternary folding,
40+
code hoisting, etc.
3041
}];
3142
let constructor = "mlir::createCIRSimplifyPass()";
3243
let dependentDialects = ["cir::CIRDialect"];

clang/lib/CIR/CodeGen/CIRPasses.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ mlir::LogicalResult runCIRToCIRPasses(
2626
llvm::StringRef lifetimeOpts, bool enableIdiomRecognizer,
2727
llvm::StringRef idiomRecognizerOpts, bool enableLibOpt,
2828
llvm::StringRef libOptOpts, std::string &passOptParsingFailure,
29-
bool flattenCIR, bool emitMLIR, bool enableCallConvLowering,
30-
bool enableMem2Reg) {
29+
bool enableCIRSimplify, bool flattenCIR, bool emitMLIR,
30+
bool enableCallConvLowering, bool enableMem2Reg) {
3131

3232
mlir::PassManager pm(mlirCtx);
33-
pm.addPass(mlir::createCIRSimplifyPass());
33+
pm.addPass(mlir::createCIRCanonicalizePass());
3434

3535
// TODO(CIR): Make this actually propagate errors correctly. This is stubbed
3636
// in to get rebases going.
@@ -66,6 +66,9 @@ mlir::LogicalResult runCIRToCIRPasses(
6666
pm.addPass(std::move(libOpPass));
6767
}
6868

69+
if (enableCIRSimplify)
70+
pm.addPass(mlir::createCIRSimplifyPass());
71+
6972
pm.addPass(mlir::createLoweringPreparePass(&astCtx));
7073

7174
// FIXME(cir): This pass should run by default, but it is lacking support for
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
//===- CIRSimplify.cpp - performs CIR canonicalization --------------------===//
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+
#include "PassDetail.h"
10+
#include "mlir/Dialect/Func/IR/FuncOps.h"
11+
#include "mlir/IR/Block.h"
12+
#include "mlir/IR/Operation.h"
13+
#include "mlir/IR/PatternMatch.h"
14+
#include "mlir/IR/Region.h"
15+
#include "mlir/Support/LogicalResult.h"
16+
#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
17+
#include "clang/CIR/Dialect/IR/CIRDialect.h"
18+
#include "clang/CIR/Dialect/Passes.h"
19+
20+
using namespace mlir;
21+
using namespace cir;
22+
23+
namespace {
24+
25+
/// Removes branches between two blocks if it is the only branch.
26+
///
27+
/// From:
28+
/// ^bb0:
29+
/// cir.br ^bb1
30+
/// ^bb1: // pred: ^bb0
31+
/// cir.return
32+
///
33+
/// To:
34+
/// ^bb0:
35+
/// cir.return
36+
struct RemoveRedundantBranches : public OpRewritePattern<BrOp> {
37+
using OpRewritePattern<BrOp>::OpRewritePattern;
38+
39+
LogicalResult matchAndRewrite(BrOp op,
40+
PatternRewriter &rewriter) const final {
41+
Block *block = op.getOperation()->getBlock();
42+
Block *dest = op.getDest();
43+
44+
if (isa<mlir::cir::LabelOp>(dest->front()))
45+
return failure();
46+
47+
// Single edge between blocks: merge it.
48+
if (block->getNumSuccessors() == 1 &&
49+
dest->getSinglePredecessor() == block) {
50+
rewriter.eraseOp(op);
51+
rewriter.mergeBlocks(dest, block);
52+
return success();
53+
}
54+
55+
return failure();
56+
}
57+
};
58+
59+
struct RemoveEmptyScope : public OpRewritePattern<ScopeOp> {
60+
using OpRewritePattern<ScopeOp>::OpRewritePattern;
61+
62+
LogicalResult match(ScopeOp op) const final {
63+
return success(op.getRegion().empty() ||
64+
(op.getRegion().getBlocks().size() == 1 &&
65+
op.getRegion().front().empty()));
66+
}
67+
68+
void rewrite(ScopeOp op, PatternRewriter &rewriter) const final {
69+
rewriter.eraseOp(op);
70+
}
71+
};
72+
73+
struct RemoveEmptySwitch : public OpRewritePattern<SwitchOp> {
74+
using OpRewritePattern<SwitchOp>::OpRewritePattern;
75+
76+
LogicalResult match(SwitchOp op) const final {
77+
return success(op.getRegions().empty());
78+
}
79+
80+
void rewrite(SwitchOp op, PatternRewriter &rewriter) const final {
81+
rewriter.eraseOp(op);
82+
}
83+
};
84+
85+
struct RemoveTrivialTry : public OpRewritePattern<TryOp> {
86+
using OpRewritePattern<TryOp>::OpRewritePattern;
87+
88+
LogicalResult match(TryOp op) const final {
89+
// FIXME: also check all catch regions are empty
90+
// return success(op.getTryRegion().hasOneBlock());
91+
return mlir::failure();
92+
}
93+
94+
void rewrite(TryOp op, PatternRewriter &rewriter) const final {
95+
// Move try body to the parent.
96+
assert(op.getTryRegion().hasOneBlock());
97+
98+
Block *parentBlock = op.getOperation()->getBlock();
99+
mlir::Block *tryBody = &op.getTryRegion().getBlocks().front();
100+
YieldOp y = dyn_cast<YieldOp>(tryBody->getTerminator());
101+
assert(y && "expected well wrapped up try block");
102+
y->erase();
103+
104+
rewriter.inlineBlockBefore(tryBody, parentBlock, Block::iterator(op));
105+
rewriter.eraseOp(op);
106+
}
107+
};
108+
109+
// Remove call exception with empty cleanups
110+
struct SimplifyCallOp : public OpRewritePattern<CallOp> {
111+
using OpRewritePattern<CallOp>::OpRewritePattern;
112+
113+
LogicalResult match(CallOp op) const final {
114+
// Applicable to cir.call exception ... clean { cir.yield }
115+
mlir::Region *r = &op.getCleanup();
116+
if (r->empty() || !r->hasOneBlock())
117+
return failure();
118+
119+
mlir::Block *b = &r->getBlocks().back();
120+
if (&b->back() != &b->front())
121+
return failure();
122+
123+
return success(isa<YieldOp>(&b->getOperations().back()));
124+
}
125+
126+
void rewrite(CallOp op, PatternRewriter &rewriter) const final {
127+
mlir::Block *b = &op.getCleanup().back();
128+
rewriter.eraseOp(&b->back());
129+
rewriter.eraseBlock(b);
130+
}
131+
};
132+
133+
//===----------------------------------------------------------------------===//
134+
// CIRCanonicalizePass
135+
//===----------------------------------------------------------------------===//
136+
137+
struct CIRCanonicalizePass : public CIRCanonicalizeBase<CIRCanonicalizePass> {
138+
using CIRCanonicalizeBase::CIRCanonicalizeBase;
139+
140+
// The same operation rewriting done here could have been performed
141+
// by CanonicalizerPass (adding hasCanonicalizer for target Ops and
142+
// implementing the same from above in CIRDialects.cpp). However, it's
143+
// currently too aggressive for static analysis purposes, since it might
144+
// remove things where a diagnostic can be generated.
145+
//
146+
// FIXME: perhaps we can add one more mode to GreedyRewriteConfig to
147+
// disable this behavior.
148+
void runOnOperation() override;
149+
};
150+
151+
void populateCIRCanonicalizePatterns(RewritePatternSet &patterns) {
152+
// clang-format off
153+
patterns.add<
154+
RemoveRedundantBranches,
155+
RemoveEmptyScope,
156+
RemoveEmptySwitch,
157+
RemoveTrivialTry,
158+
SimplifyCallOp
159+
>(patterns.getContext());
160+
// clang-format on
161+
}
162+
163+
void CIRCanonicalizePass::runOnOperation() {
164+
// Collect rewrite patterns.
165+
RewritePatternSet patterns(&getContext());
166+
populateCIRCanonicalizePatterns(patterns);
167+
168+
// Collect operations to apply patterns.
169+
SmallVector<Operation *, 16> ops;
170+
getOperation()->walk([&](Operation *op) {
171+
// CastOp here is to perform a manual `fold` in
172+
// applyOpPatternsAndFold
173+
if (isa<BrOp, BrCondOp, ScopeOp, SwitchOp, CastOp, TryOp, UnaryOp, SelectOp,
174+
ComplexCreateOp, ComplexRealOp, ComplexImagOp, CallOp>(op))
175+
ops.push_back(op);
176+
});
177+
178+
// Apply patterns.
179+
if (applyOpPatternsAndFold(ops, std::move(patterns)).failed())
180+
signalPassFailure();
181+
}
182+
183+
} // namespace
184+
185+
std::unique_ptr<Pass> mlir::createCIRCanonicalizePass() {
186+
return std::make_unique<CIRCanonicalizePass>();
187+
}

0 commit comments

Comments
 (0)