[Mlir-commits] [mlir] 2f0750d - [mlir] Add Cpp emitter

Marius Brehler llvmlistbot at llvm.org
Thu Sep 2 06:55:44 PDT 2021


Author: Marius Brehler
Date: 2021-09-02T13:51:05Z
New Revision: 2f0750dd2ec09b39ee3f05b4be73b14fce8828b3

URL: https://github.com/llvm/llvm-project/commit/2f0750dd2ec09b39ee3f05b4be73b14fce8828b3
DIFF: https://github.com/llvm/llvm-project/commit/2f0750dd2ec09b39ee3f05b4be73b14fce8828b3.diff

LOG: [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 at google.com>
Co-authored-by: Simon Camphausen <simon.camphausen at iml.fraunhofer.de>
Co-authored-by: Oliver Scherf <oliver.scherf at iml.fraunhofer.de>

Reviewed By: jpienaar

Differential Revision: https://reviews.llvm.org/D104632

Added: 
    mlir/docs/Dialects/emitc.md
    mlir/include/mlir/Target/Cpp/CppEmitter.h
    mlir/lib/Target/Cpp/CMakeLists.txt
    mlir/lib/Target/Cpp/TranslateRegistration.cpp
    mlir/lib/Target/Cpp/TranslateToCpp.cpp
    mlir/test/Target/Cpp/call.mlir
    mlir/test/Target/Cpp/common-cpp.mlir
    mlir/test/Target/Cpp/const.mlir
    mlir/test/Target/Cpp/control_flow.mlir
    mlir/test/Target/Cpp/for.mlir
    mlir/test/Target/Cpp/if.mlir
    mlir/test/Target/Cpp/invalid.mlir
    mlir/test/Target/Cpp/stdops.mlir

Modified: 
    mlir/include/mlir/Dialect/EmitC/IR/EmitCBase.td
    mlir/include/mlir/InitAllTranslations.h
    mlir/lib/Target/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/mlir/docs/Dialects/emitc.md b/mlir/docs/Dialects/emitc.md
new file mode 100644
index 0000000000000..044120b18806b
--- /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
index 5b7e81e2833a0..40ef2c40efb1f 100644
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitCBase.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitCBase.td
@@ -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;
 }
 

diff  --git a/mlir/include/mlir/InitAllTranslations.h b/mlir/include/mlir/InitAllTranslations.h
index fc319c09a8c8b..50ad3b285ba41 100644
--- a/mlir/include/mlir/InitAllTranslations.h
+++ b/mlir/include/mlir/InitAllTranslations.h
@@ -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;

diff  --git a/mlir/include/mlir/Target/Cpp/CppEmitter.h b/mlir/include/mlir/Target/Cpp/CppEmitter.h
new file mode 100644
index 0000000000000..30d3fff9fca88
--- /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 <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

diff  --git a/mlir/lib/Target/CMakeLists.txt b/mlir/lib/Target/CMakeLists.txt
index 5c0a142d4cb44..acc3985cc80e6 100644
--- 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
index 0000000000000..5def03bd91be4
--- /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
index 0000000000000..6f62eb9ecb054
--- /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<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

diff  --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
new file mode 100644
index 0000000000000..924c60828dbba
--- /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 
diff erent 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);
+}

diff  --git a/mlir/test/Target/Cpp/call.mlir b/mlir/test/Target/Cpp/call.mlir
new file mode 100644
index 0000000000000..f4d2e89c41a96
--- /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
index 0000000000000..101c80a4a56dd
--- /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 <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*">
+}

diff  --git a/mlir/test/Target/Cpp/const.mlir b/mlir/test/Target/Cpp/const.mlir
new file mode 100644
index 0000000000000..15438249b1f58
--- /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
index 0000000000000..ed8ee08ff46d5
--- /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
index 0000000000000..de575c6e013c4
--- /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
index 0000000000000..3638542eefb97
--- /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
index 0000000000000..d12245b0ade5d
--- /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 at +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 at +1 {{'std.absf' op unable to find printer for op}}
+  %0 = absf %arg0 : f64
+  return %0 : f64
+}
+
+// -----
+
+// expected-error at +1 {{cannot emit integer type 'i80'}}
+func @unsupported_integer_type(%arg0 : i80) {
+  return
+}
+
+// -----
+
+// expected-error at +1 {{cannot emit float type 'f80'}}
+func @unsupported_float_type(%arg0 : f80) {
+  return
+}
+
+// -----
+
+// expected-error at +1 {{cannot emit type 'memref<100xf32>'}}
+func @memref_type(%arg0 : memref<100xf32>) {
+  return
+}
+
+// -----
+
+// expected-error at +1 {{cannot emit type 'vector<100xf32>'}}
+func @vector_type(%arg0 : vector<100xf32>) {
+  return
+}
+
+// -----
+
+// expected-error at +1 {{cannot emit tensor type with non static shape}}
+func @non_static_shape(%arg0 : tensor<?xf32>) {
+  return
+}
+
+// -----
+
+// expected-error at +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
index 0000000000000..a523ac0cde047
--- /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<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]];


        


More information about the Mlir-commits mailing list