diff --git a/mlir/docs/Dialects/emitc.md b/mlir/docs/Dialects/emitc.md new file mode 100644 --- /dev/null +++ b/mlir/docs/Dialects/emitc.md @@ -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` diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitCBase.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitCBase.td --- a/mlir/include/mlir/Dialect/EmitC/IR/EmitCBase.td +++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitCBase.td @@ -22,6 +22,12 @@ 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; } diff --git a/mlir/include/mlir/InitAllTranslations.h b/mlir/include/mlir/InitAllTranslations.h --- a/mlir/include/mlir/InitAllTranslations.h +++ b/mlir/include/mlir/InitAllTranslations.h @@ -18,6 +18,7 @@ void registerFromLLVMIRTranslation(); void registerFromSPIRVTranslation(); +void registerToCppTranslation(); void registerToLLVMIRTranslation(); void registerToSPIRVTranslation(); @@ -28,6 +29,7 @@ static bool initOnce = []() { registerFromLLVMIRTranslation(); registerFromSPIRVTranslation(); + registerToCppTranslation(); registerToLLVMIRTranslation(); registerToSPIRVTranslation(); return true; diff --git a/mlir/include/mlir/Target/Cpp/CppEmitter.h b/mlir/include/mlir/Target/Cpp/CppEmitter.h new file mode 100644 --- /dev/null +++ b/mlir/include/mlir/Target/Cpp/CppEmitter.h @@ -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 + +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 diff --git a/mlir/lib/Target/CMakeLists.txt b/mlir/lib/Target/CMakeLists.txt --- a/mlir/lib/Target/CMakeLists.txt +++ b/mlir/lib/Target/CMakeLists.txt @@ -1,2 +1,3 @@ +add_subdirectory(Cpp) add_subdirectory(SPIRV) add_subdirectory(LLVMIR) diff --git a/mlir/lib/Target/Cpp/CMakeLists.txt b/mlir/lib/Target/Cpp/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/mlir/lib/Target/Cpp/CMakeLists.txt @@ -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 + ) diff --git a/mlir/lib/Target/Cpp/TranslateRegistration.cpp b/mlir/lib/Target/Cpp/TranslateRegistration.cpp new file mode 100644 --- /dev/null +++ b/mlir/lib/Target/Cpp/TranslateRegistration.cpp @@ -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 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 ®istry) { + // clang-format off + registry.insert(); + // clang-format on + }); +} + +} // namespace mlir diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp new file mode 100644 --- /dev/null +++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp @@ -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 +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 +inline LogicalResult interleaveWithError(const Container &c, + UnaryFunctor eachFn, + NullaryFunctor betweenFn) { + return interleaveWithError(c.begin(), c.end(), eachFn, betweenFn); +} + +template +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 types); + + /// Emits array of types as a std::tuple of the emitted types independently of + /// the array size. + LogicalResult emitTupleType(Location loc, ArrayRef 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 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 valueMapperScope; + llvm::ScopedHashTableScope 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; + using BlockMapper = llvm::ScopedHashTable; + + /// 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 valueInScopeCount; + std::stack 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()) { + 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()) { + 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()) { + // 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([&](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(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()) { + printFloat(fAttr.getValue()); + return success(); + } + if (auto dense = attr.dyn_cast()) { + os << '{'; + interleaveComma(dense, os, [&](APFloat val) { printFloat(val); }); + os << '}'; + return success(); + } + + // Print integer attributes. + if (auto iAttr = attr.dyn_cast()) { + if (auto iType = iAttr.getType().dyn_cast()) { + printInt(iAttr.getValue(), shouldMapToUnsigned(iType.getSignedness())); + return success(); + } + if (auto iType = iAttr.getType().dyn_cast()) { + printInt(iAttr.getValue(), false); + return success(); + } + } + if (auto dense = attr.dyn_cast()) { + if (auto iType = dense.getType() + .cast() + .getElementType() + .dyn_cast()) { + os << '{'; + interleaveComma(dense, os, [&](APInt val) { + printInt(val, shouldMapToUnsigned(iType.getSignedness())); + }); + os << '}'; + return success(); + } + if (auto iType = dense.getType() + .cast() + .getElementType() + .dyn_cast()) { + os << '{'; + interleaveComma(dense, os, [&](APInt val) { printInt(val, false); }); + os << '}'; + return success(); + } + } + + // Print opaque attributes. + if (auto oAttr = attr.dyn_cast()) { + os << oAttr.getValue(); + return success(); + } + + // Print symbolic reference attributes. + if (auto sAttr = attr.dyn_cast()) { + 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()) + 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 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(&op) + // EmitC ops. + .Case( + [&](auto op) { return printOperation(*this, op); }) + // SCF ops. + .Case( + [&](auto op) { return printOperation(*this, op); }) + // Standard ops. + .Case( + [&](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()) { + 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()) { + 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()) + return (os << "size_t"), success(); + if (auto tType = type.dyn_cast()) { + 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()) + return emitTupleType(loc, tType.getTypes()); + if (auto oType = type.dyn_cast()) { + os << oType.getValue(); + return success(); + } + return emitError(loc, "cannot emit type ") << type; +} + +LogicalResult CppEmitter::emitTypes(Location loc, ArrayRef 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 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); +} diff --git a/mlir/test/Target/Cpp/call.mlir b/mlir/test/Target/Cpp/call.mlir new file mode 100644 --- /dev/null +++ b/mlir/test/Target/Cpp/call.mlir @@ -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(); diff --git a/mlir/test/Target/Cpp/common-cpp.mlir b/mlir/test/Target/Cpp/common-cpp.mlir new file mode 100644 --- /dev/null +++ b/mlir/test/Target/Cpp/common-cpp.mlir @@ -0,0 +1,91 @@ +// RUN: mlir-translate -mlir-to-cpp %s | FileCheck %s + +// CHECK: #include "myheader.h" +emitc.include "myheader.h" +// CHECK: #include +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 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 mixed_types(Tensor [[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 mhlo_convert(Tensor [[V1]]) +func @mhlo_convert(%arg0: tensor) -> tensor { + // CHECK: mhlo::convert([[V1]]); + %0 = emitc.call "mhlo::convert"(%arg0) {args = [0 : index]} : (tensor) -> tensor + return %0 : tensor +} + +// 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*"> +} diff --git a/mlir/test/Target/Cpp/const.mlir b/mlir/test/Target/Cpp/const.mlir new file mode 100644 --- /dev/null +++ b/mlir/test/Target/Cpp/const.mlir @@ -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; diff --git a/mlir/test/Target/Cpp/control_flow.mlir b/mlir/test/Target/Cpp/control_flow.mlir new file mode 100644 --- /dev/null +++ b/mlir/test/Target/Cpp/control_flow.mlir @@ -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: } diff --git a/mlir/test/Target/Cpp/for.mlir b/mlir/test/Target/Cpp/for.mlir new file mode 100644 --- /dev/null +++ b/mlir/test/Target/Cpp/for.mlir @@ -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; diff --git a/mlir/test/Target/Cpp/if.mlir b/mlir/test/Target/Cpp/if.mlir new file mode 100644 --- /dev/null +++ b/mlir/test/Target/Cpp/if.mlir @@ -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; diff --git a/mlir/test/Target/Cpp/invalid.mlir b/mlir/test/Target/Cpp/invalid.mlir new file mode 100644 --- /dev/null +++ b/mlir/test/Target/Cpp/invalid.mlir @@ -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) { + return +} + +// ----- + +// expected-error@+1 {{cannot emit unranked tensor type}} +func @unranked_tensor(%arg0 : tensor<*xf32>) { + return +} diff --git a/mlir/test/Target/Cpp/stdops.mlir b/mlir/test/Target/Cpp/stdops.mlir new file mode 100644 --- /dev/null +++ b/mlir/test/Target/Cpp/stdops.mlir @@ -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 + %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 [[V3:[^ ]*]] = {0}; +// CPP-DEFAULT-NEXT: Tensor [[V4:[^ ]*]] = {0, 1}; +// CPP-DEFAULT-NEXT: Tensor [[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 [[V3:[^ ]*]]; +// CPP-DECLTOP-NEXT: Tensor [[V4:[^ ]*]]; +// CPP-DECLTOP-NEXT: Tensor [[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 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 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]];