-
Notifications
You must be signed in to change notification settings - Fork 14.3k
Moving CppEmitter to header file. #106201
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Thank you for submitting a Pull Request (PR) to the LLVM Project! This PR will be automatically labeled and the relevant teams will be notified. If you wish to, you can add reviewers by using the "Reviewers" section on this page. If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers. If you have further questions, they may be answered by the LLVM GitHub User Guide. You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums. |
@llvm/pr-subscribers-mlir-emitc @llvm/pr-subscribers-mlir Author: None (ddubov100) ChangesIn projects lowering MLIR, there might be project-specific translators that support the translation of custom dialects. For projects targeting code from the C family, it would be advantageous to use as many EmitC dialect operations as possible. If they want to benefit from EmitC dialect translation, they have two options:
It might be beneficial for such projects to use CppEmitter as a standalone class with a clear API to translate EmitC dialect operations, allowing for custom translation of unsupported operations. This PR moves CppEmitter to a header file to enable this functionality. Full diff: https://github.com/llvm/llvm-project/pull/106201.diff 2 Files Affected:
diff --git a/mlir/include/mlir/Target/Cpp/CppEmitter.h b/mlir/include/mlir/Target/Cpp/CppEmitter.h
index 99d8696cc8e077..72fba26a0f054a 100644
--- a/mlir/include/mlir/Target/Cpp/CppEmitter.h
+++ b/mlir/include/mlir/Target/Cpp/CppEmitter.h
@@ -13,12 +13,167 @@
#ifndef MLIR_TARGET_CPP_CPPEMITTER_H
#define MLIR_TARGET_CPP_CPPEMITTER_H
+#include "mlir/Dialect/EmitC/IR/EmitC.h"
+#include "mlir/IR/Location.h"
+#include "mlir/IR/Operation.h"
+#include "mlir/Support/IndentedOstream.h"
+
#include "mlir/Support/LLVM.h"
+#include "llvm/ADT/ScopedHashTable.h"
+#include "llvm/Support/raw_ostream.h"
+#include <stack>
namespace mlir {
class Operation;
namespace emitc {
+/// Emitter that uses dialect specific emitters to emit C++ code.
+struct CppEmitter {
+ explicit CppEmitter(raw_ostream &os, bool declareVariablesAtTop);
+
+ /// Emits attribute or returns failure.
+ LogicalResult emitAttribute(Location loc, Attribute attr);
+
+ /// Emits operation 'op' with/without training semicolon or returns failure.
+ LogicalResult emitOperation(Operation &op, bool trailingSemicolon);
+
+ /// Emits type 'type' or returns failure.
+ LogicalResult emitType(Location loc, Type type);
+
+ /// Emits array of types as a std::tuple of the emitted types.
+ /// - emits void for an empty array;
+ /// - emits the type of the only element for arrays of size one;
+ /// - emits a std::tuple otherwise;
+ LogicalResult emitTypes(Location loc, ArrayRef<Type> types);
+
+ /// Emits array of types as a std::tuple of the emitted types independently of
+ /// the array size.
+ LogicalResult emitTupleType(Location loc, ArrayRef<Type> types);
+
+ /// Emits an assignment for a variable which has been declared previously.
+ LogicalResult emitVariableAssignment(OpResult result);
+
+ /// Emits a variable declaration for a result of an operation.
+ LogicalResult emitVariableDeclaration(OpResult result,
+ bool trailingSemicolon);
+
+ /// Emits a declaration of a variable with the given type and name.
+ LogicalResult emitVariableDeclaration(Location loc, Type type,
+ StringRef name);
+
+ /// Emits the variable declaration and assignment prefix for 'op'.
+ /// - emits separate variable followed by std::tie for multi-valued operation;
+ /// - emits single type followed by variable for single result;
+ /// - emits nothing if no value produced by op;
+ /// Emits final '=' operator where a type is produced. Returns failure if
+ /// any result type could not be converted.
+ LogicalResult emitAssignPrefix(Operation &op);
+
+ /// Emits a global variable declaration or definition.
+ LogicalResult emitGlobalVariable(GlobalOp op);
+
+ /// Emits a label for the block.
+ LogicalResult emitLabel(Block &block);
+
+ /// Emits the operands and atttributes of the operation. All operands are
+ /// emitted first and then all attributes in alphabetical order.
+ LogicalResult emitOperandsAndAttributes(Operation &op,
+ ArrayRef<StringRef> exclude = {});
+
+ /// Emits the operands of the operation. All operands are emitted in order.
+ LogicalResult emitOperands(Operation &op);
+
+ /// Emits value as an operands of an operation
+ LogicalResult emitOperand(Value value);
+
+ /// Emit an expression as a C expression.
+ LogicalResult emitExpression(ExpressionOp expressionOp);
+
+ /// Insert the expression representing the operation into the value cache.
+ void cacheDeferredOpResult(Value value, StringRef str);
+
+ /// Return the existing or a new name for a Value.
+ StringRef getOrCreateName(Value val);
+
+ // Returns the textual representation of a subscript operation.
+ std::string getSubscriptName(emitc::SubscriptOp op);
+
+ // Returns the textual representation of a member (of object) operation.
+ std::string createMemberAccess(emitc::MemberOp op);
+
+ // Returns the textual representation of a member of pointer operation.
+ std::string createMemberAccess(emitc::MemberOfPtrOp op);
+
+ /// Return the existing or a new label of a Block.
+ StringRef getOrCreateName(Block &block);
+
+ /// Whether to map an mlir integer to a unsigned integer in C++.
+ bool shouldMapToUnsigned(IntegerType::SignednessSemantics val);
+
+ /// RAII helper function to manage entering/exiting C++ scopes.
+ struct Scope {
+ Scope(CppEmitter &emitter);
+ ~Scope();
+
+ private:
+ llvm::ScopedHashTableScope<Value, std::string> valueMapperScope;
+ llvm::ScopedHashTableScope<Block *, std::string> blockMapperScope;
+ CppEmitter &emitter;
+ };
+
+ /// Returns wether the Value is assigned to a C++ variable in the scope.
+ bool hasValueInScope(Value val);
+
+ // Returns whether a label is assigned to the block.
+ bool hasBlockLabel(Block &block);
+
+ /// Returns the output stream.
+ raw_indented_ostream &ostream() { return os; };
+
+ /// Returns if all variables for op results and basic block arguments need to
+ /// be declared at the beginning of a function.
+ bool shouldDeclareVariablesAtTop() { return declareVariablesAtTop; };
+
+ /// Get expression currently being emitted.
+ ExpressionOp getEmittedExpression() { return emittedExpression; }
+
+ /// Determine whether given value is part of the expression potentially being
+ /// emitted.
+ bool isPartOfCurrentExpression(Value value);
+
+private:
+ using ValueMapper = llvm::ScopedHashTable<Value, std::string>;
+ using BlockMapper = llvm::ScopedHashTable<Block *, std::string>;
+
+ /// Output stream to emit to.
+ raw_indented_ostream os;
+
+ /// Boolean to enforce that all variables for op results and block
+ /// arguments are declared at the beginning of the function. This also
+ /// includes results from ops located in nested regions.
+ bool declareVariablesAtTop;
+
+ /// Map from value to name of C++ variable that contain the name.
+ ValueMapper valueMapper;
+
+ /// Map from block to name of C++ label.
+ BlockMapper blockMapper;
+
+ /// The number of values in the current scope. This is used to declare the
+ /// names of values in a scope.
+ std::stack<int64_t> valueInScopeCount;
+ std::stack<int64_t> labelInScopeCount;
+
+ /// State of the current expression being emitted.
+ ExpressionOp emittedExpression;
+ SmallVector<int> emittedExpressionPrecedence;
+
+ void pushExpressionPrecedence(int precedence);
+ void popExpressionPrecedence();
+ static int lowestPrecedence();
+ int getExpressionPrecedence();
+};
+
/// Translates the given operation to C++ code. The operation or operations in
/// the region of 'op' need almost all be in EmitC dialect. The parameter
/// 'declareVariablesAtTop' enforces that all variables for op results and block
diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
index 1dadb9dd691e70..e9045548730c96 100644
--- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp
+++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
@@ -33,6 +33,44 @@ using namespace mlir;
using namespace mlir::emitc;
using llvm::formatv;
+CppEmitter::Scope::Scope(CppEmitter &emitter)
+ : valueMapperScope(emitter.valueMapper),
+ blockMapperScope(emitter.blockMapper), emitter(emitter) {
+ emitter.valueInScopeCount.push(emitter.valueInScopeCount.top());
+ emitter.labelInScopeCount.push(emitter.labelInScopeCount.top());
+}
+
+CppEmitter::Scope::~Scope() {
+ emitter.valueInScopeCount.pop();
+ emitter.labelInScopeCount.pop();
+}
+
+bool CppEmitter::isPartOfCurrentExpression(Value value) {
+ if (!emittedExpression)
+ return false;
+ Operation *def = value.getDefiningOp();
+ if (!def)
+ return false;
+ auto operandExpression = dyn_cast<ExpressionOp>(def->getParentOp());
+ return operandExpression == emittedExpression;
+}
+
+void CppEmitter::pushExpressionPrecedence(int precedence) {
+ emittedExpressionPrecedence.push_back(precedence);
+}
+
+void CppEmitter::popExpressionPrecedence() {
+ emittedExpressionPrecedence.pop_back();
+}
+
+int CppEmitter::lowestPrecedence() { return 0; }
+
+int CppEmitter::getExpressionPrecedence() {
+ if (emittedExpressionPrecedence.empty())
+ return lowestPrecedence();
+ return emittedExpressionPrecedence.back();
+}
+
/// Convenience functions to produce interleaved output with functions returning
/// a LogicalResult. This is different than those in STLExtras as functions used
/// on each element doesn't return a string.
@@ -111,177 +149,6 @@ static FailureOr<int> getOperatorPrecedence(Operation *operation) {
.Default([](auto op) { return op->emitError("unsupported operation"); });
}
-namespace {
-/// Emitter that uses dialect specific emitters to emit C++ code.
-struct CppEmitter {
- explicit CppEmitter(raw_ostream &os, bool declareVariablesAtTop);
-
- /// Emits attribute or returns failure.
- LogicalResult emitAttribute(Location loc, Attribute attr);
-
- /// Emits operation 'op' with/without training semicolon or returns failure.
- LogicalResult emitOperation(Operation &op, bool trailingSemicolon);
-
- /// Emits type 'type' or returns failure.
- LogicalResult emitType(Location loc, Type type);
-
- /// Emits array of types as a std::tuple of the emitted types.
- /// - emits void for an empty array;
- /// - emits the type of the only element for arrays of size one;
- /// - emits a std::tuple otherwise;
- LogicalResult emitTypes(Location loc, ArrayRef<Type> types);
-
- /// Emits array of types as a std::tuple of the emitted types independently of
- /// the array size.
- LogicalResult emitTupleType(Location loc, ArrayRef<Type> types);
-
- /// Emits an assignment for a variable which has been declared previously.
- LogicalResult emitVariableAssignment(OpResult result);
-
- /// Emits a variable declaration for a result of an operation.
- LogicalResult emitVariableDeclaration(OpResult result,
- bool trailingSemicolon);
-
- /// Emits a declaration of a variable with the given type and name.
- LogicalResult emitVariableDeclaration(Location loc, Type type,
- StringRef name);
-
- /// Emits the variable declaration and assignment prefix for 'op'.
- /// - emits separate variable followed by std::tie for multi-valued operation;
- /// - emits single type followed by variable for single result;
- /// - emits nothing if no value produced by op;
- /// Emits final '=' operator where a type is produced. Returns failure if
- /// any result type could not be converted.
- LogicalResult emitAssignPrefix(Operation &op);
-
- /// Emits a global variable declaration or definition.
- LogicalResult emitGlobalVariable(GlobalOp op);
-
- /// Emits a label for the block.
- LogicalResult emitLabel(Block &block);
-
- /// Emits the operands and atttributes of the operation. All operands are
- /// emitted first and then all attributes in alphabetical order.
- LogicalResult emitOperandsAndAttributes(Operation &op,
- ArrayRef<StringRef> exclude = {});
-
- /// Emits the operands of the operation. All operands are emitted in order.
- LogicalResult emitOperands(Operation &op);
-
- /// Emits value as an operands of an operation
- LogicalResult emitOperand(Value value);
-
- /// Emit an expression as a C expression.
- LogicalResult emitExpression(ExpressionOp expressionOp);
-
- /// Insert the expression representing the operation into the value cache.
- void cacheDeferredOpResult(Value value, StringRef str);
-
- /// Return the existing or a new name for a Value.
- StringRef getOrCreateName(Value val);
-
- // Returns the textual representation of a subscript operation.
- std::string getSubscriptName(emitc::SubscriptOp op);
-
- // Returns the textual representation of a member (of object) operation.
- std::string createMemberAccess(emitc::MemberOp op);
-
- // Returns the textual representation of a member of pointer operation.
- std::string createMemberAccess(emitc::MemberOfPtrOp op);
-
- /// Return the existing or a new label of a Block.
- StringRef getOrCreateName(Block &block);
-
- /// Whether to map an mlir integer to a unsigned integer in C++.
- bool shouldMapToUnsigned(IntegerType::SignednessSemantics val);
-
- /// RAII helper function to manage entering/exiting C++ scopes.
- struct Scope {
- Scope(CppEmitter &emitter)
- : valueMapperScope(emitter.valueMapper),
- blockMapperScope(emitter.blockMapper), emitter(emitter) {
- emitter.valueInScopeCount.push(emitter.valueInScopeCount.top());
- emitter.labelInScopeCount.push(emitter.labelInScopeCount.top());
- }
- ~Scope() {
- emitter.valueInScopeCount.pop();
- emitter.labelInScopeCount.pop();
- }
-
- private:
- llvm::ScopedHashTableScope<Value, std::string> valueMapperScope;
- llvm::ScopedHashTableScope<Block *, std::string> blockMapperScope;
- CppEmitter &emitter;
- };
-
- /// Returns wether the Value is assigned to a C++ variable in the scope.
- bool hasValueInScope(Value val);
-
- // Returns whether a label is assigned to the block.
- bool hasBlockLabel(Block &block);
-
- /// Returns the output stream.
- raw_indented_ostream &ostream() { return os; };
-
- /// Returns if all variables for op results and basic block arguments need to
- /// be declared at the beginning of a function.
- bool shouldDeclareVariablesAtTop() { return declareVariablesAtTop; };
-
- /// Get expression currently being emitted.
- ExpressionOp getEmittedExpression() { return emittedExpression; }
-
- /// Determine whether given value is part of the expression potentially being
- /// emitted.
- bool isPartOfCurrentExpression(Value value) {
- if (!emittedExpression)
- return false;
- Operation *def = value.getDefiningOp();
- if (!def)
- return false;
- auto operandExpression = dyn_cast<ExpressionOp>(def->getParentOp());
- return operandExpression == emittedExpression;
- };
-
-private:
- using ValueMapper = llvm::ScopedHashTable<Value, std::string>;
- using BlockMapper = llvm::ScopedHashTable<Block *, std::string>;
-
- /// Output stream to emit to.
- raw_indented_ostream os;
-
- /// Boolean to enforce that all variables for op results and block
- /// arguments are declared at the beginning of the function. This also
- /// includes results from ops located in nested regions.
- bool declareVariablesAtTop;
-
- /// Map from value to name of C++ variable that contain the name.
- ValueMapper valueMapper;
-
- /// Map from block to name of C++ label.
- BlockMapper blockMapper;
-
- /// The number of values in the current scope. This is used to declare the
- /// names of values in a scope.
- std::stack<int64_t> valueInScopeCount;
- std::stack<int64_t> labelInScopeCount;
-
- /// State of the current expression being emitted.
- ExpressionOp emittedExpression;
- SmallVector<int> emittedExpressionPrecedence;
-
- void pushExpressionPrecedence(int precedence) {
- emittedExpressionPrecedence.push_back(precedence);
- }
- void popExpressionPrecedence() { emittedExpressionPrecedence.pop_back(); }
- static int lowestPrecedence() { return 0; }
- int getExpressionPrecedence() {
- if (emittedExpressionPrecedence.empty())
- return lowestPrecedence();
- return emittedExpressionPrecedence.back();
- }
-};
-} // namespace
-
/// Determine whether expression \p op should be emitted in a deferred way.
static bool hasDeferredEmission(Operation *op) {
return isa_and_nonnull<emitc::GetGlobalOp, emitc::LiteralOp, emitc::MemberOp,
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is intended that the emitter only accepts the EmitC dialect, even though a few exceptions still exist. This was discussed during upstreaming and it was concluded not to expose the internals of the emitter.
With the emitc.verbatim
op, a forked emitter should not be needed, as unsupported ops can be converted to this op. Prior to that, a forked emitter was used within IREE, but we were able to replace it by switching to the emitc.verbatim
op, see iree-org/iree@c15b610. In general, there is room for improvement and further operations should be added to the dialect, but with the availability of the emitc.verbatim
op most downstream projects shouldn't be blocked.
If there is something that cannot be achieved with the existing ops, including the emitc.verbatim
op, please provide an example. I am not supportive of this particular change (as elaborated above) but we're are of course more than happy to accept contributions that improve the dialect!
With that said, you may want to look at the discussion we had around #84973. In particular allow me to point you to #84973 (comment).
@marbre First of all thanks a lot for such a quick response and comprehensive explanation. func.func @arith_minimumf_maximumf(%arg0 : f32, %arg1 : f32) { Lets say arith ops above can't be lowered today to EmitC dialect and are actually some custom complicated ops. Will be glad to hear your thoughts. |
That's correct. The Can you share a concrete example of what code should be generated for your custom ops? Then it's easier to discuss in which way the emitc dialect needs to be extended. |
In projects lowering MLIR, there might be project-specific translators that support the translation of custom dialects. For projects targeting code from the C family, it would be advantageous to use as many EmitC dialect operations as possible. If they want to benefit from EmitC dialect translation, they have two options:
It might be beneficial for such projects to use CppEmitter as a standalone class with a clear API to translate EmitC dialect operations, allowing for custom translation of unsupported operations.
This PR moves CppEmitter to a header file to enable this functionality.