From 4cc7d60fe3e060e33cb01f054e2d26870d6256b7 Mon Sep 17 00:00:00 2001 From: Matthias Gehre Date: Tue, 18 Feb 2025 15:21:17 +0100 Subject: [PATCH] [MLIR] emitc: Add emitc.file op (#123298) A `emitc.file` represents a file that can be emitted into a single C++ file. This allows to manage multiple source files within the same MLIR module, but emit them into separate files. This feature is opt-in. By default, `mlir-translate` emits all ops outside of `emitc.file` and ignores all `emitc.file` ops and their bodies. When specifying the `-file-id=id` flag, `mlir-translate` emits all ops outside of `emitc.file` and the ops within the `emitc.file` with matching `id`. Example: ```mlir emitc.file "main" { func @func_one() { return } } emitc.file "test" { func @func_two() { return } } ``` `mlir-translate -file-id=main` will emit `func_one` and `mlir-translate -file-id=test` will emit `func_two`. --- mlir/include/mlir/Dialect/EmitC/IR/EmitC.td | 47 ++++++++++++++++ mlir/include/mlir/Target/Cpp/CppEmitter.h | 6 ++- mlir/lib/Dialect/EmitC/IR/EmitC.cpp | 9 ++++ mlir/lib/Target/Cpp/TranslateRegistration.cpp | 7 ++- mlir/lib/Target/Cpp/TranslateToCpp.cpp | 53 ++++++++++++++----- mlir/test/Target/Cpp/file.mlir | 29 ++++++++++ 6 files changed, 136 insertions(+), 15 deletions(-) create mode 100644 mlir/test/Target/Cpp/file.mlir diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td index 4fbce995ce5b8..965634aa7141c 100644 --- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td +++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td @@ -23,6 +23,7 @@ include "mlir/Interfaces/FunctionInterfaces.td" include "mlir/Interfaces/SideEffectInterfaces.td" include "mlir/IR/OpAsmInterface.td" include "mlir/IR/RegionKindInterface.td" +include "mlir/IR/BuiltinAttributes.td" //===----------------------------------------------------------------------===// // EmitC op definitions @@ -56,6 +57,52 @@ def IntegerIndexOrOpaqueType : Type; def FloatIntegerIndexOrOpaqueType : AnyTypeOf<[EmitCFloatType, IntegerIndexOrOpaqueType]>; +def EmitC_FileOp + : EmitC_Op<"file", [IsolatedFromAbove, NoRegionArguments, SymbolTable, + OpAsmOpInterface]#GraphRegionNoTerminator.traits> { + let summary = "A file container operation"; + let description = [{ + A `file` represents a single C/C++ file. + + `mlir-translate` ignores the body of all `emitc.file` ops + unless the `-file-id=id` flag is used. With that flag, all `emitc.file` ops + with matching id are emitted. + + Example: + + ```mlir + emitc.file "main" { + emitc.func @func_one() { + emitc.return + } + } + ``` + }]; + + let arguments = (ins Builtin_StringAttr:$id); + let regions = (region SizedRegion<1>:$bodyRegion); + + let assemblyFormat = "$id attr-dict-with-keyword $bodyRegion"; + let builders = [OpBuilder<(ins CArg<"StringRef">:$id)>]; + let extraClassDeclaration = [{ + /// Construct a file op from the given location with a name. + static FileOp create(Location loc, StringRef name); + + //===------------------------------------------------------------------===// + // OpAsmOpInterface Methods + //===------------------------------------------------------------------===// + + /// EmitC ops in the body can omit their 'emitc.' prefix in the assembly. + static ::llvm::StringRef getDefaultDialect() { + return "emitc"; + } + }]; + + // We need to ensure that the body region has a block; + // the auto-generated builders do not guarantee that. + let skipDefaultBuilders = 1; +} + def EmitC_AddOp : EmitC_BinaryOp<"add", [CExpression]> { let summary = "Addition operation"; let description = [{ diff --git a/mlir/include/mlir/Target/Cpp/CppEmitter.h b/mlir/include/mlir/Target/Cpp/CppEmitter.h index 99d8696cc8e07..7c5747a888261 100644 --- a/mlir/include/mlir/Target/Cpp/CppEmitter.h +++ b/mlir/include/mlir/Target/Cpp/CppEmitter.h @@ -14,6 +14,7 @@ #define MLIR_TARGET_CPP_CPPEMITTER_H #include "mlir/Support/LLVM.h" +#include "llvm/ADT/StringRef.h" namespace mlir { class Operation; @@ -23,8 +24,11 @@ namespace emitc { /// the region of 'op' need almost all be in EmitC dialect. The parameter /// 'declareVariablesAtTop' enforces that all variables for op results and block /// arguments are declared at the beginning of the function. +/// If parameter 'fileId' is non-empty, then body of `emitc.file` ops +/// with matching id are emitted. LogicalResult translateToCpp(Operation *op, raw_ostream &os, - bool declareVariablesAtTop = false); + bool declareVariablesAtTop = false, + StringRef fileId = {}); } // namespace emitc } // namespace mlir diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp index 728a2d33f46e7..de85ec4b2695c 100644 --- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp +++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp @@ -1289,6 +1289,15 @@ void SwitchOp::getRegionInvocationBounds( bounds.emplace_back(/*lb=*/0, /*ub=*/regIndex == liveIndex); } +//===----------------------------------------------------------------------===// +// FileOp +//===----------------------------------------------------------------------===// +void FileOp::build(OpBuilder &builder, OperationState &state, StringRef id) { + state.addRegion()->emplaceBlock(); + state.attributes.push_back( + builder.getNamedAttr("id", builder.getStringAttr(id))); +} + //===----------------------------------------------------------------------===// // TableGen'd op method definitions //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Target/Cpp/TranslateRegistration.cpp b/mlir/lib/Target/Cpp/TranslateRegistration.cpp index 1aa98834a73f4..2108ffd414c56 100644 --- a/mlir/lib/Target/Cpp/TranslateRegistration.cpp +++ b/mlir/lib/Target/Cpp/TranslateRegistration.cpp @@ -29,12 +29,17 @@ void registerToCppTranslation() { llvm::cl::desc("Declare variables at top when emitting C/C++"), llvm::cl::init(false)); + static llvm::cl::opt fileId( + "file-id", llvm::cl::desc("Emit emitc.file ops with matching id"), + llvm::cl::init("")); + TranslateFromMLIRRegistration reg( "mlir-to-cpp", "translate from mlir to cpp", [](Operation *op, raw_ostream &output) { return emitc::translateToCpp( op, output, - /*declareVariablesAtTop=*/declareVariablesAtTop); + /*declareVariablesAtTop=*/declareVariablesAtTop, + /*fileId=*/fileId); }, [](DialectRegistry ®istry) { // clang-format off diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp index 3ba1244e637ff..3276ed9d394d2 100644 --- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp +++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp @@ -114,7 +114,8 @@ static FailureOr getOperatorPrecedence(Operation *operation) { namespace { /// Emitter that uses dialect specific emitters to emit C++ code. struct CppEmitter { - explicit CppEmitter(raw_ostream &os, bool declareVariablesAtTop); + explicit CppEmitter(raw_ostream &os, bool declareVariablesAtTop, + StringRef fileId); /// Emits attribute or returns failure. LogicalResult emitAttribute(Location loc, Attribute attr); @@ -231,6 +232,11 @@ struct CppEmitter { /// be declared at the beginning of a function. bool shouldDeclareVariablesAtTop() { return declareVariablesAtTop; }; + /// Returns whether this file op should be emitted + bool shouldEmitFile(FileOp file) { + return !fileId.empty() && file.getId() == fileId; + } + /// Get expression currently being emitted. ExpressionOp getEmittedExpression() { return emittedExpression; } @@ -258,6 +264,9 @@ struct CppEmitter { /// includes results from ops located in nested regions. bool declareVariablesAtTop; + /// Only emit file ops whos id matches this value. + std::string fileId; + /// Map from value to name of C++ variable that contain the name. ValueMapper valueMapper; @@ -963,6 +972,19 @@ static LogicalResult printOperation(CppEmitter &emitter, ModuleOp moduleOp) { return success(); } +static LogicalResult printOperation(CppEmitter &emitter, FileOp file) { + if (!emitter.shouldEmitFile(file)) + return success(); + + CppEmitter::Scope scope(emitter); + + for (Operation &op : file) { + if (failed(emitter.emitOperation(op, /*trailingSemicolon=*/false))) + return failure(); + } + return success(); +} + static LogicalResult printFunctionArgs(CppEmitter &emitter, Operation *functionOp, ArrayRef arguments) { @@ -1162,8 +1184,10 @@ static LogicalResult printOperation(CppEmitter &emitter, return success(); } -CppEmitter::CppEmitter(raw_ostream &os, bool declareVariablesAtTop) - : os(os), declareVariablesAtTop(declareVariablesAtTop) { +CppEmitter::CppEmitter(raw_ostream &os, bool declareVariablesAtTop, + StringRef fileId) + : os(os), declareVariablesAtTop(declareVariablesAtTop), + fileId(fileId.str()) { valueInScopeCount.push(0); labelInScopeCount.push(0); } @@ -1558,12 +1582,13 @@ LogicalResult CppEmitter::emitOperation(Operation &op, bool trailingSemicolon) { emitc::BitwiseRightShiftOp, emitc::BitwiseXorOp, emitc::CallOp, emitc::CallOpaqueOp, emitc::CastOp, emitc::CmpOp, emitc::ConditionalOp, emitc::ConstantOp, emitc::DeclareFuncOp, - emitc::DivOp, emitc::ExpressionOp, emitc::ForOp, emitc::FuncOp, - emitc::GlobalOp, emitc::IfOp, emitc::IncludeOp, emitc::LoadOp, - emitc::LogicalAndOp, emitc::LogicalNotOp, emitc::LogicalOrOp, - emitc::MulOp, emitc::RemOp, emitc::ReturnOp, emitc::SubOp, - emitc::SwitchOp, emitc::UnaryMinusOp, emitc::UnaryPlusOp, - emitc::VariableOp, emitc::VerbatimOp>( + emitc::DivOp, emitc::ExpressionOp, emitc::FileOp, emitc::ForOp, + emitc::FuncOp, emitc::GlobalOp, emitc::IfOp, emitc::IncludeOp, + emitc::LoadOp, emitc::LogicalAndOp, emitc::LogicalNotOp, + emitc::LogicalOrOp, emitc::MulOp, emitc::RemOp, emitc::ReturnOp, + emitc::SubOp, emitc::SwitchOp, emitc::UnaryMinusOp, + emitc::UnaryPlusOp, emitc::VariableOp, emitc::VerbatimOp>( + [&](auto op) { return printOperation(*this, op); }) // Func ops. .Case( @@ -1606,8 +1631,9 @@ LogicalResult CppEmitter::emitOperation(Operation &op, bool trailingSemicolon) { // Never emit a semicolon for some operations, especially if endening with // `}`. trailingSemicolon &= - !isa(op); + !isa( + op); os << (trailingSemicolon ? ";\n" : "\n"); @@ -1743,7 +1769,8 @@ LogicalResult CppEmitter::emitTupleType(Location loc, ArrayRef types) { } LogicalResult emitc::translateToCpp(Operation *op, raw_ostream &os, - bool declareVariablesAtTop) { - CppEmitter emitter(os, declareVariablesAtTop); + bool declareVariablesAtTop, + StringRef fileId) { + CppEmitter emitter(os, declareVariablesAtTop, fileId); return emitter.emitOperation(*op, /*trailingSemicolon=*/false); } diff --git a/mlir/test/Target/Cpp/file.mlir b/mlir/test/Target/Cpp/file.mlir new file mode 100644 index 0000000000000..262d3cdac27d4 --- /dev/null +++ b/mlir/test/Target/Cpp/file.mlir @@ -0,0 +1,29 @@ +// RUN: mlir-translate -mlir-to-cpp %s | FileCheck %s --check-prefix NO-FILTER +// RUN: mlir-translate -mlir-to-cpp -file-id=non-existing %s | FileCheck %s --check-prefix NON-EXISTING +// RUN: mlir-translate -mlir-to-cpp -file-id=file_one %s | FileCheck %s --check-prefix FILE-ONE +// RUN: mlir-translate -mlir-to-cpp -file-id=file_two %s | FileCheck %s --check-prefix FILE-TWO + + +// NO-FILTER-NOT: func_one +// NO-FILTER-NOT: func_two + +// NON-EXISTING-NOT: func_one +// NON-EXISTING-NOT: func_two + +// FILE-ONE: func_one +// FILE-ONE-NOT: func_two + +// FILE-TWO-NOT: func_one +// FILE-TWO: func_two + +emitc.file "file_one" { + emitc.func @func_one(%arg: f32) { + emitc.return + } +} + +emitc.file "file_two" { + emitc.func @func_two(%arg: f32) { + emitc.return + } +}