MLIR 24.0.0git
FuncToEmitC.cpp
Go to the documentation of this file.
1//===- FuncToEmitC.cpp - Func to EmitC Patterns -----------------*- C++ -*-===//
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// This file implements patterns to convert the Func dialect to the EmitC
10// dialect.
11//
12//===----------------------------------------------------------------------===//
13
15
20#include "mlir/IR/SymbolTable.h"
22#include "llvm/ADT/StringExtras.h"
23#include "llvm/Support/LogicalResult.h"
24
25using namespace mlir;
26
27namespace {
28
29//===----------------------------------------------------------------------===//
30// Multi-return struct helpers
31//===----------------------------------------------------------------------===//
32
33// Looks up or creates an `emitc.class` named after `types` in the nearest
34// enclosing symbol table of `op`, suitable for packing those types as plain
35// struct fields (field0, field1, ...). If the class already exists it is
36// verified to have exactly the right fields and no methods. Returns the
37// corresponding !emitc.opaque<"struct ..."> type on success.
38static FailureOr<emitc::OpaqueType>
39getOrCreateMultiReturnType(ConversionPatternRewriter &rewriter, Location loc,
40 Operation *op, TypeRange types) {
41 // Build the struct name from the types, e.g. "return_i32_i32". Each type is
42 // printed and non-alphanumeric characters are replaced with '_'.
43 std::string structName = "return";
44 for (Type type : types) {
45 std::string typeName;
46 llvm::raw_string_ostream os(typeName);
47 type.print(os);
48 std::replace_if(
49 typeName.begin(), typeName.end(),
50 [](char c) { return !llvm::isAlnum(c); }, '_');
51 structName += "_" + typeName;
52 }
53
54 // Find the enclosing symbol table and the direct child op within it that
55 // contains `op`; the class will be inserted immediately before that child.
57 Operation *insertBefore = op;
58 while (insertBefore->getParentOp() != symbolTableOp)
59 insertBefore = insertBefore->getParentOp();
60
61 if (Operation *sym = SymbolTable::lookupSymbolIn(symbolTableOp, structName)) {
62 auto classOp = dyn_cast<emitc::ClassOp>(sym);
63 if (!classOp)
64 return emitError(loc) << "symbol '" << structName
65 << "' exists but is not an emitc.class";
66
67 if (classOp.getClassType() != emitc::ClassType::struct_)
68 return emitError(loc)
69 << "existing class '" << structName << "' is not a struct";
70
72 for (Operation &bodyOp : classOp.getBody().front()) {
73 if (isa<emitc::FuncOp>(bodyOp))
74 return emitError(loc) << "existing class '" << structName
75 << "' has methods; expected a plain struct";
76 if (auto fieldOp = dyn_cast<emitc::FieldOp>(bodyOp))
77 fields.push_back(fieldOp);
78 }
79 if (fields.size() != types.size())
80 return emitError(loc) << "existing class '" << structName
81 << "' has wrong number of fields";
82 for (auto [i, fieldOp] : llvm::enumerate(fields)) {
83 if (fieldOp.getSymName() != "field" + std::to_string(i))
84 return emitError(loc) << "existing class '" << structName
85 << "': unexpected field name at index " << i;
86 if (fieldOp.getTypeAttr().getValue() != types[i])
87 return emitError(loc) << "existing class '" << structName
88 << "': wrong type for field " << i;
89 }
90 } else {
91 // Create the ClassOp before `insertBefore`, then restore the insertion
92 // point.
93 auto savedIP = rewriter.saveInsertionPoint();
94 rewriter.setInsertionPoint(insertBefore);
95
96 emitc::ClassOp classOp = emitc::ClassOp::create(
97 rewriter, loc, structName, /*sym_visibility=*/nullptr,
98 /*final_specifier=*/false, emitc::ClassType::struct_);
99 rewriter.createBlock(&classOp.getBody());
100 rewriter.setInsertionPointToStart(&classOp.getBody().front());
101
102 for (auto [i, type] : llvm::enumerate(types)) {
103 auto fieldName = rewriter.getStringAttr("field" + std::to_string(i));
104 emitc::FieldOp::create(rewriter, loc, fieldName,
105 /*sym_visibility=*/nullptr, TypeAttr::get(type),
106 nullptr);
107 }
108
109 rewriter.restoreInsertionPoint(savedIP);
110 }
111 return emitc::OpaqueType::get(rewriter.getContext(), "struct " + structName);
112}
113
114// Packs multiple SSA values into an emitc.class struct variable and loads the
115// result as a single SSA value of the opaque struct type.
116static Value packValuesIntoStruct(ConversionPatternRewriter &rewriter,
117 Location loc, ValueRange values,
118 emitc::OpaqueType structType) {
119 MLIRContext *ctx = rewriter.getContext();
120 auto noInit = emitc::OpaqueAttr::get(ctx, "");
121 Value structLv =
122 emitc::VariableOp::create(rewriter, loc,
123 emitc::LValueType::get(structType), noInit)
124 .getResult();
125 for (auto [i, val] : llvm::enumerate(values)) {
126 Value fieldLv =
127 emitc::MemberOp::create(
128 rewriter, loc, emitc::LValueType::get(val.getType()),
129 rewriter.getStringAttr("field" + std::to_string(i)), structLv)
130 .getResult();
131 emitc::AssignOp::create(rewriter, loc, fieldLv, val);
132 }
133 return emitc::LoadOp::create(rewriter, loc, structType, structLv).getResult();
134}
135
136/// Implement the interface to convert Func to EmitC.
137struct FuncToEmitCDialectInterface : public ConvertToEmitCPatternInterface {
138 FuncToEmitCDialectInterface(Dialect *dialect)
139 : ConvertToEmitCPatternInterface(dialect) {}
140
141 /// Hook for derived dialect interface to provide conversion patterns
142 /// and mark dialect legal for the conversion target.
143 void populateConvertToEmitCConversionPatterns(
144 ConversionTarget &target, TypeConverter &typeConverter,
145 RewritePatternSet &patterns, std::optional<bool> lowerToCpp) const final {
146 populateFuncToEmitCPatterns(typeConverter, patterns,
147 lowerToCpp.value_or(true));
148 }
149};
150} // namespace
151
153 registry.addExtension(+[](MLIRContext *ctx, func::FuncDialect *dialect) {
154 dialect->addInterfaces<FuncToEmitCDialectInterface>();
155 });
156}
157
158//===----------------------------------------------------------------------===//
159// Conversion Patterns
160//===----------------------------------------------------------------------===//
161
162namespace {
163class CallOpConversion final : public OpConversionPattern<func::CallOp> {
164public:
165 CallOpConversion(const TypeConverter &typeConverter, MLIRContext *ctx,
166 bool lowerToCpp)
167 : OpConversionPattern<func::CallOp>(typeConverter, ctx),
168 lowerToCpp(lowerToCpp) {}
169
170 LogicalResult
171 matchAndRewrite(func::CallOp callOp, OpAdaptor adaptor,
172 ConversionPatternRewriter &rewriter) const override {
173 // Do not convert multiple-return functions if lowering target is Cpp.
174 // The translator will emit the return values as an std::tuple.
175 if (callOp.getNumResults() > 1 && lowerToCpp)
176 return rewriter.notifyMatchFailure(
177 callOp, "only functions with zero or one result can be converted");
178
179 SmallVector<Type> convertedResultTypes;
180 for (Type t : callOp.getResultTypes()) {
181 Type resultType = getTypeConverter()->convertType(t);
182 if (!resultType)
183 return rewriter.notifyMatchFailure(callOp,
184 "result type conversion failed");
185 if (isa<emitc::ArrayType>(resultType))
186 return rewriter.notifyMatchFailure(
187 callOp, "function calls returning arrays are not supported");
188 convertedResultTypes.push_back(resultType);
189 }
190
191 if (callOp.getNumResults() <= 1) {
192 auto newCall = rewriter.replaceOpWithNewOp<emitc::CallOp>(
193 callOp, callOp.getCalleeAttr(), convertedResultTypes,
194 adaptor.getOperands());
195 newCall.setArgAttrsAttr(callOp.getArgAttrsAttr());
196 newCall.setResAttrsAttr(callOp.getResAttrsAttr());
197 newCall->setDiscardableAttrs(callOp->getDiscardableAttrDictionary());
198 return success();
199 }
200
201 // Multi-result call: determine the struct type.
202 Location loc = callOp.getLoc();
203
204 auto structType =
205 getOrCreateMultiReturnType(rewriter, loc, callOp, convertedResultTypes);
206 if (failed(structType))
207 return rewriter.notifyMatchFailure(callOp,
208 "incompatible multi-return struct");
209
210 // Emit a call returning the packed struct.
211 Value structVal =
212 emitc::CallOp::create(rewriter, loc, callOp.getCalleeAttr(),
213 TypeRange{*structType}, adaptor.getOperands())
214 .getResult(0);
215
216 // Unpack struct fields to replace the original multiple results.
217 SmallVector<Value> results;
218 for (auto [i, result] : llvm::enumerate(callOp.getResults())) {
219 if (result.use_empty()) {
220 results.push_back(Value()); // No replacement needed.
221 continue;
222 }
223 Type fieldType = convertedResultTypes[i];
224 StringAttr fieldName =
225 rewriter.getStringAttr("field" + std::to_string(i));
226 Value fieldValue = emitc::MemberOp::create(rewriter, loc, fieldType,
227 fieldName, structVal)
228 .getResult();
229 results.push_back(fieldValue);
230 }
231
232 rewriter.replaceOp(callOp, results);
233 return success();
234 }
235
236private:
237 bool lowerToCpp;
238};
239
240class FuncOpConversion final : public OpConversionPattern<func::FuncOp> {
241public:
242 FuncOpConversion(const TypeConverter &typeConverter, MLIRContext *ctx,
243 bool lowerToCpp)
244 : OpConversionPattern<func::FuncOp>(typeConverter, ctx),
245 lowerToCpp(lowerToCpp) {}
246
247 LogicalResult
248 matchAndRewrite(func::FuncOp funcOp, OpAdaptor adaptor,
249 ConversionPatternRewriter &rewriter) const override {
250 FunctionType fnType = funcOp.getFunctionType();
251
252 // Do not convert multiple-return functions if lowering target is Cpp.
253 // The translator will emit the return values as an std::tuple.
254 if (fnType.getNumResults() > 1 && lowerToCpp)
255 return rewriter.notifyMatchFailure(
256 funcOp, "only functions with zero or one result can be converted");
257
258 TypeConverter::SignatureConversion signatureConverter(
259 fnType.getNumInputs());
260 for (const auto &argType : enumerate(fnType.getInputs())) {
261 auto convertedType = getTypeConverter()->convertType(argType.value());
262 if (!convertedType)
263 return rewriter.notifyMatchFailure(funcOp,
264 "argument type conversion failed");
265 signatureConverter.addInputs(argType.index(), convertedType);
266 }
267
268 SmallVector<Type> convertedResultTypes;
269 for (Type t : fnType.getResults()) {
270 Type resultType = getTypeConverter()->convertType(t);
271 if (!resultType)
272 return rewriter.notifyMatchFailure(funcOp,
273 "result type conversion failed");
274 if (isa<emitc::ArrayType>(resultType))
275 return rewriter.notifyMatchFailure(
276 funcOp, "functions returning arrays are not supported");
277 convertedResultTypes.push_back(resultType);
278 }
279
280 Type resultType;
281 if (fnType.getNumResults() == 1) {
282 resultType = convertedResultTypes[0];
283 } else if (fnType.getNumResults() > 1) {
284 auto structTypeOrErr = getOrCreateMultiReturnType(
285 rewriter, funcOp.getLoc(), funcOp, convertedResultTypes);
286 if (failed(structTypeOrErr))
287 return rewriter.notifyMatchFailure(funcOp,
288 "incompatible multi-return struct");
289 resultType = *structTypeOrErr;
290 }
291
292 // Create the converted `emitc.func` op.
293 emitc::FuncOp newFuncOp = emitc::FuncOp::create(
294 rewriter, funcOp.getLoc(), funcOp.getName(),
295 FunctionType::get(rewriter.getContext(),
296 signatureConverter.getConvertedTypes(),
297 resultType ? TypeRange(resultType) : TypeRange()));
298
299 newFuncOp.setArgAttrsAttr(funcOp.getArgAttrsAttr());
300 newFuncOp.setResAttrsAttr(funcOp.getResAttrsAttr());
301 newFuncOp.setVisibility(funcOp.getVisibility());
302
303 // Copy over the discardable attributes.
304 for (const auto &namedAttr :
305 funcOp->getDiscardableAttrDictionary().getValue())
306 newFuncOp->setDiscardableAttr(namedAttr.getName(), namedAttr.getValue());
307
308 // Add `extern` to specifiers if `func.func` is declaration only.
309 if (funcOp.isDeclaration()) {
310 ArrayAttr specifiers = rewriter.getStrArrayAttr({"extern"});
311 newFuncOp.setSpecifiersAttr(specifiers);
312 }
313
314 // Add `static` to specifiers if `func.func` is private but not a
315 // declaration.
316 if (funcOp.isPrivate() && !funcOp.isDeclaration()) {
317 ArrayAttr specifiers = rewriter.getStrArrayAttr({"static"});
318 newFuncOp.setSpecifiersAttr(specifiers);
319 }
320
321 if (!funcOp.isDeclaration()) {
322 rewriter.inlineRegionBefore(funcOp.getBody(), newFuncOp.getBody(),
323 newFuncOp.end());
324 if (failed(rewriter.convertRegionTypes(
325 &newFuncOp.getBody(), *getTypeConverter(), &signatureConverter)))
326 return failure();
327 }
328 rewriter.eraseOp(funcOp);
329
330 return success();
331 }
332
333private:
334 bool lowerToCpp;
335};
336
337class ReturnOpConversion final : public OpConversionPattern<func::ReturnOp> {
338public:
339 ReturnOpConversion(const TypeConverter &typeConverter, MLIRContext *ctx,
340 bool lowerToCpp)
341 : OpConversionPattern<func::ReturnOp>(typeConverter, ctx),
342 lowerToCpp(lowerToCpp) {}
343
344 LogicalResult
345 matchAndRewrite(func::ReturnOp returnOp, OpAdaptor adaptor,
346 ConversionPatternRewriter &rewriter) const override {
347 // Do not convert multiple-return functions if lowering target is Cpp.
348 // The translator will emit the return values as an std::tuple.
349 if (returnOp.getNumOperands() > 1 && lowerToCpp)
350 return rewriter.notifyMatchFailure(
351 returnOp, "only zero or one operand is supported");
352
353 if (llvm::any_of(adaptor.getOperands(), [](Value operand) {
354 return isa<emitc::ArrayType>(operand.getType());
355 }))
356 return rewriter.notifyMatchFailure(returnOp,
357 "returning arrays is not supported");
358
359 if (returnOp.getNumOperands() <= 1) {
360 rewriter.replaceOpWithNewOp<emitc::ReturnOp>(
361 returnOp,
362 returnOp.getNumOperands() ? adaptor.getOperands()[0] : nullptr);
363 return success();
364 }
365
366 // Multi-operand return: pack values into a struct.
367 Location loc = returnOp.getLoc();
368
369 auto structType = getOrCreateMultiReturnType(
370 rewriter, loc, returnOp, adaptor.getOperands().getTypes());
371 if (failed(structType))
372 return rewriter.notifyMatchFailure(returnOp,
373 "incompatible multi-return struct");
374
375 Value structVal =
376 packValuesIntoStruct(rewriter, loc, adaptor.getOperands(), *structType);
377 rewriter.replaceOpWithNewOp<emitc::ReturnOp>(returnOp, structVal);
378 return success();
379 }
380
381private:
382 bool lowerToCpp;
383};
384} // namespace
385
386//===----------------------------------------------------------------------===//
387// Pattern population
388//===----------------------------------------------------------------------===//
389
391 RewritePatternSet &patterns,
392 bool lowerToCpp) {
393 MLIRContext *ctx = patterns.getContext();
394
395 patterns.add<CallOpConversion, FuncOpConversion, ReturnOpConversion>(
396 typeConverter, ctx, lowerToCpp);
397}
return success()
ArrayAttr()
The DialectRegistry maps a dialect namespace to a constructor for the matching dialect.
bool addExtension(TypeID extensionID, std::unique_ptr< DialectExtensionBase > extension)
Add the given extension to the registry.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Definition Operation.h:251
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.
static Operation * lookupSymbolIn(Operation *op, StringAttr symbol)
Returns the operation registered with the given symbol name with the regions of 'symbolTableOp'.
static Operation * getNearestSymbolTable(Operation *from)
Returns the nearest symbol table from a given operation from.
This class provides an abstraction over the various different ranges of value types.
Definition TypeRange.h:40
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
constexpr void enumerate(std::tuple< Tys... > &tuple, CallbackT &&callback)
Definition Matchers.h:344
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:732
Include the generated interface declarations.
void populateFuncToEmitCPatterns(const TypeConverter &typeConverter, RewritePatternSet &patterns, bool lowerToCpp=true)
void registerConvertFuncToEmitCInterface(DialectRegistry &registry)
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.