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 resetMergeState = [&]() {
284 cascadingCases.clear();
285 cascadingCaseValues.clear();
286 };
287
288 auto flushMergedOps = [&]() {
289 for (CaseOp &c : cascadingCases)
290 rewriter.eraseOp(c);
291 resetMergeState();
292 };
293
294 auto mergeCascadingInto = [&](CaseOp &target) {
295 rewriter.modifyOpInPlace(target, [&]() {
296 target.setValueAttr(rewriter.getArrayAttr(cascadingCaseValues));
297 target.setKind(CaseOpKind::Anyof);
298 });
299 changed = mlir::success();
300 };
301
302 // Merge all pending cascading cases into the last one collected, which
303 // survives as a distinct `cir.case`; the rest are erased since their
304 // values have been folded into it.
305 auto mergeLastCascadingAndFlush = [&]() {
307 CaseOp lastCascadingCase = cascadingCases.back();
308 mergeCascadingInto(lastCascadingCase);
309 cascadingCases.pop_back();
310 flushMergedOps();
311 };
312
313 for (CaseOp c : cases) {
314 // Cascading cases must be textually adjacent to the previously
315 // collected cascading case. This is false when something is in the
316 // way (e.g. a goto) or when the previous cascading case was found
317 // nested inside a sibling case's body (e.g. a case label that falls
318 // through into a compound statement) rather than next to `c`.
319 bool isAdjacentToLastCascadingCase =
320 !cascadingCases.empty() &&
321 c->getPrevNode() == cascadingCases.back().getOperation();
322
323 if (!cascadingCases.empty() && !isAdjacentToLastCascadingCase) {
324 if (cascadingCases.size() > 1)
325 mergeLastCascadingAndFlush();
326 else
327 resetMergeState();
328 }
329
330 cir::CaseOpKind kind = c.getKind();
331 if (kind == cir::CaseOpKind::Equal &&
332 isa<YieldOp>(c.getCaseRegion().front().front())) {
333 // If the case contains only a YieldOp, collect it for cascading merge
334 cascadingCases.push_back(c);
335 cascadingCaseValues.push_back(c.getValue()[0]);
336 } else if (kind == cir::CaseOpKind::Equal && !cascadingCases.empty()) {
337 // merge previously collected cascading cases
338 cascadingCaseValues.push_back(c.getValue()[0]);
339 mergeCascadingInto(c);
340 flushMergedOps();
341 } else if (kind != cir::CaseOpKind::Equal && cascadingCases.size() > 1) {
342 // If a Default, Anyof or Range case is found and there are previous
343 // cascading cases, merge all of them into the last cascading case.
344 // We don't currently fold case range statements with other case
345 // statements.
346 mergeLastCascadingAndFlush();
347 } else {
348 resetMergeState();
349 }
350 }
351
352 // Edge case: all cases are simple cascading cases
353 if (cascadingCases.size() == cases.size())
354 mergeLastCascadingAndFlush();
355
356 return changed;
357 }
358};
359
360struct SimplifyVecSplat : public OpRewritePattern<VecSplatOp> {
361 using OpRewritePattern<VecSplatOp>::OpRewritePattern;
362 LogicalResult matchAndRewrite(VecSplatOp op,
363 PatternRewriter &rewriter) const override {
364 mlir::Value splatValue = op.getValue();
365 auto constant = splatValue.getDefiningOp<cir::ConstantOp>();
366 if (!constant)
367 return mlir::failure();
368
369 auto value = constant.getValue();
370 if (!mlir::isa_and_nonnull<cir::IntAttr>(value) &&
371 !mlir::isa_and_nonnull<cir::FPAttr>(value))
372 return mlir::failure();
373
374 // FIXME(CIR): We should consider making a matrix constant attribute so that
375 // we can simplify it here too.
377 auto resultType = mlir::dyn_cast<cir::VectorType>(op.getResult().getType());
378 if (!resultType)
379 return mlir::failure();
380 SmallVector<mlir::Attribute, 16> elements(resultType.getSize(), value);
381 auto constVecAttr = cir::ConstVectorAttr::get(
382 resultType, mlir::ArrayAttr::get(getContext(), elements));
383
384 rewriter.replaceOpWithNewOp<cir::ConstantOp>(op, constVecAttr);
385 return mlir::success();
386 }
387};
388
389//===----------------------------------------------------------------------===//
390// CIRSimplifyPass
391//===----------------------------------------------------------------------===//
392
393struct CIRSimplifyPass : public impl::CIRSimplifyBase<CIRSimplifyPass> {
394 using CIRSimplifyBase::CIRSimplifyBase;
395
396 void runOnOperation() override;
397
398private:
399 void runSimplifyConstantLoad();
400};
401
402void populateMergeCleanupPatterns(RewritePatternSet &patterns) {
403 // clang-format off
404 patterns.add<
405 SimplifyTernary,
406 SimplifySelect,
407 SimplifySwitch,
408 SimplifyVecSplat
409 >(patterns.getContext());
410 // clang-format on
411}
412
413void CIRSimplifyPass::runOnOperation() {
414 // Collect rewrite patterns.
415 RewritePatternSet patterns(&getContext());
416 populateMergeCleanupPatterns(patterns);
417
418 // Collect operations to apply patterns.
419 llvm::SmallVector<Operation *, 16> ops;
420 getOperation()->walk([&](Operation *op) {
421 if (isa<TernaryOp, SelectOp, SwitchOp, VecSplatOp>(op))
422 ops.push_back(op);
423 });
424
425 // Apply patterns.
426 if (applyOpPatternsGreedily(ops, std::move(patterns)).failed())
427 signalPassFailure();
428
429 // SimplifyConstantLoad needs to query dominance information, which could be
430 // invalidated by other rewrite patterns. Thus we run it separately after
431 // other patterns have been applied.
432 runSimplifyConstantLoad();
433}
434
435void CIRSimplifyPass::runSimplifyConstantLoad() {
436 RewritePatternSet patterns(&getContext());
437 patterns.add<SimplifyConstantLoad>(patterns.getContext());
438
439 llvm::SmallVector<Operation *, 16> ops;
440 getOperation()->walk([&](Operation *op) {
441 if (isa<LoadOp>(op))
442 ops.push_back(op);
443 });
444 if (applyOpPatternsGreedily(ops, std::move(patterns)).failed())
445 signalPassFailure();
446}
447
448} // namespace
449
450std::unique_ptr<Pass> mlir::createCIRSimplifyPass() {
451 return std::make_unique<CIRSimplifyPass>();
452}
*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()