clang 24.0.0git
CIRSimplify.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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/Dominance.h"
13#include "mlir/IR/Operation.h"
14#include "mlir/IR/PatternMatch.h"
15#include "mlir/IR/Region.h"
16#include "mlir/Support/LogicalResult.h"
17#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
20#include "llvm/ADT/SmallVector.h"
21
22using namespace mlir;
23using namespace cir;
24
25namespace mlir {
26#define GEN_PASS_DEF_CIRSIMPLIFY
27#include "clang/CIR/Dialect/Passes.h.inc"
28} // namespace mlir
29
30//===----------------------------------------------------------------------===//
31// Rewrite patterns
32//===----------------------------------------------------------------------===//
33
34namespace {
35
36/// Find the `cir.store` operation that stores to the given alloca and dominates
37/// the given load operation. Dominance calculation is done through the given
38/// DominanceInfo object.
39///
40/// Return nullptr if no such store operation exists or if multiple store
41/// operations satisfy the criteria.
42cir::StoreOp findDominatingInitOp(cir::AllocaOp alloca, cir::LoadOp load,
43 const DominanceInfo &domInfo) {
44 cir::StoreOp result;
45
46 // Walk through all uses of the alloca and visit the store operations that
47 // store to the alloca
48 for (const mlir::OpOperand &use : alloca->getUses()) {
49 auto store = mlir::dyn_cast<cir::StoreOp>(use.getOwner());
50 if (!store)
51 continue;
52
53 // `cir.store` has two operands, we're only interested if the store is
54 // storing into the alloca, not if the store is storing the address of the
55 // alloca slot into somewhere else
56 if (use.getOperandNumber() != cir::StoreOp::odsIndex_addr)
57 continue;
58
59 if (domInfo.dominates(store, load)) {
60 if (result) {
61 // If we have already found a dominating store, then there are multiple
62 // dominating stores, we intentionally don't simplify the load.
63 return nullptr;
64 }
65 result = store;
66 }
67 }
68
69 return result;
70}
71
72/// Simplify `cir.load` that loads from an alloca marked as "constant".
73///
74/// For example:
75///
76/// %0 = cir.alloca "x" align(4) const : !cir.ptr<!s32i>
77/// cir.store %init, %0 : !s32i, !cir.ptr<!s32i>
78/// %1 = cir.load %0 : !cir.ptr<!s32i>
79///
80/// All uses of the load above could be replaced with the SSA value `%init`.
81struct SimplifyConstantLoad : public OpRewritePattern<LoadOp> {
82 using OpRewritePattern<LoadOp>::OpRewritePattern;
83
84 LogicalResult matchAndRewrite(LoadOp op,
85 PatternRewriter &rewriter) const override {
86 // Volatile or atomic loads should not be simplified.
87 if (op.getIsVolatile() || op.getMemOrder())
88 return mlir::failure();
89
90 auto allocaOp = op.getAddr().getDefiningOp<cir::AllocaOp>();
91 if (!allocaOp || !allocaOp.getConstant())
92 return mlir::failure();
93
94 cir::StoreOp initStoreOp = findDominatingInitOp(allocaOp, op, domInfo);
95 if (!initStoreOp)
96 return mlir::failure();
97 if (initStoreOp.getIsVolatile() || initStoreOp.getMemOrder()) {
98 // We intentionally act conservatively here and we don't want to simplify
99 // the load if the corresponding store is either volatile or atomic.
100 return mlir::failure();
101 }
102
103 rewriter.replaceOp(op, initStoreOp.getValue());
104 return mlir::success();
105 }
106
107private:
108 mlir::DominanceInfo domInfo;
109};
110
111/// Simplify suitable ternary operations into select operations.
112///
113/// For now we only simplify those ternary operations whose true and false
114/// branches directly yield a value or a constant. That is, both of the true and
115/// the false branch must either contain a cir.yield operation as the only
116/// operation in the branch, or contain a cir.const operation followed by a
117/// cir.yield operation that yields the constant value.
118///
119/// For example, we will simplify the following ternary operation:
120///
121/// %0 = ...
122/// %1 = cir.ternary (%condition, true {
123/// %2 = cir.const ...
124/// cir.yield %2
125/// } false {
126/// cir.yield %0
127///
128/// into the following sequence of operations:
129///
130/// %1 = cir.const ...
131/// %0 = cir.select if %condition then %1 else %2
132struct SimplifyTernary final : public OpRewritePattern<TernaryOp> {
133 using OpRewritePattern<TernaryOp>::OpRewritePattern;
134
135 LogicalResult matchAndRewrite(TernaryOp op,
136 PatternRewriter &rewriter) const override {
137 if (op->getNumResults() != 1)
138 return mlir::failure();
139
140 if (!isSimpleTernaryBranch(op.getTrueRegion()) ||
141 !isSimpleTernaryBranch(op.getFalseRegion()))
142 return mlir::failure();
143
144 cir::YieldOp trueBranchYieldOp =
145 mlir::cast<cir::YieldOp>(op.getTrueRegion().front().getTerminator());
146 cir::YieldOp falseBranchYieldOp =
147 mlir::cast<cir::YieldOp>(op.getFalseRegion().front().getTerminator());
148 mlir::Value trueValue = trueBranchYieldOp.getArgs()[0];
149 mlir::Value falseValue = falseBranchYieldOp.getArgs()[0];
150
151 rewriter.inlineBlockBefore(&op.getTrueRegion().front(), op);
152 rewriter.inlineBlockBefore(&op.getFalseRegion().front(), op);
153 rewriter.eraseOp(trueBranchYieldOp);
154 rewriter.eraseOp(falseBranchYieldOp);
155 rewriter.replaceOpWithNewOp<cir::SelectOp>(op, op.getCond(), trueValue,
156 falseValue);
157
158 return mlir::success();
159 }
160
161private:
162 bool isSimpleTernaryBranch(mlir::Region &region) const {
163 if (!region.hasOneBlock())
164 return false;
165
166 mlir::Block &onlyBlock = region.front();
167 mlir::Block::OpListType &ops = onlyBlock.getOperations();
168
169 // The region/block could only contain at most 2 operations.
170 if (ops.size() > 2)
171 return false;
172
173 if (ops.size() == 1) {
174 // The region/block only contain a cir.yield operation.
175 return true;
176 }
177
178 // Check whether the region/block contains a cir.const followed by a
179 // cir.yield that yields the value.
180 auto yieldOp = mlir::cast<cir::YieldOp>(onlyBlock.getTerminator());
181 auto yieldValueDefOp =
182 yieldOp.getArgs()[0].getDefiningOp<cir::ConstantOp>();
183 return yieldValueDefOp && yieldValueDefOp->getBlock() == &onlyBlock;
184 }
185};
186
187/// Simplify select operations with boolean constants into simpler forms.
188///
189/// This pattern simplifies select operations where both true and false values
190/// are boolean constants. Two specific cases are handled:
191///
192/// 1. When selecting between true and false based on a condition,
193/// the operation simplifies to just the condition itself:
194///
195/// %0 = cir.select if %condition then true else false
196/// ->
197/// (replaced with %condition directly)
198///
199/// 2. When selecting between false and true based on a condition,
200/// the operation simplifies to the logical negation of the condition:
201///
202/// %0 = cir.select if %condition then false else true
203/// ->
204/// %0 = cir.not %condition
205struct SimplifySelect : public OpRewritePattern<SelectOp> {
206 using OpRewritePattern<SelectOp>::OpRewritePattern;
207
208 LogicalResult matchAndRewrite(SelectOp op,
209 PatternRewriter &rewriter) const final {
210 auto trueValueOp = op.getTrueValue().getDefiningOp<cir::ConstantOp>();
211 auto falseValueOp = op.getFalseValue().getDefiningOp<cir::ConstantOp>();
212 if (!trueValueOp || !falseValueOp)
213 return mlir::failure();
214
215 auto trueValue = trueValueOp.getValueAttr<cir::BoolAttr>();
216 auto falseValue = falseValueOp.getValueAttr<cir::BoolAttr>();
217 if (!trueValue || !falseValue)
218 return mlir::failure();
219
220 // cir.select if %0 then #true else #false -> %0
221 if (trueValue.getValue() && !falseValue.getValue()) {
222 rewriter.replaceAllUsesWith(op, op.getCondition());
223 rewriter.eraseOp(op);
224 return mlir::success();
225 }
226
227 // cir.select if %0 then #false else #true -> cir.not %0
228 if (!trueValue.getValue() && falseValue.getValue()) {
229 rewriter.replaceOpWithNewOp<cir::NotOp>(op, op.getCondition());
230 return mlir::success();
231 }
232
233 return mlir::failure();
234 }
235};
236
237/// Simplify `cir.switch` operations by folding cascading cases
238/// into a single `cir.case` with the `anyof` kind.
239///
240/// This pattern identifies cascading cases within a `cir.switch` operation.
241/// Cascading cases are defined as consecutive `cir.case` operations of kind
242/// `equal`, each containing a single `cir.yield` operation in their body.
243///
244/// The pattern merges these cascading cases into a single `cir.case` operation
245/// with kind `anyof`, aggregating all the case values.
246///
247/// The merging process continues until a `cir.case` with a different body
248/// (e.g., containing `cir.break` or compound stmt) is encountered, which
249/// breaks the chain.
250///
251/// Example:
252///
253/// Before:
254/// cir.case equal, [#cir.int<0> : !s32i] {
255/// cir.yield
256/// }
257/// cir.case equal, [#cir.int<1> : !s32i] {
258/// cir.yield
259/// }
260/// cir.case equal, [#cir.int<2> : !s32i] {
261/// cir.break
262/// }
263///
264/// After applying SimplifySwitch:
265/// cir.case anyof, [#cir.int<0> : !s32i, #cir.int<1> : !s32i, #cir.int<2> :
266/// !s32i] {
267/// cir.break
268/// }
269struct SimplifySwitch : public OpRewritePattern<SwitchOp> {
270 using OpRewritePattern<SwitchOp>::OpRewritePattern;
271 LogicalResult matchAndRewrite(SwitchOp op,
272 PatternRewriter &rewriter) const override {
273
274 LogicalResult changed = mlir::failure();
275 SmallVector<CaseOp, 8> cases;
276 SmallVector<CaseOp, 4> cascadingCases;
277 SmallVector<mlir::Attribute, 4> cascadingCaseValues;
278
279 op.collectCases(cases);
280 if (cases.empty())
281 return mlir::failure();
282
283 auto flushMergedOps = [&]() {
284 for (CaseOp &c : cascadingCases)
285 rewriter.eraseOp(c);
286 cascadingCases.clear();
287 cascadingCaseValues.clear();
288 };
289
290 auto mergeCascadingInto = [&](CaseOp &target) {
291 rewriter.modifyOpInPlace(target, [&]() {
292 target.setValueAttr(rewriter.getArrayAttr(cascadingCaseValues));
293 target.setKind(CaseOpKind::Anyof);
294 });
295 changed = mlir::success();
296 };
297
298 for (CaseOp c : cases) {
299 cir::CaseOpKind kind = c.getKind();
300 if (kind == cir::CaseOpKind::Equal &&
301 isa<YieldOp>(c.getCaseRegion().front().front())) {
302 // If the case contains only a YieldOp, collect it for cascading merge
303 cascadingCases.push_back(c);
304 cascadingCaseValues.push_back(c.getValue()[0]);
305 } else if (kind == cir::CaseOpKind::Equal && !cascadingCases.empty()) {
306 // merge previously collected cascading cases
307 cascadingCaseValues.push_back(c.getValue()[0]);
308 mergeCascadingInto(c);
309 flushMergedOps();
310 } else if (kind != cir::CaseOpKind::Equal && cascadingCases.size() > 1) {
311 // If a Default, Anyof or Range case is found and there are previous
312 // cascading cases, merge all of them into the last cascading case.
313 // We don't currently fold case range statements with other case
314 // statements.
316 CaseOp lastCascadingCase = cascadingCases.back();
317 mergeCascadingInto(lastCascadingCase);
318 cascadingCases.pop_back();
319 flushMergedOps();
320 } else {
321 cascadingCases.clear();
322 cascadingCaseValues.clear();
323 }
324 }
325
326 // Edge case: all cases are simple cascading cases
327 if (cascadingCases.size() == cases.size()) {
328 CaseOp lastCascadingCase = cascadingCases.back();
329 mergeCascadingInto(lastCascadingCase);
330 cascadingCases.pop_back();
331 flushMergedOps();
332 }
333
334 return changed;
335 }
336};
337
338struct SimplifyVecSplat : public OpRewritePattern<VecSplatOp> {
339 using OpRewritePattern<VecSplatOp>::OpRewritePattern;
340 LogicalResult matchAndRewrite(VecSplatOp op,
341 PatternRewriter &rewriter) const override {
342 mlir::Value splatValue = op.getValue();
343 auto constant = splatValue.getDefiningOp<cir::ConstantOp>();
344 if (!constant)
345 return mlir::failure();
346
347 auto value = constant.getValue();
348 if (!mlir::isa_and_nonnull<cir::IntAttr>(value) &&
349 !mlir::isa_and_nonnull<cir::FPAttr>(value))
350 return mlir::failure();
351
352 cir::VectorType resultType = op.getResult().getType();
353 SmallVector<mlir::Attribute, 16> elements(resultType.getSize(), value);
354 auto constVecAttr = cir::ConstVectorAttr::get(
355 resultType, mlir::ArrayAttr::get(getContext(), elements));
356
357 rewriter.replaceOpWithNewOp<cir::ConstantOp>(op, constVecAttr);
358 return mlir::success();
359 }
360};
361
362//===----------------------------------------------------------------------===//
363// CIRSimplifyPass
364//===----------------------------------------------------------------------===//
365
366struct CIRSimplifyPass : public impl::CIRSimplifyBase<CIRSimplifyPass> {
367 using CIRSimplifyBase::CIRSimplifyBase;
368
369 void runOnOperation() override;
370
371private:
372 void runSimplifyConstantLoad();
373};
374
375void populateMergeCleanupPatterns(RewritePatternSet &patterns) {
376 // clang-format off
377 patterns.add<
378 SimplifyTernary,
379 SimplifySelect,
380 SimplifySwitch,
381 SimplifyVecSplat
382 >(patterns.getContext());
383 // clang-format on
384}
385
386void CIRSimplifyPass::runOnOperation() {
387 // Collect rewrite patterns.
388 RewritePatternSet patterns(&getContext());
389 populateMergeCleanupPatterns(patterns);
390
391 // Collect operations to apply patterns.
392 llvm::SmallVector<Operation *, 16> ops;
393 getOperation()->walk([&](Operation *op) {
394 if (isa<TernaryOp, SelectOp, SwitchOp, VecSplatOp>(op))
395 ops.push_back(op);
396 });
397
398 // Apply patterns.
399 if (applyOpPatternsGreedily(ops, std::move(patterns)).failed())
400 signalPassFailure();
401
402 // SimplifyConstantLoad needs to query dominance information, which could be
403 // invalidated by other rewrite patterns. Thus we run it separately after
404 // other patterns have been applied.
405 runSimplifyConstantLoad();
406}
407
408void CIRSimplifyPass::runSimplifyConstantLoad() {
409 RewritePatternSet patterns(&getContext());
410 patterns.add<SimplifyConstantLoad>(patterns.getContext());
411
412 llvm::SmallVector<Operation *, 16> ops;
413 getOperation()->walk([&](Operation *op) {
414 if (isa<LoadOp>(op))
415 ops.push_back(op);
416 });
417 if (applyOpPatternsGreedily(ops, std::move(patterns)).failed())
418 signalPassFailure();
419}
420
421} // namespace
422
423std::unique_ptr<Pass> mlir::createCIRSimplifyPass() {
424 return std::make_unique<CIRSimplifyPass>();
425}
*collection of selector each with an associated kind and an ordered *collection of selectors A selector has a kind
std::unique_ptr< Pass > createCIRSimplifyPass()
static bool foldRangeCase()