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