[mlir] Add Cpp emitter

This upstreams the Cpp emitter, initially presented with [1], from [2]
to MLIR core. Together with the previously upstreamed EmitC dialect [3],
the target allows to translate MLIR to C/C++.

[1] https://reviews.llvm.org/D76571
[2] https://github.com/iml130/mlir-emitc
[3] https://reviews.llvm.org/D103969

Co-authored-by: Jacques Pienaar <jpienaar@google.com>
Co-authored-by: Simon Camphausen <simon.camphausen@iml.fraunhofer.de>
Co-authored-by: Oliver Scherf <oliver.scherf@iml.fraunhofer.de>

Reviewed By: jpienaar

Differential Revision: https://reviews.llvm.org/D104632
This commit is contained in:
Marius Brehler 2021-06-21 10:44:20 +00:00
parent 8647e4c3a0
commit 2f0750dd2e
16 changed files with 1714 additions and 0 deletions

View File

@ -0,0 +1,34 @@
The EmitC dialect allows to convert operations from other MLIR dialects to
EmitC ops. Those can be translated to C/C++ via the Cpp emitter.
The following convention is followed:
* If template arguments are passed to an `emitc.call` operation,
C++ is generated.
* If tensors are used, C++ is generated.
* If multiple return values are used within in a functions or an
`emitc.call` operation, C++11 is required.
* If floating-point type template arguments are passed to an `emitc.call`
operation, C++20 is required.
* Else the generated code is compatible with C99.
These restrictions are neither inherent to the EmitC dialect itself nor to the
Cpp emitter and therefore need to be considered while implementing conversions.
After the conversion, C/C++ code can be emitted with `mlir-translate`. The tool
support translating MLIR to C/C++ by passing `-mlir-to-cpp`.
Furthermore, code with variables declared at top can be generated by passing
`-mlir-to-cpp-with-variable-declarations-at-top`.
Besides operations part of the EmitC dialect, the Cpp targets supports
translating the following operations:
* 'std' Dialect
* `std.br`
* `std.call`
* `std.cond_br`
* `std.constant`
* `std.return`
* 'scf' Dialect
* `scf.for`
* `scf.yield`

View File

@ -22,6 +22,12 @@ include "mlir/IR/OpBase.td"
def EmitC_Dialect : Dialect {
let name = "emitc";
let cppNamespace = "::mlir::emitc";
let summary = "Dialect to generate C/C++ from MLIR.";
let description = [{
[include "Dialects/emitc.md"]
}];
let hasConstantMaterializer = 1;
}

View File

@ -18,6 +18,7 @@ namespace mlir {
void registerFromLLVMIRTranslation();
void registerFromSPIRVTranslation();
void registerToCppTranslation();
void registerToLLVMIRTranslation();
void registerToSPIRVTranslation();
@ -28,6 +29,7 @@ inline void registerAllTranslations() {
static bool initOnce = []() {
registerFromLLVMIRTranslation();
registerFromSPIRVTranslation();
registerToCppTranslation();
registerToLLVMIRTranslation();
registerToSPIRVTranslation();
return true;

View File

@ -0,0 +1,34 @@
//===- CppEmitter.h - Helpers to create C++ emitter -------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file defines helpers to emit C++ code using the EmitC dialect.
//
//===----------------------------------------------------------------------===//
#ifndef MLIR_TARGET_CPP_CPPEMITTER_H
#define MLIR_TARGET_CPP_CPPEMITTER_H
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/Value.h"
#include "llvm/ADT/ScopedHashTable.h"
#include "llvm/Support/raw_ostream.h"
#include <stack>
namespace mlir {
namespace emitc {
/// 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
/// arguments are declared at the beginning of the function.
LogicalResult translateToCpp(Operation *op, raw_ostream &os,
bool declareVariablesAtTop = false);
} // namespace emitc
} // namespace mlir
#endif // MLIR_TARGET_CPP_CPPEMITTER_H

View File

@ -1,2 +1,3 @@
add_subdirectory(Cpp)
add_subdirectory(SPIRV)
add_subdirectory(LLVMIR)

View File

@ -0,0 +1,14 @@
add_mlir_translation_library(MLIRTargetCpp
TranslateRegistration.cpp
TranslateToCpp.cpp
ADDITIONAL_HEADER_DIRS
${EMITC_MAIN_INCLUDE_DIR}/emitc/Target/Cpp
LINK_LIBS PUBLIC
MLIREmitC
MLIRIR
MLIRSCF
MLIRStandard
MLIRSupport
)

View File

@ -0,0 +1,48 @@
//===- TranslateRegistration.cpp - Register translation -------------------===//
//
// 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/Dialect.h"
#include "mlir/Target/Cpp/CppEmitter.h"
#include "mlir/Translation.h"
#include "llvm/Support/CommandLine.h"
using namespace mlir;
namespace mlir {
//===----------------------------------------------------------------------===//
// Cpp registration
//===----------------------------------------------------------------------===//
void registerToCppTranslation() {
static llvm::cl::opt<bool> declareVariablesAtTop(
"declare-variables-at-top",
llvm::cl::desc("Declare variables at top when emitting C/C++"),
llvm::cl::init(false));
TranslateFromMLIRRegistration reg(
"mlir-to-cpp",
[](ModuleOp module, raw_ostream &output) {
return emitc::translateToCpp(
module, output,
/*declareVariablesAtTop=*/declareVariablesAtTop);
},
[](DialectRegistry &registry) {
// clang-format off
registry.insert<emitc::EmitCDialect,
StandardOpsDialect,
scf::SCFDialect>();
// clang-format on
});
}
} // namespace mlir

View File

@ -0,0 +1,983 @@
//===- 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,
mlir::ConstantOp constantOp) {
Operation *operation = constantOp.getOperation();
Attribute value = constantOp.value();
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_ostream &os = emitter.ostream();
Block &trueSuccessor = *condBranchOp.getTrueDest();
Block &falseSuccessor = *condBranchOp.getFalseDest();
os << "if (" << emitter.getOrCreateName(condBranchOp.getCondition())
<< ") {\n";
// 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 << "} else {\n";
// 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 << "}";
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;
}
}
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 isSigned) {
if (val.getBitWidth() == 1) {
if (val.getBoolValue())
os << "true";
else
os << "false";
} else {
val.print(os, isSigned);
}
};
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.first.strref())) {
os << ", ";
break;
}
}
}
// Emit attributes.
auto emitNamedAttribute = [&](NamedAttribute attr) -> LogicalResult {
if (llvm::is_contained(exclude, attr.first.strref()))
return success();
os << "/* " << attr.first << " */";
if (failed(emitAttribute(op.getLoc(), attr.second)))
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");
os << 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); })
.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);
}

