blob: 90f4f3080ad234951eb44ada726ea2efc472da97 [file] [log] [blame]
//===- TranslateToCpp.cpp - Translating to C++ calls ----------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "mlir/Dialect/EmitC/IR/EmitC.h"
#include "mlir/Dialect/SCF/SCF.h"
#include "mlir/Dialect/StandardOps/IR/Ops.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/Dialect.h"
#include "mlir/IR/Operation.h"
#include "mlir/Support/IndentedOstream.h"
#include "mlir/Target/Cpp/CppEmitter.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/TypeSwitch.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/FormatVariadic.h"
#define DEBUG_TYPE "translate-to-cpp"
using namespace mlir;
using namespace mlir::emitc;
using llvm::formatv;
/// 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.
template <typename ForwardIterator, typename UnaryFunctor,
typename NullaryFunctor>
inline LogicalResult
interleaveWithError(ForwardIterator begin, ForwardIterator end,
UnaryFunctor eachFn, NullaryFunctor betweenFn) {
if (begin == end)
return success();
if (failed(eachFn(*begin)))
return failure();
++begin;
for (; begin != end; ++begin) {
betweenFn();
if (failed(eachFn(*begin)))
return failure();
}
return success();
}
template <typename Container, typename UnaryFunctor, typename NullaryFunctor>
inline LogicalResult interleaveWithError(const Container &c,
UnaryFunctor eachFn,
NullaryFunctor betweenFn) {
return interleaveWithError(c.begin(), c.end(), eachFn, betweenFn);
}
template <typename Container, typename UnaryFunctor>
inline LogicalResult interleaveCommaWithError(const Container &c,
raw_ostream &os,
UnaryFunctor eachFn) {
return interleaveWithError(c.begin(), c.end(), eachFn, [&]() { os << ", "; });
}
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 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 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);
/// Return the existing or a new name for a Value.
StringRef getOrCreateName(Value val);
/// 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; };
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;
};
} // namespace
static LogicalResult printConstantOp(CppEmitter &emitter, Operation *operation,
Attribute value) {
OpResult result = operation->getResult(0);
// Only emit an assignment as the variable was already declared when printing
// the FuncOp.
if (emitter.shouldDeclareVariablesAtTop()) {
// Skip the assignment if the emitc.constant has no value.
if (auto oAttr = value.dyn_cast<emitc::OpaqueAttr>()) {
if (oAttr.getValue().empty())
return success();
}
if (failed(emitter.emitVariableAssignment(result)))
return failure();
return emitter.emitAttribute(operation->getLoc(), value);
}
// Emit a variable declaration for an emitc.constant op without value.
if (auto oAttr = value.dyn_cast<emitc::OpaqueAttr>()) {
if (oAttr.getValue().empty())
// The semicolon gets printed by the emitOperation function.
return emitter.emitVariableDeclaration(result,
/*trailingSemicolon=*/false);
}
// Emit a variable declaration.
if (failed(emitter.emitAssignPrefix(*operation)))
return failure();
return emitter.emitAttribute(operation->getLoc(), value);
}
static LogicalResult printOperation(CppEmitter &emitter,
emitc::ConstantOp constantOp) {
Operation *operation = constantOp.getOperation();
Attribute value = constantOp.value();
return printConstantOp(emitter, operation, value);
}
static LogicalResult printOperation(CppEmitter &emitter,
arith::ConstantOp constantOp) {
Operation *operation = constantOp.getOperation();
Attribute value = constantOp.getValue();
return printConstantOp(emitter, operation, value);
}
static LogicalResult printOperation(CppEmitter &emitter,
mlir::ConstantOp constantOp) {
Operation *operation = constantOp.getOperation();
Attribute value = constantOp.getValue();
return printConstantOp(emitter, operation, value);
}
static LogicalResult printOperation(CppEmitter &emitter, BranchOp branchOp) {
raw_ostream &os = emitter.ostream();
Block &successor = *branchOp.getSuccessor();
for (auto pair :
llvm::zip(branchOp.getOperands(), successor.getArguments())) {
Value &operand = std::get<0>(pair);
BlockArgument &argument = std::get<1>(pair);
os << emitter.getOrCreateName(argument) << " = "
<< emitter.getOrCreateName(operand) << ";\n";
}
os << "goto ";
if (!(emitter.hasBlockLabel(successor)))
return branchOp.emitOpError("unable to find label for successor block");
os << emitter.getOrCreateName(successor);
return success();
}
static LogicalResult printOperation(CppEmitter &emitter,
CondBranchOp condBranchOp) {
raw_indented_ostream &os = emitter.ostream();
Block &trueSuccessor = *condBranchOp.getTrueDest();
Block &falseSuccessor = *condBranchOp.getFalseDest();
os << "if (" << emitter.getOrCreateName(condBranchOp.getCondition())
<< ") {\n";
os.indent();
// If condition is true.
for (auto pair : llvm::zip(condBranchOp.getTrueOperands(),
trueSuccessor.getArguments())) {
Value &operand = std::get<0>(pair);
BlockArgument &argument = std::get<1>(pair);
os << emitter.getOrCreateName(argument) << " = "
<< emitter.getOrCreateName(operand) << ";\n";
}
os << "goto ";
if (!(emitter.hasBlockLabel(trueSuccessor))) {
return condBranchOp.emitOpError("unable to find label for successor block");
}
os << emitter.getOrCreateName(trueSuccessor) << ";\n";
os.unindent() << "} else {\n";
os.indent();
// If condition is false.
for (auto pair : llvm::zip(condBranchOp.getFalseOperands(),
falseSuccessor.getArguments())) {
Value &operand = std::get<0>(pair);
BlockArgument &argument = std::get<1>(pair);
os << emitter.getOrCreateName(argument) << " = "
<< emitter.getOrCreateName(operand) << ";\n";
}
os << "goto ";
if (!(emitter.hasBlockLabel(falseSuccessor))) {
return condBranchOp.emitOpError()
<< "unable to find label for successor block";
}
os << emitter.getOrCreateName(falseSuccessor) << ";\n";
os.unindent() << "}";
return success();
}
static LogicalResult printOperation(CppEmitter &emitter, mlir::CallOp callOp) {
if (failed(emitter.emitAssignPrefix(*callOp.getOperation())))
return failure();
raw_ostream &os = emitter.ostream();
os << callOp.getCallee() << "(";
if (failed(emitter.emitOperands(*callOp.getOperation())))
return failure();
os << ")";
return success();
}
static LogicalResult printOperation(CppEmitter &emitter, emitc::CallOp callOp) {
raw_ostream &os = emitter.ostream();
Operation &op = *callOp.getOperation();
if (failed(emitter.emitAssignPrefix(op)))
return failure();
os << callOp.callee();
auto emitArgs = [&](Attribute attr) -> LogicalResult {
if (auto t = attr.dyn_cast<IntegerAttr>()) {
// Index attributes are treated specially as operand index.
if (t.getType().isIndex()) {
int64_t idx = t.getInt();
if ((idx < 0) || (idx >= op.getNumOperands()))
return op.emitOpError("invalid operand index");
if (!emitter.hasValueInScope(op.getOperand(idx)))
return op.emitOpError("operand ")
<< idx << "'s value not defined in scope";
os << emitter.getOrCreateName(op.getOperand(idx));
return success();
}
}
if (failed(emitter.emitAttribute(op.getLoc(), attr)))
return failure();
return success();
};
if (callOp.template_args()) {
os << "<";
if (failed(interleaveCommaWithError(*callOp.template_args(), os, emitArgs)))
return failure();
os << ">";
}
os << "(";
LogicalResult emittedArgs =
callOp.args() ? interleaveCommaWithError(*callOp.args(), os, emitArgs)
: emitter.emitOperands(op);
if (failed(emittedArgs))
return failure();
os << ")";
return success();
}
static LogicalResult printOperation(CppEmitter &emitter,
emitc::ApplyOp applyOp) {
raw_ostream &os = emitter.ostream();
Operation &op = *applyOp.getOperation();
if (failed(emitter.emitAssignPrefix(op)))
return failure();
os << applyOp.applicableOperator();
os << emitter.getOrCreateName(applyOp.getOperand());
return success();
}
static LogicalResult printOperation(CppEmitter &emitter,
emitc::IncludeOp includeOp) {
raw_ostream &os = emitter.ostream();
os << "#include ";
if (includeOp.is_standard_include())
os << "<" << includeOp.include() << ">";
else
os << "\"" << includeOp.include() << "\"";
return success();
}
static LogicalResult printOperation(CppEmitter &emitter, scf::ForOp forOp) {
raw_indented_ostream &os = emitter.ostream();
OperandRange operands = forOp.getIterOperands();
Block::BlockArgListType iterArgs = forOp.getRegionIterArgs();
Operation::result_range results = forOp.getResults();
if (!emitter.shouldDeclareVariablesAtTop()) {
for (OpResult result : results) {
if (failed(emitter.emitVariableDeclaration(result,
/*trailingSemicolon=*/true)))
return failure();
}
}
for (auto pair : llvm::zip(iterArgs, operands)) {
if (failed(emitter.emitType(forOp.getLoc(), std::get<0>(pair).getType())))
return failure();
os << " " << emitter.getOrCreateName(std::get<0>(pair)) << " = ";
os << emitter.getOrCreateName(std::get<1>(pair)) << ";";
os << "\n";
}
os << "for (";
if (failed(
emitter.emitType(forOp.getLoc(), forOp.getInductionVar().getType())))
return failure();
os << " ";
os << emitter.getOrCreateName(forOp.getInductionVar());
os << " = ";
os << emitter.getOrCreateName(forOp.lowerBound());
os << "; ";
os << emitter.getOrCreateName(forOp.getInductionVar());
os << " < ";
os << emitter.getOrCreateName(forOp.upperBound());
os << "; ";
os << emitter.getOrCreateName(forOp.getInductionVar());
os << " += ";
os << emitter.getOrCreateName(forOp.step());
os << ") {\n";
os.indent();
Region &forRegion = forOp.region();
auto regionOps = forRegion.getOps();
// We skip the trailing yield op because this updates the result variables
// of the for op in the generated code. Instead we update the iterArgs at
// the end of a loop iteration and set the result variables after the for
// loop.
for (auto it = regionOps.begin(); std::next(it) != regionOps.end(); ++it) {
if (failed(emitter.emitOperation(*it, /*trailingSemicolon=*/true)))
return failure();
}
Operation *yieldOp = forRegion.getBlocks().front().getTerminator();
// Copy yield operands into iterArgs at the end of a loop iteration.
for (auto pair : llvm::zip(iterArgs, yieldOp->getOperands())) {
BlockArgument iterArg = std::get<0>(pair);
Value operand = std::get<1>(pair);
os << emitter.getOrCreateName(iterArg) << " = "
<< emitter.getOrCreateName(operand) << ";\n";
}
os.unindent() << "}";
// Copy iterArgs into results after the for loop.
for (auto pair : llvm::zip(results, iterArgs)) {
OpResult result = std::get<0>(pair);
BlockArgument iterArg = std::get<1>(pair);
os << "\n"
<< emitter.getOrCreateName(result) << " = "
<< emitter.getOrCreateName(iterArg) << ";";
}
return success();
}
static LogicalResult printOperation(CppEmitter &emitter, scf::IfOp ifOp) {
raw_indented_ostream &os = emitter.ostream();
if (!emitter.shouldDeclareVariablesAtTop()) {
for (OpResult result : ifOp.getResults()) {
if (failed(emitter.emitVariableDeclaration(result,
/*trailingSemicolon=*/true)))
return failure();
}
}
os << "if (";
if (failed(emitter.emitOperands(*ifOp.getOperation())))
return failure();
os << ") {\n";
os.indent();
Region &thenRegion = ifOp.thenRegion();
for (Operation &op : thenRegion.getOps()) {
// Note: This prints a superfluous semicolon if the terminating yield op has
// zero results.
if (failed(emitter.emitOperation(op, /*trailingSemicolon=*/true)))
return failure();
}
os.unindent() << "}";
Region &elseRegion = ifOp.elseRegion();
if (!elseRegion.empty()) {
os << " else {\n";
os.indent();
for (Operation &op : elseRegion.getOps()) {
// Note: This prints a superfluous semicolon if the terminating yield op
// has zero results.
if (failed(emitter.emitOperation(op, /*trailingSemicolon=*/true)))
return failure();
}
os.unindent() << "}";
}
return success();
}
static LogicalResult printOperation(CppEmitter &emitter, scf::YieldOp yieldOp) {
raw_ostream &os = emitter.ostream();
Operation &parentOp = *yieldOp.getOperation()->getParentOp();
if (yieldOp.getNumOperands() != parentOp.getNumResults()) {
return yieldOp.emitError("number of operands does not to match the number "
"of the parent op's results");
}
if (failed(interleaveWithError(
llvm::zip(parentOp.getResults(), yieldOp.getOperands()),
[&](auto pair) -> LogicalResult {
auto result = std::get<0>(pair);
auto operand = std::get<1>(pair);
os << emitter.getOrCreateName(result) << " = ";
if (!emitter.hasValueInScope(operand))
return yieldOp.emitError("operand value not in scope");
os << emitter.getOrCreateName(operand);
return success();
},
[&]() { os << ";\n"; })))
return failure();
return success();
}
static LogicalResult printOperation(CppEmitter &emitter, ReturnOp returnOp) {
raw_ostream &os = emitter.ostream();
os << "return";
switch (returnOp.getNumOperands()) {
case 0:
return success();
case 1:
os << " " << emitter.getOrCreateName(returnOp.getOperand(0));
return success(emitter.hasValueInScope(returnOp.getOperand(0)));
default:
os << " std::make_tuple(";
if (failed(emitter.emitOperandsAndAttributes(*returnOp.getOperation())))
return failure();
os << ")";
return success();
}
}
static LogicalResult printOperation(CppEmitter &emitter, ModuleOp moduleOp) {
CppEmitter::Scope scope(emitter);
for (Operation &op : moduleOp) {
if (failed(emitter.emitOperation(op, /*trailingSemicolon=*/false)))
return failure();
}
return success();
}
static LogicalResult printOperation(CppEmitter &emitter, FuncOp functionOp) {
// We need to declare variables at top if the function has multiple blocks.
if (!emitter.shouldDeclareVariablesAtTop() &&
functionOp.getBlocks().size() > 1) {
return functionOp.emitOpError(
"with multiple blocks needs variables declared at top");
}
CppEmitter::Scope scope(emitter);
raw_indented_ostream &os = emitter.ostream();
if (failed(emitter.emitTypes(functionOp.getLoc(),
functionOp.getType().getResults())))
return failure();
os << " " << functionOp.getName();
os << "(";
if (failed(interleaveCommaWithError(
functionOp.getArguments(), os,
[&](BlockArgument arg) -> LogicalResult {
if (failed(emitter.emitType(functionOp.getLoc(), arg.getType())))
return failure();
os << " " << emitter.getOrCreateName(arg);
return success();
})))
return failure();
os << ") {\n";
os.indent();
if (emitter.shouldDeclareVariablesAtTop()) {
// Declare all variables that hold op results including those from nested
// regions.
WalkResult result =
functionOp.walk<WalkOrder::PreOrder>([&](Operation *op) -> WalkResult {
for (OpResult result : op->getResults()) {
if (failed(emitter.emitVariableDeclaration(
result, /*trailingSemicolon=*/true))) {
return WalkResult(
op->emitError("unable to declare result variable for op"));
}
}
return WalkResult::advance();
});
if (result.wasInterrupted())
return failure();
}
Region::BlockListType &blocks = functionOp.getBlocks();
// Create label names for basic blocks.
for (Block &block : blocks) {
emitter.getOrCreateName(block);
}
// Declare variables for basic block arguments.
for (auto it = std::next(blocks.begin()); it != blocks.end(); ++it) {
Block &block = *it;
for (BlockArgument &arg : block.getArguments()) {
if (emitter.hasValueInScope(arg))
return functionOp.emitOpError(" block argument #")
<< arg.getArgNumber() << " is out of scope";
if (failed(
emitter.emitType(block.getParentOp()->getLoc(), arg.getType()))) {
return failure();
}
os << " " << emitter.getOrCreateName(arg) << ";\n";
}
}
for (Block &block : blocks) {
// Only print a label if there is more than one block.
if (blocks.size() > 1) {
if (failed(emitter.emitLabel(block)))
return failure();
}
for (Operation &op : block.getOperations()) {
// When generating code for an scf.if or std.cond_br op no semicolon needs
// to be printed after the closing brace.
// When generating code for an scf.for op, printing a trailing semicolon
// is handled within the printOperation function.
bool trailingSemicolon = !isa<scf::IfOp, scf::ForOp, CondBranchOp>(op);
if (failed(emitter.emitOperation(
op, /*trailingSemicolon=*/trailingSemicolon)))
return failure();
}
}
os.unindent() << "}\n";
return success();
}
CppEmitter::CppEmitter(raw_ostream &os, bool declareVariablesAtTop)
: os(os), declareVariablesAtTop(declareVariablesAtTop) {
valueInScopeCount.push(0);
labelInScopeCount.push(0);
}
/// Return the existing or a new name for a Value.
StringRef CppEmitter::getOrCreateName(Value val) {
if (!valueMapper.count(val))
valueMapper.insert(val, formatv("v{0}", ++valueInScopeCount.top()));
return *valueMapper.begin(val);
}
/// Return the existing or a new label for a Block.
StringRef CppEmitter::getOrCreateName(Block &block) {
if (!blockMapper.count(&block))
blockMapper.insert(&block, formatv("label{0}", ++labelInScopeCount.top()));
return *blockMapper.begin(&block);
}
bool CppEmitter::shouldMapToUnsigned(IntegerType::SignednessSemantics val) {
switch (val) {
case IntegerType::Signless:
return false;
case IntegerType::Signed:
return false;
case IntegerType::Unsigned:
return true;
}
llvm_unreachable("Unexpected IntegerType::SignednessSemantics");
}
bool CppEmitter::hasValueInScope(Value val) { return valueMapper.count(val); }
bool CppEmitter::hasBlockLabel(Block &block) {
return blockMapper.count(&block);
}
LogicalResult CppEmitter::emitAttribute(Location loc, Attribute attr) {
auto printInt = [&](APInt val, bool isUnsigned) {
if (val.getBitWidth() == 1) {
if (val.getBoolValue())
os << "true";
else
os << "false";
} else {
SmallString<128> strValue;
val.toString(strValue, 10, !isUnsigned, false);
os << strValue;
}
};
auto printFloat = [&](APFloat val) {
if (val.isFinite()) {
SmallString<128> strValue;
// Use default values of toString except don't truncate zeros.
val.toString(strValue, 0, 0, false);
switch (llvm::APFloatBase::SemanticsToEnum(val.getSemantics())) {
case llvm::APFloatBase::S_IEEEsingle:
os << "(float)";
break;
case llvm::APFloatBase::S_IEEEdouble:
os << "(double)";
break;
default:
break;
};
os << strValue;
} else if (val.isNaN()) {
os << "NAN";
} else if (val.isInfinity()) {
if (val.isNegative())
os << "-";
os << "INFINITY";
}
};
// Print floating point attributes.
if (auto fAttr = attr.dyn_cast<FloatAttr>()) {
printFloat(fAttr.getValue());
return success();
}
if (auto dense = attr.dyn_cast<DenseFPElementsAttr>()) {
os << '{';
interleaveComma(dense, os, [&](APFloat val) { printFloat(val); });
os << '}';
return success();
}
// Print integer attributes.
if (auto iAttr = attr.dyn_cast<IntegerAttr>()) {
if (auto iType = iAttr.getType().dyn_cast<IntegerType>()) {
printInt(iAttr.getValue(), shouldMapToUnsigned(iType.getSignedness()));
return success();
}
if (auto iType = iAttr.getType().dyn_cast<IndexType>()) {
printInt(iAttr.getValue(), false);
return success();
}
}
if (auto dense = attr.dyn_cast<DenseIntElementsAttr>()) {
if (auto iType = dense.getType()
.cast<TensorType>()
.getElementType()
.dyn_cast<IntegerType>()) {
os << '{';
interleaveComma(dense, os, [&](APInt val) {
printInt(val, shouldMapToUnsigned(iType.getSignedness()));
});
os << '}';
return success();
}
if (auto iType = dense.getType()
.cast<TensorType>()
.getElementType()
.dyn_cast<IndexType>()) {
os << '{';
interleaveComma(dense, os, [&](APInt val) { printInt(val, false); });
os << '}';
return success();
}
}
// Print opaque attributes.
if (auto oAttr = attr.dyn_cast<emitc::OpaqueAttr>()) {
os << oAttr.getValue();
return success();
}
// Print symbolic reference attributes.
if (auto sAttr = attr.dyn_cast<SymbolRefAttr>()) {
if (sAttr.getNestedReferences().size() > 1)
return emitError(loc, "attribute has more than 1 nested reference");
os << sAttr.getRootReference().getValue();
return success();
}
// Print type attributes.
if (auto type = attr.dyn_cast<TypeAttr>())
return emitType(loc, type.getValue());
return emitError(loc, "cannot emit attribute of type ") << attr.getType();
}
LogicalResult CppEmitter::emitOperands(Operation &op) {
auto emitOperandName = [&](Value result) -> LogicalResult {
if (!hasValueInScope(result))
return op.emitOpError() << "operand value not in scope";
os << getOrCreateName(result);
return success();
};
return interleaveCommaWithError(op.getOperands(), os, emitOperandName);
}
LogicalResult
CppEmitter::emitOperandsAndAttributes(Operation &op,
ArrayRef<StringRef> exclude) {
if (failed(emitOperands(op)))
return failure();
// Insert comma in between operands and non-filtered attributes if needed.
if (op.getNumOperands() > 0) {
for (NamedAttribute attr : op.getAttrs()) {
if (!llvm::is_contained(exclude, attr.getName().strref())) {
os << ", ";
break;
}
}
}
// Emit attributes.
auto emitNamedAttribute = [&](NamedAttribute attr) -> LogicalResult {
if (llvm::is_contained(exclude, attr.getName().strref()))
return success();
os << "/* " << attr.getName().getValue() << " */";
if (failed(emitAttribute(op.getLoc(), attr.getValue())))
return failure();
return success();
};
return interleaveCommaWithError(op.getAttrs(), os, emitNamedAttribute);
}
LogicalResult CppEmitter::emitVariableAssignment(OpResult result) {
if (!hasValueInScope(result)) {
return result.getDefiningOp()->emitOpError(
"result variable for the operation has not been declared");
}
os << getOrCreateName(result) << " = ";
return success();
}
LogicalResult CppEmitter::emitVariableDeclaration(OpResult result,
bool trailingSemicolon) {
if (hasValueInScope(result)) {
return result.getDefiningOp()->emitError(
"result variable for the operation already declared");
}
if (failed(emitType(result.getOwner()->getLoc(), result.getType())))
return failure();
os << " " << getOrCreateName(result);
if (trailingSemicolon)
os << ";\n";
return success();
}
LogicalResult CppEmitter::emitAssignPrefix(Operation &op) {
switch (op.getNumResults()) {
case 0:
break;
case 1: {
OpResult result = op.getResult(0);
if (shouldDeclareVariablesAtTop()) {
if (failed(emitVariableAssignment(result)))
return failure();
} else {
if (failed(emitVariableDeclaration(result, /*trailingSemicolon=*/false)))
return failure();
os << " = ";
}
break;
}
default:
if (!shouldDeclareVariablesAtTop()) {
for (OpResult result : op.getResults()) {
if (failed(emitVariableDeclaration(result, /*trailingSemicolon=*/true)))
return failure();
}
}
os << "std::tie(";
interleaveComma(op.getResults(), os,
[&](Value result) { os << getOrCreateName(result); });
os << ") = ";
}
return success();
}
LogicalResult CppEmitter::emitLabel(Block &block) {
if (!hasBlockLabel(block))
return block.getParentOp()->emitError("label for block not found");
// FIXME: Add feature in `raw_indented_ostream` to ignore indent for block
// label instead of using `getOStream`.
os.getOStream() << getOrCreateName(block) << ":\n";
return success();
}
LogicalResult CppEmitter::emitOperation(Operation &op, bool trailingSemicolon) {
LogicalResult status =
llvm::TypeSwitch<Operation *, LogicalResult>(&op)
// EmitC ops.
.Case<emitc::ApplyOp, emitc::CallOp, emitc::ConstantOp,
emitc::IncludeOp>(
[&](auto op) { return printOperation(*this, op); })
// SCF ops.
.Case<scf::ForOp, scf::IfOp, scf::YieldOp>(
[&](auto op) { return printOperation(*this, op); })
// Standard ops.
.Case<BranchOp, mlir::CallOp, CondBranchOp, mlir::ConstantOp, FuncOp,
ModuleOp, ReturnOp>(
[&](auto op) { return printOperation(*this, op); })
// Arithmetic ops.
.Case<arith::ConstantOp>(
[&](auto op) { return printOperation(*this, op); })
.Default([&](Operation *) {
return op.emitOpError("unable to find printer for op");
});
if (failed(status))
return failure();
os << (trailingSemicolon ? ";\n" : "\n");
return success();
}
LogicalResult CppEmitter::emitType(Location loc, Type type) {
if (auto iType = type.dyn_cast<IntegerType>()) {
switch (iType.getWidth()) {
case 1:
return (os << "bool"), success();
case 8:
case 16:
case 32:
case 64:
if (shouldMapToUnsigned(iType.getSignedness()))
return (os << "uint" << iType.getWidth() << "_t"), success();
else
return (os << "int" << iType.getWidth() << "_t"), success();
default:
return emitError(loc, "cannot emit integer type ") << type;
}
}
if (auto fType = type.dyn_cast<FloatType>()) {
switch (fType.getWidth()) {
case 32:
return (os << "float"), success();
case 64:
return (os << "double"), success();
default:
return emitError(loc, "cannot emit float type ") << type;
}
}
if (auto iType = type.dyn_cast<IndexType>())
return (os << "size_t"), success();
if (auto tType = type.dyn_cast<TensorType>()) {
if (!tType.hasRank())
return emitError(loc, "cannot emit unranked tensor type");
if (!tType.hasStaticShape())
return emitError(loc, "cannot emit tensor type with non static shape");
os << "Tensor<";
if (failed(emitType(loc, tType.getElementType())))
return failure();
auto shape = tType.getShape();
for (auto dimSize : shape) {
os << ", ";
os << dimSize;
}
os << ">";
return success();
}
if (auto tType = type.dyn_cast<TupleType>())
return emitTupleType(loc, tType.getTypes());
if (auto oType = type.dyn_cast<emitc::OpaqueType>()) {
os << oType.getValue();
return success();
}
return emitError(loc, "cannot emit type ") << type;
}
LogicalResult CppEmitter::emitTypes(Location loc, ArrayRef<Type> types) {
switch (types.size()) {
case 0:
os << "void";
return success();
case 1:
return emitType(loc, types.front());
default:
return emitTupleType(loc, types);
}
}
LogicalResult CppEmitter::emitTupleType(Location loc, ArrayRef<Type> types) {
os << "std::tuple<";
if (failed(interleaveCommaWithError(
types, os, [&](Type type) { return emitType(loc, type); })))
return failure();
os << ">";
return success();
}
LogicalResult emitc::translateToCpp(Operation *op, raw_ostream &os,
bool declareVariablesAtTop) {
CppEmitter emitter(os, declareVariablesAtTop);
return emitter.emitOperation(*op, /*trailingSemicolon=*/false);
}