MLIR 24.0.0git
StructuralTypeConversions.cpp
Go to the documentation of this file.
1//===- StructuralTypeConversions.cpp - scf structural type conversions ----===//
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
12#include <optional>
13
14using namespace mlir;
15using namespace mlir::scf;
16
17namespace {
18
19/// Flatten the given value ranges into a single vector of values.
22 for (const auto &vals : values)
23 llvm::append_range(result, vals);
24 return result;
25}
26
27// CRTP
28// A base class that takes care of 1:N type conversion, which maps the converted
29// op results (computed by the derived class) and materializes 1:N conversion.
30template <typename SourceOp, typename ConcretePattern>
31class Structural1ToNConversionPattern : public OpConversionPattern<SourceOp> {
32public:
33 using OpConversionPattern<SourceOp>::typeConverter;
34 using OpConversionPattern<SourceOp>::OpConversionPattern;
35 using OneToNOpAdaptor =
36 typename OpConversionPattern<SourceOp>::OneToNOpAdaptor;
37
38 //
39 // Derived classes should provide the following method which performs the
40 // actual conversion. It should return std::nullopt upon conversion failure
41 // and return the converted operation upon success.
42 //
43 // std::optional<SourceOp> convertSourceOp(
44 // SourceOp op, OneToNOpAdaptor adaptor,
45 // ConversionPatternRewriter &rewriter,
46 // TypeRange dstTypes) const;
47
48 LogicalResult
49 matchAndRewrite(SourceOp op, OneToNOpAdaptor adaptor,
50 ConversionPatternRewriter &rewriter) const override {
51 SmallVector<Type> dstTypes;
52 SmallVector<unsigned> offsets;
53 offsets.push_back(0);
54 // Do the type conversion and record the offsets.
55 for (Value v : op.getResults()) {
56 if (failed(typeConverter->convertType(v, dstTypes)))
57 return rewriter.notifyMatchFailure(op, "could not convert result type");
58 offsets.push_back(dstTypes.size());
59 }
60
61 // Calls the actual converter implementation to convert the operation.
62 std::optional<SourceOp> newOp =
63 static_cast<const ConcretePattern *>(this)->convertSourceOp(
64 op, adaptor, rewriter, dstTypes);
65
66 if (!newOp)
67 return rewriter.notifyMatchFailure(op, "could not convert operation");
68
69 // Packs the return value.
70 SmallVector<ValueRange> packedRets;
71 for (unsigned i = 1, e = offsets.size(); i < e; i++) {
72 unsigned start = offsets[i - 1], end = offsets[i];
73 unsigned len = end - start;
74 ValueRange mappedValue = newOp->getResults().slice(start, len);
75 packedRets.push_back(mappedValue);
76 }
77
78 rewriter.replaceOpWithMultiple(op, packedRets);
79 return success();
80 }
81};
82
83class ConvertForOpTypes
84 : public Structural1ToNConversionPattern<ForOp, ConvertForOpTypes> {
85public:
86 using Structural1ToNConversionPattern::Structural1ToNConversionPattern;
87
88 // The callback required by CRTP.
89 std::optional<ForOp> convertSourceOp(ForOp op, OneToNOpAdaptor adaptor,
90 ConversionPatternRewriter &rewriter,
91 TypeRange dstTypes) const {
92 // Loop bounds are single operands in the SCF IR. A 1:N conversion may
93 // produce zero or multiple values, in which case this operation cannot be
94 // reconstructed.
95 if (!llvm::hasSingleElement(adaptor.getLowerBound()) ||
96 !llvm::hasSingleElement(adaptor.getUpperBound()) ||
97 !llvm::hasSingleElement(adaptor.getStep()))
98 return std::nullopt;
99
100 // Create a empty new op and inline the regions from the old op.
101 //
102 // This is a little bit tricky. We have two concerns here:
103 //
104 // 1. We cannot update the op in place because the dialect conversion
105 // framework does not track type changes for ops updated in place, so it
106 // won't insert appropriate materializations on the changed result types.
107 // PR47938 tracks this issue, but it seems hard to fix. Instead, we need
108 // to clone the op.
109 //
110 // 2. We need to reuse the original region instead of cloning it, otherwise
111 // the dialect conversion framework thinks that we just inserted all the
112 // cloned child ops. But what we want is to "take" the child regions and let
113 // the dialect conversion framework continue recursively into ops inside
114 // those regions (which are already in its worklist; inlining them into the
115 // new op's regions doesn't remove the child ops from the worklist).
116
117 // convertRegionTypes already takes care of 1:N conversion.
118 if (failed(rewriter.convertRegionTypes(&op.getRegion(), *typeConverter)))
119 return std::nullopt;
120
121 // We can not do clone as the number of result types after conversion
122 // might be different.
123 ForOp newOp = ForOp::create(rewriter, op.getLoc(),
124 llvm::getSingleElement(adaptor.getLowerBound()),
125 llvm::getSingleElement(adaptor.getUpperBound()),
126 llvm::getSingleElement(adaptor.getStep()),
127 flattenValues(adaptor.getInitArgs()),
128 /*bodyBuilder=*/nullptr, op.getUnsignedCmp());
129
130 // Reserve whatever attributes in the original op.
131 newOp->setDiscardableAttrs(op->getDiscardableAttrDictionary().getValue());
132
133 // We do not need the empty block created by rewriter.
134 rewriter.eraseBlock(newOp.getBody(0));
135 // Inline the type converted region from the original operation.
136 rewriter.inlineRegionBefore(op.getRegion(), newOp.getRegion(),
137 newOp.getRegion().end());
138 return newOp;
139 }
140};
141} // namespace
142
143namespace {
144class ConvertIfOpTypes
145 : public Structural1ToNConversionPattern<IfOp, ConvertIfOpTypes> {
146public:
147 using Structural1ToNConversionPattern::Structural1ToNConversionPattern;
148
149 std::optional<IfOp> convertSourceOp(IfOp op, OneToNOpAdaptor adaptor,
150 ConversionPatternRewriter &rewriter,
151 TypeRange dstTypes) const {
152 if (!llvm::hasSingleElement(adaptor.getCondition()))
153 return std::nullopt;
154
155 IfOp newOp =
156 IfOp::create(rewriter, op.getLoc(), dstTypes,
157 llvm::getSingleElement(adaptor.getCondition()), true);
158 newOp->setDiscardableAttrs(op->getDiscardableAttrDictionary().getValue());
159
160 // We do not need the empty blocks created by rewriter.
161 rewriter.eraseBlock(newOp.elseBlock());
162 rewriter.eraseBlock(newOp.thenBlock());
163
164 // Inlines block from the original operation.
165 rewriter.inlineRegionBefore(op.getThenRegion(), newOp.getThenRegion(),
166 newOp.getThenRegion().end());
167 rewriter.inlineRegionBefore(op.getElseRegion(), newOp.getElseRegion(),
168 newOp.getElseRegion().end());
169
170 return newOp;
171 }
172};
173} // namespace
174
175namespace {
176class ConvertWhileOpTypes
177 : public Structural1ToNConversionPattern<WhileOp, ConvertWhileOpTypes> {
178public:
179 using Structural1ToNConversionPattern::Structural1ToNConversionPattern;
180
181 std::optional<WhileOp> convertSourceOp(WhileOp op, OneToNOpAdaptor adaptor,
182 ConversionPatternRewriter &rewriter,
183 TypeRange dstTypes) const {
184 auto newOp = WhileOp::create(rewriter, op.getLoc(), dstTypes,
185 flattenValues(adaptor.getOperands()));
186
187 for (auto i : {0u, 1u}) {
188 if (failed(rewriter.convertRegionTypes(&op.getRegion(i), *typeConverter)))
189 return std::nullopt;
190 auto &dstRegion = newOp.getRegion(i);
191 rewriter.inlineRegionBefore(op.getRegion(i), dstRegion, dstRegion.end());
192 }
193 return newOp;
194 }
195};
196} // namespace
197
198namespace {
199class ConvertIndexSwitchOpTypes
200 : public Structural1ToNConversionPattern<IndexSwitchOp,
201 ConvertIndexSwitchOpTypes> {
202public:
203 using Structural1ToNConversionPattern::Structural1ToNConversionPattern;
204
205 std::optional<IndexSwitchOp>
206 convertSourceOp(IndexSwitchOp op, OneToNOpAdaptor adaptor,
207 ConversionPatternRewriter &rewriter,
208 TypeRange dstTypes) const {
209 auto newOp =
210 IndexSwitchOp::create(rewriter, op.getLoc(), dstTypes, op.getArg(),
211 op.getCases(), op.getNumCases());
212
213 for (unsigned i = 0u; i < op.getNumRegions(); i++) {
214 auto &dstRegion = newOp.getRegion(i);
215 rewriter.inlineRegionBefore(op.getRegion(i), dstRegion, dstRegion.end());
216 }
217 return newOp;
218 }
219};
220} // namespace
221
222namespace {
223// When the result types of a ForOp/IfOp get changed, the operand types of the
224// corresponding yield op need to be changed. In order to trigger the
225// appropriate type conversions / materializations, we need a dummy pattern.
226class ConvertYieldOpTypes : public OpConversionPattern<scf::YieldOp> {
227public:
228 using OpConversionPattern::OpConversionPattern;
229 LogicalResult
230 matchAndRewrite(scf::YieldOp op, OneToNOpAdaptor adaptor,
231 ConversionPatternRewriter &rewriter) const override {
232 rewriter.replaceOpWithNewOp<scf::YieldOp>(
233 op, flattenValues(adaptor.getOperands()));
234 return success();
235 }
236};
237} // namespace
238
239namespace {
240class ConvertConditionOpTypes : public OpConversionPattern<ConditionOp> {
241public:
242 using OpConversionPattern<ConditionOp>::OpConversionPattern;
243 LogicalResult
244 matchAndRewrite(ConditionOp op, OneToNOpAdaptor adaptor,
245 ConversionPatternRewriter &rewriter) const override {
246 rewriter.modifyOpInPlace(
247 op, [&]() { op->setOperands(flattenValues(adaptor.getOperands())); });
248 return success();
249 }
250};
251} // namespace
252
254 const TypeConverter &typeConverter, RewritePatternSet &patterns,
255 PatternBenefit benefit) {
256 patterns.add<ConvertForOpTypes, ConvertIfOpTypes, ConvertYieldOpTypes,
257 ConvertWhileOpTypes, ConvertConditionOpTypes,
258 ConvertIndexSwitchOpTypes>(typeConverter, patterns.getContext(),
259 benefit);
260}
261
263 const TypeConverter &typeConverter, ConversionTarget &target) {
264 target.addDynamicallyLegalOp<ForOp, IfOp, IndexSwitchOp>(
265 [&](Operation *op) { return typeConverter.isLegal(op->getResults()); });
266 target.addDynamicallyLegalOp<scf::YieldOp>([&](scf::YieldOp op) {
267 // We only have conversions for a subset of ops that use scf.yield
268 // terminators.
269 if (!isa<ForOp, IfOp, WhileOp, IndexSwitchOp>(op->getParentOp()))
270 return true;
271 return typeConverter.isLegal(op.getOperands());
272 });
273 target.addDynamicallyLegalOp<WhileOp, ConditionOp>(
274 [&](Operation *op) { return typeConverter.isLegal(op); });
275}
276
return success()
static SmallVector< Value > flattenValues(ArrayRef< ValueRange > values)
Flatten the given value ranges into a single vector of values.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
This class represents the benefit of a pattern match in a unitless scheme that ranges from 0 (very li...
MLIRContext * getContext() const
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:732
void populateSCFStructuralTypeConversions(const TypeConverter &typeConverter, RewritePatternSet &patterns, PatternBenefit benefit=1)
Similar to populateSCFStructuralTypeConversionsAndLegality but does not populate the conversion targe...
void populateSCFStructuralTypeConversionsAndLegality(const TypeConverter &typeConverter, RewritePatternSet &patterns, ConversionTarget &target, PatternBenefit benefit=1)
Populates patterns for SCF structural type conversions and sets up the provided ConversionTarget with...
void populateSCFStructuralTypeConversionTarget(const TypeConverter &typeConverter, ConversionTarget &target)
Updates the ConversionTarget with dynamic legality of SCF operations based on the provided type conve...
Include the generated interface declarations.