View File

@ -0,0 +1,36 @@
// RUN: mlir-translate -mlir-to-cpp %s | FileCheck %s -check-prefix=CPP-DEFAULT
// RUN: mlir-translate -mlir-to-cpp -declare-variables-at-top %s | FileCheck %s -check-prefix=CPP-DECLTOP
func @emitc_call() {
%0 = emitc.call "func_a" () : () -> i32
%1 = emitc.call "func_b" () : () -> i32
return
}
// CPP-DEFAULT: void emitc_call() {
// CPP-DEFAULT-NEXT: int32_t [[V0:[^ ]*]] = func_a();
// CPP-DEFAULT-NEXT: int32_t [[V1:[^ ]*]] = func_b();
// CPP-DECLTOP: void emitc_call() {
// CPP-DECLTOP-NEXT: int32_t [[V0:[^ ]*]];
// CPP-DECLTOP-NEXT: int32_t [[V1:[^ ]*]];
// CPP-DECLTOP-NEXT: [[V0:]] = func_a();
// CPP-DECLTOP-NEXT: [[V1:]] = func_b();
func @emitc_call_two_results() {
%0 = constant 0 : index
%1:2 = emitc.call "two_results" () : () -> (i32, i32)
return
}
// CPP-DEFAULT: void emitc_call_two_results() {
// CPP-DEFAULT-NEXT: size_t [[V1:[^ ]*]] = 0;
// CPP-DEFAULT-NEXT: int32_t [[V2:[^ ]*]];
// CPP-DEFAULT-NEXT: int32_t [[V3:[^ ]*]];
// CPP-DEFAULT-NEXT: std::tie([[V2]], [[V3]]) = two_results();
// CPP-DECLTOP: void emitc_call_two_results() {
// CPP-DECLTOP-NEXT: size_t [[V1:[^ ]*]];
// CPP-DECLTOP-NEXT: int32_t [[V2:[^ ]*]];
// CPP-DECLTOP-NEXT: int32_t [[V3:[^ ]*]];
// CPP-DECLTOP-NEXT: [[V1]] = 0;
// CPP-DECLTOP-NEXT: std::tie([[V2]], [[V3]]) = two_results();

View File

