Files
clang-p2996/mlir/lib/Target/Cpp/TranslateToCpp.cpp
Marius Brehler a2426eb603 [mlir][emitc] Add div, mul and rem operators
This adds operations for binary multiplicative arithmetic operators to
EmitC. The input and output arguments for the remainder operator are
restricted to index (emitted as size_t), integers and the EmitC opaque
types (as the operator can be overloaded for a custom type). The
multiplication and division operator further support floating point
numbers.

Reviewed By: jpienaar

Differential Revision: https://reviews.llvm.org/D154846
2023-07-12 08:45:10 +02:00

1086 lines
36 KiB
C++

//===- 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/Arith/IR/Arith.h"
#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
#include "mlir/Dialect/EmitC/IR/EmitC.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/SCF/IR/SCF.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"
#include <utility>
#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 = dyn_cast<emitc::OpaqueAttr>(value)) {
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 = dyn_cast<emitc::OpaqueAttr>(value)) {
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.getValue();
return printConstantOp(emitter, operation, value);
}
static LogicalResult printOperation(CppEmitter &emitter,
emitc::VariableOp variableOp) {
Operation *operation = variableOp.getOperation();
Attribute value = variableOp.getValue();
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,
func::ConstantOp constantOp) {
Operation *operation = constantOp.getOperation();
Attribute value = constantOp.getValueAttr();
return printConstantOp(emitter, operation, value);
}
static LogicalResult printBinaryArithOperation(CppEmitter &emitter,
Operation *operation,
StringRef binaryArithOperator) {
raw_ostream &os = emitter.ostream();
if (failed(emitter.emitAssignPrefix(*operation)))
return failure();
os << emitter.getOrCreateName(operation->getOperand(0));
os << " " << binaryArithOperator;
os << " " << emitter.getOrCreateName(operation->getOperand(1));
return success();
}
static LogicalResult printOperation(CppEmitter &emitter, emitc::AddOp addOp) {
Operation *operation = addOp.getOperation();
return printBinaryArithOperation(emitter, operation, "+");
}
static LogicalResult printOperation(CppEmitter &emitter, emitc::DivOp divOp) {
Operation *operation = divOp.getOperation();
return printBinaryArithOperation(emitter, operation, "/");
}
static LogicalResult printOperation(CppEmitter &emitter, emitc::MulOp mulOp) {
Operation *operation = mulOp.getOperation();
return printBinaryArithOperation(emitter, operation, "*");
}
static LogicalResult printOperation(CppEmitter &emitter, emitc::RemOp remOp) {
Operation *operation = remOp.getOperation();
return printBinaryArithOperation(emitter, operation, "%");
}
static LogicalResult printOperation(CppEmitter &emitter, emitc::SubOp subOp) {
Operation *operation = subOp.getOperation();
return printBinaryArithOperation(emitter, operation, "-");
}
static LogicalResult printOperation(CppEmitter &emitter,
cf::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,
cf::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, func::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.getCallee();
auto emitArgs = [&](Attribute attr) -> LogicalResult {
if (auto t = dyn_cast<IntegerAttr>(attr)) {
// 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.getTemplateArgs()) {
os << "<";
if (failed(
interleaveCommaWithError(*callOp.getTemplateArgs(), os, emitArgs)))
return failure();
os << ">";
}
os << "(";
LogicalResult emittedArgs =
callOp.getArgs()
? interleaveCommaWithError(*callOp.getArgs(), 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.getApplicableOperator();
os << emitter.getOrCreateName(applyOp.getOperand());
return success();
}
static LogicalResult printOperation(CppEmitter &emitter, emitc::CastOp castOp) {
raw_ostream &os = emitter.ostream();
Operation &op = *castOp.getOperation();
if (failed(emitter.emitAssignPrefix(op)))
return failure();
os << "(";
if (failed(emitter.emitType(op.getLoc(), op.getResult(0).getType())))
return failure();
os << ") ";
os << emitter.getOrCreateName(castOp.getOperand());
return success();
}
static LogicalResult printOperation(CppEmitter &emitter,
emitc::IncludeOp includeOp) {
raw_ostream &os = emitter.ostream();
os << "#include ";
if (includeOp.getIsStandardInclude())
os << "<" << includeOp.getInclude() << ">";
else
os << "\"" << includeOp.getInclude() << "\"";
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.getLowerBound());
os << "; ";
os << emitter.getOrCreateName(forOp.getInductionVar());
os << " < ";
os << emitter.getOrCreateName(forOp.getUpperBound());
os << "; ";
os << emitter.getOrCreateName(forOp.getInductionVar());
os << " += ";
os << emitter.getOrCreateName(forOp.getStep());
os << ") {\n";
os.indent();
Region &forRegion = forOp.getRegion();
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.getThenRegion();
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.getElseRegion();
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,
func::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,
func::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.getFunctionType().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 (Block &block : llvm::drop_begin(blocks)) {
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 the block has predecessors.
if (!block.hasNoPredecessors()) {
if (failed(emitter.emitLabel(block)))
return failure();
}
for (Operation &op : block.getOperations()) {
// When generating code for an scf.if or cf.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, cf::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 = [&](const 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 = [&](const 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 = dyn_cast<FloatAttr>(attr)) {
printFloat(fAttr.getValue());
return success();
}
if (auto dense = dyn_cast<DenseFPElementsAttr>(attr)) {
os << '{';
interleaveComma(dense, os, [&](const APFloat &val) { printFloat(val); });
os << '}';
return success();
}
// Print integer attributes.
if (auto iAttr = dyn_cast<IntegerAttr>(attr)) {
if (auto iType = dyn_cast<IntegerType>(iAttr.getType())) {
printInt(iAttr.getValue(), shouldMapToUnsigned(iType.getSignedness()));
return success();
}
if (auto iType = dyn_cast<IndexType>(iAttr.getType())) {
printInt(iAttr.getValue(), false);
return success();
}
}
if (auto dense = dyn_cast<DenseIntElementsAttr>(attr)) {
if (auto iType = dyn_cast<IntegerType>(
cast<TensorType>(dense.getType()).getElementType())) {
os << '{';
interleaveComma(dense, os, [&](const APInt &val) {
printInt(val, shouldMapToUnsigned(iType.getSignedness()));
});
os << '}';
return success();
}
if (auto iType = dyn_cast<IndexType>(
cast<TensorType>(dense.getType()).getElementType())) {
os << '{';
interleaveComma(dense, os,
[&](const APInt &val) { printInt(val, false); });
os << '}';
return success();
}
}
// Print opaque attributes.
if (auto oAttr = dyn_cast<emitc::OpaqueAttr>(attr)) {
os << oAttr.getValue();
return success();
}
// Print symbolic reference attributes.
if (auto sAttr = dyn_cast<SymbolRefAttr>(attr)) {
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 = dyn_cast<TypeAttr>(attr))
return emitType(loc, type.getValue());
return emitError(loc, "cannot emit attribute: ") << attr;
}
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)
// Builtin ops.
.Case<ModuleOp>([&](auto op) { return printOperation(*this, op); })
// CF ops.
.Case<cf::BranchOp, cf::CondBranchOp>(
[&](auto op) { return printOperation(*this, op); })
// EmitC ops.
.Case<emitc::AddOp, emitc::ApplyOp, emitc::CallOp, emitc::CastOp,
emitc::ConstantOp, emitc::DivOp, emitc::IncludeOp, emitc::MulOp,
emitc::RemOp, emitc::SubOp, emitc::VariableOp>(
[&](auto op) { return printOperation(*this, op); })
// Func ops.
.Case<func::CallOp, func::ConstantOp, func::FuncOp, func::ReturnOp>(
[&](auto op) { return printOperation(*this, op); })
// SCF ops.
.Case<scf::ForOp, scf::IfOp, scf::YieldOp>(
[&](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 = dyn_cast<IntegerType>(type)) {
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 = dyn_cast<FloatType>(type)) {
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 = dyn_cast<IndexType>(type))
return (os << "size_t"), success();
if (auto tType = dyn_cast<TensorType>(type)) {
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 = dyn_cast<TupleType>(type))
return emitTupleType(loc, tType.getTypes());
if (auto oType = dyn_cast<emitc::OpaqueType>(type)) {
os << oType.getValue();
return success();
}
if (auto pType = dyn_cast<emitc::PointerType>(type)) {
if (failed(emitType(loc, pType.getPointee())))
return failure();
os << "*";
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);
}