@ -0,0 +1,91 @@
// RUN: mlir-translate -mlir-to-cpp %s | FileCheck %s
// CHECK: #include "myheader.h"
emitc.include "myheader.h"
// CHECK: #include <myheader.h>
emitc.include <"myheader.h">
// CHECK: void test_foo_print() {
func @test_foo_print() {
// CHECK: [[V1:[^ ]*]] = foo::constant({0, 1});
%0 = emitc.call "foo::constant"() {args = [dense<[0, 1]> : tensor<2xi32>]} : () -> (i32)
// CHECK: [[V2:[^ ]*]] = foo::op_and_attr({0, 1}, [[V1]]);
%1 = emitc.call "foo::op_and_attr"(%0) {args = [dense<[0, 1]> : tensor<2xi32>, 0 : index]} : (i32) -> (i32)
// CHECK: [[V3:[^ ]*]] = foo::op_and_attr([[V2]], {0, 1});
%2 = emitc.call "foo::op_and_attr"(%1) {args = [0 : index, dense<[0, 1]> : tensor<2xi32>]} : (i32) -> (i32)
// CHECK: foo::print([[V3]]);
emitc.call "foo::print"(%2): (i32) -> ()
return
}
// CHECK: int32_t test_single_return(int32_t [[V2:.*]])
func @test_single_return(%arg0 : i32) -> i32 {
// CHECK: return [[V2]]
return %arg0 : i32
}
// CHECK: std::tuple<int32_t, int32_t> test_multiple_return()
func @test_multiple_return() -> (i32, i32) {
// CHECK: std::tie([[V3:.*]], [[V4:.*]]) = foo::blah();
%0:2 = emitc.call "foo::blah"() : () -> (i32, i32)
// CHECK: [[V5:[^ ]*]] = test_single_return([[V3]]);
%1 = call @test_single_return(%0#0) : (i32) -> i32
// CHECK: return std::make_tuple([[V5]], [[V4]]);
return %1, %0#1 : i32, i32
}
// CHECK: test_float
func @test_float() {
// CHECK: foo::constant({(float)0.0e+00, (float)1.000000000e+00})
%0 = emitc.call "foo::constant"() {args = [dense<[0.000000e+00, 1.000000e+00]> : tensor<2xf32>]} : () -> f32
return
}
// CHECK: test_uint
func @test_uint() {
// CHECK: uint32_t
%0 = emitc.call "foo::constant"() {args = [dense<[0, 1]> : tensor<2xui32>]} : () -> ui32
// CHECK: uint64_t
%1 = emitc.call "foo::constant"() {args = [dense<[0, 1]> : tensor<2xui64>]} : () -> ui64
return
}
// CHECK: int64_t test_plus_int(int64_t [[V1]])
func @test_plus_int(%arg0 : i64) -> i64 {
// CHECK: mhlo::add([[V1]], [[V1]])
%0 = emitc.call "mhlo::add"(%arg0, %arg0) {args = [0 : index, 1 : index]} : (i64, i64) -> i64
return %0 : i64
}
// CHECK: Tensor<float, 2> mixed_types(Tensor<double, 2> [[V1]])
func @mixed_types(%arg0: tensor<2xf64>) -> tensor<2xf32> {
// CHECK: foo::mixed_types([[V1]]);
%0 = emitc.call "foo::mixed_types"(%arg0) {args = [0 : index]} : (tensor<2xf64>) -> tensor<2xf32>
return %0 : tensor<2xf32>
}
// CHECK: Tensor<uint64_t> mhlo_convert(Tensor<uint32_t> [[V1]])
func @mhlo_convert(%arg0: tensor<ui32>) -> tensor<ui64> {
// CHECK: mhlo::convert([[V1]]);
%0 = emitc.call "mhlo::convert"(%arg0) {args = [0 : index]} : (tensor<ui32>) -> tensor<ui64>
return %0 : tensor<ui64>
}
// CHECK: status_t opaque_types(bool [[V1:[^ ]*]], char [[V2:[^ ]*]]) {
func @opaque_types(%arg0: !emitc.opaque<"bool">, %arg1: !emitc.opaque<"char">) -> !emitc.opaque<"status_t"> {
// CHECK: int [[V3:[^ ]*]] = a([[V1]], [[V2]]);
%0 = emitc.call "a"(%arg0, %arg1) : (!emitc.opaque<"bool">, !emitc.opaque<"char">) -> (!emitc.opaque<"int">)
// CHECK: char [[V4:[^ ]*]] = b([[V3]]);
%1 = emitc.call "b"(%0): (!emitc.opaque<"int">) -> (!emitc.opaque<"char">)
// CHECK: status_t [[V5:[^ ]*]] = c([[V3]], [[V4]]);
%2 = emitc.call "c"(%0, %1): (!emitc.opaque<"int">, !emitc.opaque<"char">) -> (!emitc.opaque<"status_t">)
return %2 : !emitc.opaque<"status_t">
}
func @apply(%arg0: i32) -> !emitc.opaque<"int32_t*"> {
// CHECK: int32_t* [[V2]] = &[[V1]];
%0 = emitc.apply "&"(%arg0) : (i32) -> !emitc.opaque<"int32_t*">
// CHECK: int32_t [[V3]] = *[[V2]];
%1 = emitc.apply "*"(%0) : (!emitc.opaque<"int32_t*">) -> (i32)
return %0 : !emitc.opaque<"int32_t*">
}

View File

@ -0,0 +1,26 @@
// RUN: mlir-translate -mlir-to-cpp %s | FileCheck %s -check-prefix=CPP-DEFAULT
// RUN: mlir-translate -mlir-to-cpp -declare-variables-at-top %s | FileCheck %s -check-prefix=CPP-DECLTOP
func @emitc_constant() {
%c0 = "emitc.constant"(){value = #emitc.opaque<""> : i32} : () -> i32
%c1 = "emitc.constant"(){value = 42 : i32} : () -> i32
%c2 = "emitc.constant"(){value = #emitc.opaque<""> : !emitc.opaque<"int32_t*">} : () -> !emitc.opaque<"int32_t*">
%c3 = "emitc.constant"(){value = #emitc.opaque<"NULL"> : !emitc.opaque<"int32_t*">} : () -> !emitc.opaque<"int32_t*">
return
}
// CPP-DEFAULT: void emitc_constant() {
// CPP-DEFAULT-NEXT: int32_t [[V0:[^ ]*]];
// CPP-DEFAULT-NEXT: int32_t [[V1:[^ ]*]] = 42;
// CPP-DEFAULT-NEXT: int32_t* [[V2:[^ ]*]];
// CPP-DEFAULT-NEXT: int32_t* [[V3:[^ ]*]] = NULL;
// CPP-DECLTOP: void emitc_constant() {
// CPP-DECLTOP-NEXT: int32_t [[V0:[^ ]*]];
// CPP-DECLTOP-NEXT: int32_t [[V1:[^ ]*]];
// CPP-DECLTOP-NEXT: int32_t* [[V2:[^ ]*]];
// CPP-DECLTOP-NEXT: int32_t* [[V3:[^ ]*]];
// CPP-DECLTOP-NEXT: ;
// CPP-DECLTOP-NEXT: [[V1]] = 42;
// CPP-DECLTOP-NEXT: ;
// CPP-DECLTOP-NEXT: [[V3]] = NULL;

View File

@ -0,0 +1,73 @@
// RUN: mlir-translate -mlir-to-cpp -declare-variables-at-top %s | FileCheck %s -check-prefix=CPP-DECLTOP
// simple(10, true) -> 20
// simple(10, false) -> 30
func @simple(i64, i1) -> i64 {
^bb0(%a: i64, %cond: i1):
cond_br %cond, ^bb1, ^bb2
^bb1:
br ^bb3(%a: i64)
^bb2:
%b = emitc.call "add"(%a, %a) : (i64, i64) -> i64
br ^bb3(%b: i64)
^bb3(%c: i64):
br ^bb4(%c, %a : i64, i64)
^bb4(%d : i64, %e : i64):
%0 = emitc.call "add"(%d, %e) : (i64, i64) -> i64
return %0 : i64
}
// CPP-DECLTOP: int64_t simple(int64_t [[A:[^ ]*]], bool [[COND:[^ ]*]]) {
// CPP-DECLTOP-NEXT: int64_t [[B:[^ ]*]];
// CPP-DECLTOP-NEXT: int64_t [[V0:[^ ]*]];
// CPP-DECLTOP-NEXT: int64_t [[C:[^ ]*]];
// CPP-DECLTOP-NEXT: int64_t [[D:[^ ]*]];
// CPP-DECLTOP-NEXT: int64_t [[E:[^ ]*]];
// CPP-DECLTOP-NEXT: [[BB0:[^ ]*]]:
// CPP-DECLTOP-NEXT: if ([[COND]]) {
// CPP-DECLTOP-NEXT: goto [[BB1:[^ ]*]];
// CPP-DECLTOP-NEXT: } else {
// CPP-DECLTOP-NEXT: goto [[BB2:[^ ]*]];
// CPP-DECLTOP-NEXT: }
// CPP-DECLTOP-NEXT: [[BB1]]:
// CPP-DECLTOP-NEXT: [[C]] = [[A]];
// CPP-DECLTOP-NEXT: goto [[BB3:[^ ]*]];
// CPP-DECLTOP-NEXT: [[BB2]]:
// CPP-DECLTOP-NEXT: [[B]] = add([[A]], [[A]]);
// CPP-DECLTOP-NEXT: [[C]] = [[B]];
// CPP-DECLTOP-NEXT: goto [[BB3]];
// CPP-DECLTOP-NEXT: [[BB3]]:
// CPP-DECLTOP-NEXT: [[D]] = [[C]];
// CPP-DECLTOP-NEXT: [[E]] = [[A]];
// CPP-DECLTOP-NEXT: goto [[BB4:[^ ]*]];
// CPP-DECLTOP-NEXT: [[BB4]]:
// CPP-DECLTOP-NEXT: [[V0]] = add([[D]], [[E]]);
// CPP-DECLTOP-NEXT: return [[V0]];
func @block_labels0() {
^bb1:
br ^bb2
^bb2:
return
}
// CPP-DECLTOP: void block_labels0() {
// CPP-DECLTOP-NEXT: label1:
// CPP-DECLTOP-NEXT: goto label2;
// CPP-DECLTOP-NEXT: label2:
// CPP-DECLTOP-NEXT: return;
// CPP-DECLTOP-NEXT: }
// Repeat the same function to make sure the names of the block labels get reset.
func @block_labels1() {
^bb1:
br ^bb2
^bb2:
return
}
// CPP-DECLTOP: void block_labels1() {
// CPP-DECLTOP-NEXT: label1:
// CPP-DECLTOP-NEXT: goto label2;
// CPP-DECLTOP-NEXT: label2:
// CPP-DECLTOP-NEXT: return;
// CPP-DECLTOP-NEXT: }

View File

@ -0,0 +1,84 @@
// RUN: mlir-translate -mlir-to-cpp %s | FileCheck %s -check-prefix=CPP-DEFAULT
// RUN: mlir-translate -mlir-to-cpp -declare-variables-at-top %s | FileCheck %s -check-prefix=CPP-DECLTOP
func @test_for(%arg0 : index, %arg1 : index, %arg2 : index) {
scf.for %i0 = %arg0 to %arg1 step %arg2 {
%0 = emitc.call "f"() : () -> i32
}
return
}
// CPP-DEFAULT: void test_for(size_t [[START:[^ ]*]], size_t [[STOP:[^ ]*]], size_t [[STEP:[^ ]*]]) {
// CPP-DEFAULT-NEXT: for (size_t [[ITER:[^ ]*]] = [[START]]; [[ITER]] < [[STOP]]; [[ITER]] += [[STEP]]) {
// CPP-DEFAULT-NEXT: int32_t [[V4:[^ ]*]] = f();
// CPP-DEFAULT-NEXT: }
// CPP-DEFAULT-NEXT: return;
// CPP-DECLTOP: void test_for(size_t [[START:[^ ]*]], size_t [[STOP:[^ ]*]], size_t [[STEP:[^ ]*]]) {
// CPP-DECLTOP-NEXT: int32_t [[V4:[^ ]*]];
// CPP-DECLTOP-NEXT: for (size_t [[ITER:[^ ]*]] = [[START]]; [[ITER]] < [[STOP]]; [[ITER]] += [[STEP]]) {
// CPP-DECLTOP-NEXT: [[V4]] = f();
// CPP-DECLTOP-NEXT: }
// CPP-DECLTOP-NEXT: return;
func @test_for_yield() {
%start = constant 0 : index
%stop = constant 10 : index
%step = constant 1 : index
%s0 = constant 0 : i32
%p0 = constant 1.0 : f32
%result:2 = scf.for %iter = %start to %stop step %step iter_args(%si = %s0, %pi = %p0) -> (i32, f32) {
%sn = emitc.call "add"(%si, %iter) : (i32, index) -> i32
%pn = emitc.call "mul"(%pi, %iter) : (f32, index) -> f32
scf.yield %sn, %pn : i32, f32
}
return
}
// CPP-DEFAULT: void test_for_yield() {
// CPP-DEFAULT-NEXT: size_t [[START:[^ ]*]] = 0;
// CPP-DEFAULT-NEXT: size_t [[STOP:[^ ]*]] = 10;
// CPP-DEFAULT-NEXT: size_t [[STEP:[^ ]*]] = 1;
// CPP-DEFAULT-NEXT: int32_t [[S0:[^ ]*]] = 0;
// CPP-DEFAULT-NEXT: float [[P0:[^ ]*]] = (float)1.000000000e+00;
// CPP-DEFAULT-NEXT: int32_t [[SE:[^ ]*]];
// CPP-DEFAULT-NEXT: float [[PE:[^ ]*]];
// CPP-DEFAULT-NEXT: int32_t [[SI:[^ ]*]] = [[S0]];
// CPP-DEFAULT-NEXT: float [[PI:[^ ]*]] = [[P0]];
// CPP-DEFAULT-NEXT: for (size_t [[ITER:[^ ]*]] = [[START]]; [[ITER]] < [[STOP]]; [[ITER]] += [[STEP]]) {
// CPP-DEFAULT-NEXT: int32_t [[SN:[^ ]*]] = add([[SI]], [[ITER]]);
// CPP-DEFAULT-NEXT: float [[PN:[^ ]*]] = mul([[PI]], [[ITER]]);
// CPP-DEFAULT-NEXT: [[SI]] = [[SN]];
// CPP-DEFAULT-NEXT: [[PI]] = [[PN]];
// CPP-DEFAULT-NEXT: }
// CPP-DEFAULT-NEXT: [[SE]] = [[SI]];
// CPP-DEFAULT-NEXT: [[PE]] = [[PI]];
// CPP-DEFAULT-NEXT: return;
// CPP-DECLTOP: void test_for_yield() {
// CPP-DECLTOP-NEXT: size_t [[START:[^ ]*]];
// CPP-DECLTOP-NEXT: size_t [[STOP:[^ ]*]];
// CPP-DECLTOP-NEXT: size_t [[STEP:[^ ]*]];
// CPP-DECLTOP-NEXT: int32_t [[S0:[^ ]*]];
// CPP-DECLTOP-NEXT: float [[P0:[^ ]*]];
// CPP-DECLTOP-NEXT: int32_t [[SE:[^ ]*]];
// CPP-DECLTOP-NEXT: float [[PE:[^ ]*]];
// CPP-DECLTOP-NEXT: int32_t [[SN:[^ ]*]];
// CPP-DECLTOP-NEXT: float [[PN:[^ ]*]];
// CPP-DECLTOP-NEXT: [[START]] = 0;
// CPP-DECLTOP-NEXT: [[STOP]] = 10;
// CPP-DECLTOP-NEXT: [[STEP]] = 1;
// CPP-DECLTOP-NEXT: [[S0]] = 0;
// CPP-DECLTOP-NEXT: [[P0]] = (float)1.000000000e+00;
// CPP-DECLTOP-NEXT: int32_t [[SI:[^ ]*]] = [[S0]];
// CPP-DECLTOP-NEXT: float [[PI:[^ ]*]] = [[P0]];
// CPP-DECLTOP-NEXT: for (size_t [[ITER:[^ ]*]] = [[START]]; [[ITER]] < [[STOP]]; [[ITER]] += [[STEP]]) {
// CPP-DECLTOP-NEXT: [[SN]] = add([[SI]], [[ITER]]);
// CPP-DECLTOP-NEXT: [[PN]] = mul([[PI]], [[ITER]]);
// CPP-DECLTOP-NEXT: [[SI]] = [[SN]];
// CPP-DECLTOP-NEXT: [[PI]] = [[PN]];
// CPP-DECLTOP-NEXT: }
// CPP-DECLTOP-NEXT: [[SE]] = [[SI]];
// CPP-DECLTOP-NEXT: [[PE]] = [[PI]];
// CPP-DECLTOP-NEXT: return;

View File

@ -0,0 +1,107 @@
// RUN: mlir-translate -mlir-to-cpp %s | FileCheck %s -check-prefix=CPP-DEFAULT
// RUN: mlir-translate -mlir-to-cpp -declare-variables-at-top %s | FileCheck %s -check-prefix=CPP-DECLTOP
func @test_if(%arg0: i1, %arg1: f32) {
scf.if %arg0 {
%0 = emitc.call "func_const"(%arg1) : (f32) -> i32
}
return
}
// CPP-DEFAULT: void test_if(bool [[V0:[^ ]*]], float [[V1:[^ ]*]]) {
// CPP-DEFAULT-NEXT: if ([[V0]]) {
// CPP-DEFAULT-NEXT: int32_t [[V2:[^ ]*]] = func_const([[V1]]);
// CPP-DEFAULT-NEXT: ;
// CPP-DEFAULT-NEXT: }
// CPP-DEFAULT-NEXT: return;
// CPP-DECLTOP: void test_if(bool [[V0:[^ ]*]], float [[V1:[^ ]*]]) {
// CPP-DECLTOP-NEXT: int32_t [[V2:[^ ]*]];
// CPP-DECLTOP-NEXT: if ([[V0]]) {
// CPP-DECLTOP-NEXT: [[V2]] = func_const([[V1]]);
// CPP-DECLTOP-NEXT: ;
// CPP-DECLTOP-NEXT: }
// CPP-DECLTOP-NEXT: return;
func @test_if_else(%arg0: i1, %arg1: f32) {
scf.if %arg0 {
%0 = emitc.call "func_true"(%arg1) : (f32) -> i32
} else {
%0 = emitc.call "func_false"(%arg1) : (f32) -> i32
}
return
}
// CPP-DEFAULT: void test_if_else(bool [[V0:[^ ]*]], float [[V1:[^ ]*]]) {
// CPP-DEFAULT-NEXT: if ([[V0]]) {
// CPP-DEFAULT-NEXT: int32_t [[V2:[^ ]*]] = func_true([[V1]]);
// CPP-DEFAULT-NEXT: ;
// CPP-DEFAULT-NEXT: } else {
// CPP-DEFAULT-NEXT: int32_t [[V3:[^ ]*]] = func_false([[V1]]);
// CPP-DEFAULT-NEXT: ;
// CPP-DEFAULT-NEXT: }
// CPP-DEFAULT-NEXT: return;
// CPP-DECLTOP: void test_if_else(bool [[V0:[^ ]*]], float [[V1:[^ ]*]]) {
// CPP-DECLTOP-NEXT: int32_t [[V2:[^ ]*]];
// CPP-DECLTOP-NEXT: int32_t [[V3:[^ ]*]];
// CPP-DECLTOP-NEXT: if ([[V0]]) {
// CPP-DECLTOP-NEXT: [[V2]] = func_true([[V1]]);
// CPP-DECLTOP-NEXT: ;
// CPP-DECLTOP-NEXT: } else {
// CPP-DECLTOP-NEXT: [[V3]] = func_false([[V1]]);
// CPP-DECLTOP-NEXT: ;
// CPP-DECLTOP-NEXT: }
// CPP-DECLTOP-NEXT: return;
func @test_if_yield(%arg0: i1, %arg1: f32) {
%0 = constant 0 : i8
%x, %y = scf.if %arg0 -> (i32, f64) {
%1 = emitc.call "func_true_1"(%arg1) : (f32) -> i32
%2 = emitc.call "func_true_2"(%arg1) : (f32) -> f64
scf.yield %1, %2 : i32, f64
} else {
%1 = emitc.call "func_false_1"(%arg1) : (f32) -> i32
%2 = emitc.call "func_false_2"(%arg1) : (f32) -> f64
scf.yield %1, %2 : i32, f64
}
return
}
// CPP-DEFAULT: void test_if_yield(bool [[V0:[^ ]*]], float [[V1:[^ ]*]]) {
// CPP-DEFAULT-NEXT: int8_t [[V2:[^ ]*]] = 0;
// CPP-DEFAULT-NEXT: int32_t [[V3:[^ ]*]];
// CPP-DEFAULT-NEXT: double [[V4:[^ ]*]];
// CPP-DEFAULT-NEXT: if ([[V0]]) {
// CPP-DEFAULT-NEXT: int32_t [[V5:[^ ]*]] = func_true_1([[V1]]);
// CPP-DEFAULT-NEXT: double [[V6:[^ ]*]] = func_true_2([[V1]]);
// CPP-DEFAULT-NEXT: [[V3]] = [[V5]];
// CPP-DEFAULT-NEXT: [[V4]] = [[V6]];
// CPP-DEFAULT-NEXT: } else {
// CPP-DEFAULT-NEXT: int32_t [[V7:[^ ]*]] = func_false_1([[V1]]);
// CPP-DEFAULT-NEXT: double [[V8:[^ ]*]] = func_false_2([[V1]]);
// CPP-DEFAULT-NEXT: [[V3]] = [[V7]];
// CPP-DEFAULT-NEXT: [[V4]] = [[V8]];
// CPP-DEFAULT-NEXT: }
// CPP-DEFAULT-NEXT: return;
// CPP-DECLTOP: void test_if_yield(bool [[V0:[^ ]*]], float [[V1:[^ ]*]]) {
// CPP-DECLTOP-NEXT: int8_t [[V2:[^ ]*]];
// CPP-DECLTOP-NEXT: int32_t [[V3:[^ ]*]];
// CPP-DECLTOP-NEXT: double [[V4:[^ ]*]];
// CPP-DECLTOP-NEXT: int32_t [[V5:[^ ]*]];
// CPP-DECLTOP-NEXT: double [[V6:[^ ]*]];
// CPP-DECLTOP-NEXT: int32_t [[V7:[^ ]*]];
// CPP-DECLTOP-NEXT: double [[V8:[^ ]*]];
// CPP-DECLTOP-NEXT: [[V2]] = 0;
// CPP-DECLTOP-NEXT: if ([[V0]]) {
// CPP-DECLTOP-NEXT: [[V5]] = func_true_1([[V1]]);
// CPP-DECLTOP-NEXT: [[V6]] = func_true_2([[V1]]);
// CPP-DECLTOP-NEXT: [[V3]] = [[V5]];
// CPP-DECLTOP-NEXT: [[V4]] = [[V6]];
// CPP-DECLTOP-NEXT: } else {
// CPP-DECLTOP-NEXT: [[V7]] = func_false_1([[V1]]);
// CPP-DECLTOP-NEXT: [[V8]] = func_false_2([[V1]]);
// CPP-DECLTOP-NEXT: [[V3]] = [[V7]];
// CPP-DECLTOP-NEXT: [[V4]] = [[V8]];
// CPP-DECLTOP-NEXT: }
// CPP-DECLTOP-NEXT: return;

View File

@ -0,0 +1,59 @@
// RUN: mlir-translate -split-input-file -mlir-to-cpp -verify-diagnostics %s
// expected-error@+1 {{'builtin.func' op with multiple blocks needs variables declared at top}}
func @multiple_blocks() {
^bb1:
br ^bb2
^bb2:
return
}
// -----
func @unsupported_std_op(%arg0: f64) -> f64 {
// expected-error@+1 {{'std.absf' op unable to find printer for op}}
%0 = absf %arg0 : f64
return %0 : f64
}
// -----
// expected-error@+1 {{cannot emit integer type 'i80'}}
func @unsupported_integer_type(%arg0 : i80) {
return
}
// -----
// expected-error@+1 {{cannot emit float type 'f80'}}
func @unsupported_float_type(%arg0 : f80) {
return
}
// -----
// expected-error@+1 {{cannot emit type 'memref<100xf32>'}}
func @memref_type(%arg0 : memref<100xf32>) {
return
}
// -----
// expected-error@+1 {{cannot emit type 'vector<100xf32>'}}
func @vector_type(%arg0 : vector<100xf32>) {
return
}
// -----
// expected-error@+1 {{cannot emit tensor type with non static shape}}
func @non_static_shape(%arg0 : tensor<?xf32>) {
return
}
// -----
// expected-error@+1 {{cannot emit unranked tensor type}}
func @unranked_tensor(%arg0 : tensor<*xf32>) {
return
}

View File

@ -0,0 +1,116 @@
// RUN: mlir-translate -mlir-to-cpp %s | FileCheck %s -check-prefix=CPP-DEFAULT
// RUN: mlir-translate -mlir-to-cpp -declare-variables-at-top %s | FileCheck %s -check-prefix=CPP-DECLTOP
func @std_constant() {
%c0 = constant 0 : i32
%c1 = constant 2 : index
%c2 = constant 2.0 : f32
%c3 = constant dense<0> : tensor<i32>
%c4 = constant dense<[0, 1]> : tensor<2xindex>
%c5 = constant dense<[[0.0, 1.0], [2.0, 3.0]]> : tensor<2x2xf32>
return
}
// CPP-DEFAULT: void std_constant() {
// CPP-DEFAULT-NEXT: int32_t [[V0:[^ ]*]] = 0;
// CPP-DEFAULT-NEXT: size_t [[V1:[^ ]*]] = 2;
// CPP-DEFAULT-NEXT: float [[V2:[^ ]*]] = (float)2.000000000e+00;
// CPP-DEFAULT-NEXT: Tensor<int32_t> [[V3:[^ ]*]] = {0};
// CPP-DEFAULT-NEXT: Tensor<size_t, 2> [[V4:[^ ]*]] = {0, 1};
// CPP-DEFAULT-NEXT: Tensor<float, 2, 2> [[V5:[^ ]*]] = {(float)0.0e+00, (float)1.000000000e+00, (float)2.000000000e+00, (float)3.000000000e+00};
// CPP-DECLTOP: void std_constant() {
// CPP-DECLTOP-NEXT: int32_t [[V0:[^ ]*]];
// CPP-DECLTOP-NEXT: size_t [[V1:[^ ]*]];
// CPP-DECLTOP-NEXT: float [[V2:[^ ]*]];
// CPP-DECLTOP-NEXT: Tensor<int32_t> [[V3:[^ ]*]];
// CPP-DECLTOP-NEXT: Tensor<size_t, 2> [[V4:[^ ]*]];
// CPP-DECLTOP-NEXT: Tensor<float, 2, 2> [[V5:[^ ]*]];
// CPP-DECLTOP-NEXT: [[V0]] = 0;
// CPP-DECLTOP-NEXT: [[V1]] = 2;
// CPP-DECLTOP-NEXT: [[V2]] = (float)2.000000000e+00;
// CPP-DECLTOP-NEXT: [[V3]] = {0};
// CPP-DECLTOP-NEXT: [[V4]] = {0, 1};
// CPP-DECLTOP-NEXT: [[V5]] = {(float)0.0e+00, (float)1.000000000e+00, (float)2.000000000e+00, (float)3.000000000e+00};
func @std_call() {
%0 = call @one_result () : () -> i32
%1 = call @one_result () : () -> i32
return
}
// CPP-DEFAULT: void std_call() {
// CPP-DEFAULT-NEXT: int32_t [[V0:[^ ]*]] = one_result();
// CPP-DEFAULT-NEXT: int32_t [[V1:[^ ]*]] = one_result();
// CPP-DECLTOP: void std_call() {
// CPP-DECLTOP-NEXT: int32_t [[V0:[^ ]*]];
// CPP-DECLTOP-NEXT: int32_t [[V1:[^ ]*]];
// CPP-DECLTOP-NEXT: [[V0]] = one_result();
// CPP-DECLTOP-NEXT: [[V1]] = one_result();
func @std_call_two_results() {
%c = constant 0 : i8
%0:2 = call @two_results () : () -> (i32, f32)
%1:2 = call @two_results () : () -> (i32, f32)
return
}
// CPP-DEFAULT: void std_call_two_results() {
// CPP-DEFAULT-NEXT: int8_t [[V0:[^ ]*]] = 0;
// CPP-DEFAULT-NEXT: int32_t [[V1:[^ ]*]];
// CPP-DEFAULT-NEXT: float [[V2:[^ ]*]];
// CPP-DEFAULT-NEXT: std::tie([[V1]], [[V2]]) = two_results();
// CPP-DEFAULT-NEXT: int32_t [[V3:[^ ]*]];
// CPP-DEFAULT-NEXT: float [[V4:[^ ]*]];
// CPP-DEFAULT-NEXT: std::tie([[V3]], [[V4]]) = two_results();
// CPP-DECLTOP: void std_call_two_results() {
// CPP-DECLTOP-NEXT: int8_t [[V0:[^ ]*]];
// CPP-DECLTOP-NEXT: int32_t [[V1:[^ ]*]];
// CPP-DECLTOP-NEXT: float [[V2:[^ ]*]];
// CPP-DECLTOP-NEXT: int32_t [[V3:[^ ]*]];
// CPP-DECLTOP-NEXT: float [[V4:[^ ]*]];
// CPP-DECLTOP-NEXT: [[V0]] = 0;
// CPP-DECLTOP-NEXT: std::tie([[V1]], [[V2]]) = two_results();
// CPP-DECLTOP-NEXT: std::tie([[V3]], [[V4]]) = two_results();
func @one_result() -> i32 {
%0 = constant 0 : i32
return %0 : i32
}
// CPP-DEFAULT: int32_t one_result() {
// CPP-DEFAULT-NEXT: int32_t [[V0:[^ ]*]] = 0;
// CPP-DEFAULT-NEXT: return [[V0]];
// CPP-DECLTOP: int32_t one_result() {
// CPP-DECLTOP-NEXT: int32_t [[V0:[^ ]*]];
// CPP-DECLTOP-NEXT: [[V0]] = 0;
// CPP-DECLTOP-NEXT: return [[V0]];
func @two_results() -> (i32, f32) {
%0 = constant 0 : i32
%1 = constant 1.0 : f32
return %0, %1 : i32, f32
}
// CPP-DEFAULT: std::tuple<int32_t, float> two_results() {
// CPP-DEFAULT: int32_t [[V0:[^ ]*]] = 0;
// CPP-DEFAULT: float [[V1:[^ ]*]] = (float)1.000000000e+00;
// CPP-DEFAULT: return std::make_tuple([[V0]], [[V1]]);
// CPP-DECLTOP: std::tuple<int32_t, float> two_results() {
// CPP-DECLTOP: int32_t [[V0:[^ ]*]];
// CPP-DECLTOP: float [[V1:[^ ]*]];
// CPP-DECLTOP: [[V0]] = 0;
// CPP-DECLTOP: [[V1]] = (float)1.000000000e+00;
// CPP-DECLTOP: return std::make_tuple([[V0]], [[V1]]);
func @single_return_statement(%arg0 : i32) -> i32 {
return %arg0 : i32
}
// CPP-DEFAULT: int32_t single_return_statement(int32_t [[V0:[^ ]*]]) {
// CPP-DEFAULT-NEXT: return [[V0]];
// CPP-DECLTOP: int32_t single_return_statement(int32_t [[V0:[^ ]*]]) {
// CPP-DECLTOP-NEXT: return [[V0]];