text: //===- 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 "emitc/Target/Cpp/CppEmitter.h"
  #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 "llvm/ADT/DenseMap.h"
  #include "llvm/ADT/StringExtras.h"
  #include "llvm/ADT/StringMap.h"
  #include "llvm/ADT/TypeSwitch.h"
  #include "llvm/Support/Debug.h"
  #include "llvm/Support/FormatVariadic.h"
  
  #define DEBUG_TYPE "translate-to-cpp"
  
  using namespace mlir;
  using namespace mlir::emitc;
  using llvm::formatv;
  
  /// Convenience functions to produce interleaved output with functions returning
  /// a LogicalResult. This is different than those in STLExtras as functions used
  /// on each element doesn't return a string.
  template <typename ForwardIterator, typename UnaryFunctor,
            typename NullaryFunctor>
  inline LogicalResult
  interleaveWithError(ForwardIterator begin, ForwardIterator end,
                      UnaryFunctor eachFn, NullaryFunctor betweenFn) {
    if (begin == end)
      return success();
    if (failed(eachFn(*begin)))
      return failure();
    ++begin;
    for (; begin != end; ++begin) {
      betweenFn();
      if (failed(eachFn(*begin)))
        return failure();
    }
    return success();
  }
  
  template <typename Container, typename UnaryFunctor, typename NullaryFunctor>
  inline LogicalResult interleaveWithError(const Container &c,
                                           UnaryFunctor eachFn,
                                           NullaryFunctor betweenFn) {
    return interleaveWithError(c.begin(), c.end(), eachFn, betweenFn);
  }
  
  template <typename Container, typename UnaryFunctor>
  inline LogicalResult interleaveCommaWithError(const Container &c,
                                                raw_ostream &os,
                                                UnaryFunctor eachFn) {
    return interleaveWithError(c.begin(), c.end(), eachFn, [&]() { os << ", "; });
  }
  
  namespace {
  /// Emitter that uses dialect specific emitters to emit C++ code.
  struct CppEmitter {
    explicit CppEmitter(raw_ostream &os, bool declareVariablesAtTop);
  
    /// Emits attribute or returns failure.
    LogicalResult emitAttribute(Location loc, Attribute attr);
  
    /// Emits operation 'op' with/without training semicolon or returns failure.
    LogicalResult emitOperation(Operation &op, bool trailingSemicolon);
  
    /// Emits type 'type' or returns failure.
    LogicalResult emitType(Location loc, Type type);
  
    /// Emits array of types as a std::tuple of the emitted types.
    /// - emits void for an empty array;
    /// - emits the type of the only element for arrays of size one;
    /// - emits a std::tuple otherwise;
    LogicalResult emitTypes(Location loc, ArrayRef<Type> types);
  
    /// Emits array of types as a std::tuple of the emitted types independently of
    /// the array size.
    LogicalResult emitTupleType(Location loc, ArrayRef<Type> types);
  
    /// Emits an assignment for a variable which has been declared previously.
    LogicalResult emitVariableAssignment(OpResult result);
  
    /// Emits a variable declaration for a result of an operation.
    LogicalResult emitVariableDeclaration(OpResult result,
                                          bool trailingSemicolon);
  
    /// Emits the variable declaration and assignment prefix for 'op'.
    /// - emits separate variable followed by std::tie for multi-valued operation;
    /// - emits single type followed by variable for single result;
    /// - emits nothing if no value produced by op;
    /// Emits final '=' operator where a type is produced. Returns failure if
    /// any result type could not be converted.
    LogicalResult emitAssignPrefix(Operation &op);
  
    /// Emits a label for the block.
    LogicalResult emitLabel(Block &block);
  
    /// Emits the operands and atttributes of the operation. All operands are
    /// emitted first and then all attributes in alphabetical order.
    LogicalResult emitOperandsAndAttributes(Operation &op,
                                            ArrayRef<StringRef> exclude = {});
  
    /// Emits the operands of the operation. All operands are emitted in order.
    LogicalResult emitOperands(Operation &op);
  
    /// Return the existing or a new name for a Value.
    StringRef getOrCreateName(Value val);
  
    /// Return the existing or a new label of a Block.
    StringRef getOrCreateName(Block &block);
  
    /// Whether to map an mlir integer to a unsigned integer in C++.
    bool shouldMapToUnsigned(IntegerType::SignednessSemantics val);
  
    /// RAII helper function to manage entering/exiting C++ scopes.
    struct Scope {
      Scope(CppEmitter &emitter)
          : valueMapperScope(emitter.valueMapper),
            blockMapperScope(emitter.blockMapper), emitter(emitter) {
        emitter.valueInScopeCount.push(emitter.valueInScopeCount.top());
        emitter.labelInScopeCount.push(emitter.labelInScopeCount.top());
      }
      ~Scope() {
        emitter.valueInScopeCount.pop();
        emitter.labelInScopeCount.pop();
      }
  
    private:
      llvm::ScopedHashTableScope<Value, std::string> valueMapperScope;
      llvm::ScopedHashTableScope<Block *, std::string> blockMapperScope;
      CppEmitter &emitter;
    };
  
    /// Returns wether the Value is assigned to a C++ variable in the scope.
    bool hasValueInScope(Value val);
  
    // Returns whether a label is assigned to the block.
    bool hasBlockLabel(Block &block);
  
    /// Returns the output stream.
    raw_indented_ostream &ostream() { return os; };
  
    /// Returns if all variables for op results and basic block arguments need to
    /// be declared at the beginning of a function.
    bool shouldDeclareVariablesAtTop() { return declareVariablesAtTop; };
  
  private:
    using ValueMapper = llvm::ScopedHashTable<Value, std::string>;
    using BlockMapper = llvm::ScopedHashTable<Block *, std::string>;
  
    /// Output stream to emit to.
    raw_indented_ostream os;
  
    /// Boolean to enforce that all variables for op results and block
    /// arguments are declared at the beginning of the function. This also
    /// includes results from ops located in nested regions.
    bool declareVariablesAtTop;
  
    /// Map from value to name of C++ variable that contain the name.
    ValueMapper valueMapper;
  
    /// Map from block to name of C++ label.
    BlockMapper blockMapper;
  
    /// The number of values in the current scope. This is used to declare the
    /// names of values in a scope.
    std::stack<int64_t> valueInScopeCount;
    std::stack<int64_t> labelInScopeCount;
  };
  } // namespace
  
  static LogicalResult printConstantOp(CppEmitter &emitter, Operation *operation,
                                       Attribute value) {
    OpResult result = operation->getResult(0);
  
    // Only emit an assignment as the variable was already declared when printing
    // the FuncOp.
    if (emitter.shouldDeclareVariablesAtTop()) {
      // Skip the assignment if the emitc.constant has no value.
      if (auto oAttr = value.dyn_cast<emitc::OpaqueAttr>()) {
        if (oAttr.getValue().empty())
          return success();
      }
  
      if (failed(emitter.emitVariableAssignment(result)))
        return failure();
      return emitter.emitAttribute(operation->getLoc(), value);
    }
  
    // Emit a variable declaration for an emitc.constant op without value.
    if (auto oAttr = value.dyn_cast<emitc::OpaqueAttr>()) {
      if (oAttr.getValue().empty())
        // The semicolon gets printed by the emitOperation function.
        return emitter.emitVariableDeclaration(result,
                                               /*trailingSemicolon=*/false);
    }
  
    // Emit a variable declaration.
    if (failed(emitter.emitAssignPrefix(*operation)))
      return failure();
    return emitter.emitAttribute(operation->getLoc(), value);
  }
  
  static LogicalResult printOperation(CppEmitter &emitter,
                                      emitc::ConstantOp constantOp) {
    Operation *operation = constantOp.getOperation();
    Attribute value = constantOp.value();
  
    return printConstantOp(emitter, operation, value);
  }
  
  static LogicalResult printOperation(CppEmitter &emitter,
                                      mlir::ConstantOp constantOp) {
    Operation *operation = constantOp.getOperation();
    Attribute value = constantOp.value();
  
    return printConstantOp(emitter, operation, value);
  }
  
  static LogicalResult printOperation(CppEmitter &emitter, BranchOp branchOp) {
    raw_ostream &os = emitter.ostream();
    Block &successor = *branchOp.getSuccessor();
  
    for (auto pair :
         llvm::zip(branchOp.getOperands(), successor.getArguments())) {
      Value &operand = std::get<0>(pair);
      BlockArgument &argument = std::get<1>(pair);
      os << emitter.getOrCreateName(argument) << " = "
         << emitter.getOrCreateName(operand) << ";\n";
    }
  
    os << "goto ";
    if (!(emitter.hasBlockLabel(successor)))
      return branchOp.emitOpError("unable to find label for successor block");
    os << emitter.getOrCreateName(successor);
    return success();
  }
  
  static LogicalResult printOperation(CppEmitter &emitter,
                                      CondBranchOp condBranchOp) {
    raw_ostream &os = emitter.ostream();
    Block &trueSuccessor = *condBranchOp.getTrueDest();
    Block &falseSuccessor = *condBranchOp.getFalseDest();
  
    os << "if (" << emitter.getOrCreateName(condBranchOp.getCondition())
       << ") {\n";
  
    // If condition is true.
    for (auto pair : llvm::zip(condBranchOp.getTrueOperands(),
                               trueSuccessor.getArguments())) {
      Value &operand = std::get<0>(pair);
      BlockArgument &argument = std::get<1>(pair);
      os << emitter.getOrCreateName(argument) << " = "
         << emitter.getOrCreateName(operand) << ";\n";
    }
  
    os << "goto ";
    if (!(emitter.hasBlockLabel(trueSuccessor))) {
      return condBranchOp.emitOpError("unable to find label for successor block");
    }
    os << emitter.getOrCreateName(trueSuccessor) << ";\n";
    os << "} else {\n";
    // If condition is false.
    for (auto pair : llvm::zip(condBranchOp.getFalseOperands(),
                               falseSuccessor.getArguments())) {
      Value &operand = std::get<0>(pair);
      BlockArgument &argument = std::get<1>(pair);
      os << emitter.getOrCreateName(argument) << " = "
         << emitter.getOrCreateName(operand) << ";\n";
    }
  
    os << "goto ";
    if (!(emitter.hasBlockLabel(falseSuccessor))) {
      return condBranchOp.emitOpError()
             << "unable to find label for successor block";
    }
    os << emitter.getOrCreateName(falseSuccessor) << ";\n";
    os << "}";
    return success();
  }
  
  static LogicalResult printOperation(CppEmitter &emitter, mlir::CallOp callOp) {
    if (failed(emitter.emitAssignPrefix(*callOp.getOperation())))
      return failure();
  
    raw_ostream &os = emitter.ostream();
    os << callOp.getCallee() << "(";
    if (failed(emitter.emitOperands(*callOp.getOperation())))
      return failure();
    os << ")";
    return success();
  }
  
  static LogicalResult printOperation(CppEmitter &emitter, emitc::CallOp callOp) {
    raw_ostream &os = emitter.ostream();
    Operation &op = *callOp.getOperation();
  
    if (failed(emitter.emitAssignPrefix(op)))
      return failure();
    os << callOp.callee();
  
    auto emitArgs = [&](Attribute attr) -> LogicalResult {
      if (auto t = attr.dyn_cast<IntegerAttr>()) {
        // Index attributes are treated specially as operand index.
        if (t.getType().isIndex()) {
          int64_t idx = t.getInt();
          if ((idx < 0) || (idx >= op.getNumOperands()))
            return op.emitOpError("invalid operand index");
          if (!emitter.hasValueInScope(op.getOperand(idx)))
            return op.emitOpError("operand ")
                   << idx << "'s value not defined in scope";
          os << emitter.getOrCreateName(op.getOperand(idx));
          return success();
        }
      }
      if (failed(emitter.emitAttribute(op.getLoc(), attr)))
        return failure();
  
      return success();
    };
  
    if (callOp.template_args()) {
      os << "<";
      if (failed(interleaveCommaWithError(*callOp.template_args(), os, emitArgs)))
        return failure();
      os << ">";
    }
  
    os << "(";
  
    LogicalResult emittedArgs =
        callOp.args() ? interleaveCommaWithError(*callOp.args(), os, emitArgs)
                      : emitter.emitOperands(op);
    if (failed(emittedArgs))
      return failure();
    os << ")";
    return success();
  }
  
  static LogicalResult printOperation(CppEmitter &emitter,
                                      emitc::ApplyOp applyOp) {
    raw_ostream &os = emitter.ostream();
    Operation &op = *applyOp.getOperation();
  
    if (failed(emitter.emitAssignPrefix(op)))
      return failure();
    os << applyOp.applicableOperator();
    os << emitter.getOrCreateName(applyOp.getOperand());
  
    return success();
  }
  
  static LogicalResult printOperation(CppEmitter &emitter,
                                      emitc::IncludeOp includeOp) {
    raw_ostream &os = emitter.ostream();
  
    os << "#include ";
    if (includeOp.is_standard_include())
      os << "<" << includeOp.include() << ">";
    else
      os << "\"" << includeOp.include() << "\"";
  
    return success();
  }
  
  static LogicalResult printOperation(CppEmitter &emitter, scf::ForOp forOp) {
  
    raw_indented_ostream &os = emitter.ostream();
  
    OperandRange operands = forOp.getIterOperands();
    Block::BlockArgListType iterArgs = forOp.getRegionIterArgs();
    Operation::result_range results = forOp.getResults();
  
    if (!emitter.shouldDeclareVariablesAtTop()) {
      for (OpResult result : results) {
        if (failed(emitter.emitVariableDeclaration(result,
                                                   /*trailingSemicolon=*/true)))
          return failure();
      }
    }
  
    for (auto pair : llvm::zip(iterArgs, operands)) {
      if (failed(emitter.emitType(forOp.getLoc(), std::get<0>(pair).getType())))
        return failure();
      os << " " << emitter.getOrCreateName(std::get<0>(pair)) << " = ";
      os << emitter.getOrCreateName(std::get<1>(pair)) << ";";
      os << "\n";
    }
  
    os << "for (";
    if (failed(
            emitter.emitType(forOp.getLoc(), forOp.getInductionVar().getType())))
      return failure();
    os << " ";
    os << emitter.getOrCreateName(forOp.getInductionVar());
    os << " = ";
    os << emitter.getOrCreateName(forOp.lowerBound());
    os << "; ";
    os << emitter.getOrCreateName(forOp.getInductionVar());
    os << " < ";
    os << emitter.getOrCreateName(forOp.upperBound());
    os << "; ";
    os << emitter.getOrCreateName(forOp.getInductionVar());
    os << " += ";
    os << emitter.getOrCreateName(forOp.step());
    os << ") {\n";
    os.indent();
  
    Region &forRegion = forOp.region();
    auto regionOps = forRegion.getOps();
  
    // We skip the trailing yield op because this updates the result variables
    // of the for op in the generated code. Instead we update the iterArgs at
    // the end of a loop iteration and set the result variables after the for
    // loop.
    for (auto it = regionOps.begin(); std::next(it) != regionOps.end(); ++it) {
      if (failed(emitter.emitOperation(*it, /*trailingSemicolon=*/true)))
        return failure();
    }
  
    Operation *yieldOp = forRegion.getBlocks().front().getTerminator();
    // Copy yield operands into iterArgs at the end of a loop iteration.
    for (auto pair : llvm::zip(iterArgs, yieldOp->getOperands())) {
      BlockArgument iterArg = std::get<0>(pair);
      Value operand = std::get<1>(pair);
      os << emitter.getOrCreateName(iterArg) << " = "
         << emitter.getOrCreateName(operand) << ";\n";
    }
  
    os.unindent() << "}";
  
    // Copy iterArgs into results after the for loop.
    for (auto pair : llvm::zip(results, iterArgs)) {
      OpResult result = std::get<0>(pair);
      BlockArgument iterArg = std::get<1>(pair);
      os << "\n"
         << emitter.getOrCreateName(result) << " = "
         << emitter.getOrCreateName(iterArg) << ";";
    }
  
    return success();
  }
  
  static LogicalResult printOperation(CppEmitter &emitter, scf::IfOp ifOp) {
    raw_indented_ostream &os = emitter.ostream();
  
    if (!emitter.shouldDeclareVariablesAtTop()) {
      for (OpResult result : ifOp.getResults()) {
        if (failed(emitter.emitVariableDeclaration(result,
                                                   /*trailingSemicolon=*/true)))
          return failure();
      }
    }
  
    os << "if (";
    if (failed(emitter.emitOperands(*ifOp.getOperation())))
      return failure();
    os << ") {\n";
    os.indent();
  
    Region &thenRegion = ifOp.thenRegion();
    for (Operation &op : thenRegion.getOps()) {
      // Note: This prints a superfluous semicolon if the terminating yield op has
      // zero results.
      if (failed(emitter.emitOperation(op, /*trailingSemicolon=*/true)))
        return failure();
    }
  
    os.unindent() << "}";
  
    Region &elseRegion = ifOp.elseRegion();
    if (!elseRegion.empty()) {
      os << " else {\n";
      os.indent();
  
      for (Operation &op : elseRegion.getOps()) {
        // Note: This prints a superfluous semicolon if the terminating yield op
        // has zero results.
        if (failed(emitter.emitOperation(op, /*trailingSemicolon=*/true)))
          return failure();
      }
  
      os.unindent() << "}";
    }
  
    return success();
  }
  
  static LogicalResult printOperation(CppEmitter &emitter, scf::YieldOp yieldOp) {
    raw_ostream &os = emitter.ostream();
    Operation &parentOp = *yieldOp.getOperation()->getParentOp();
  
    if (yieldOp.getNumOperands() != parentOp.getNumResults()) {
      return yieldOp.emitError("number of operands does not to match the number "
                               "of the parent op's results");
    }
  
    if (failed(interleaveWithError(
            llvm::zip(parentOp.getResults(), yieldOp.getOperands()),
            [&](auto pair) -> LogicalResult {
              auto result = std::get<0>(pair);
              auto operand = std::get<1>(pair);
              os << emitter.getOrCreateName(result) << " = ";
  
              if (!emitter.hasValueInScope(operand))
                return yieldOp.emitError("operand value not in scope");
              os << emitter.getOrCreateName(operand);
              return success();
            },
            [&]() { os << ";\n"; })))
      return failure();
  
    return success();
  }
  
  static LogicalResult printOperation(CppEmitter &emitter, ReturnOp returnOp) {
    raw_ostream &os = emitter.ostream();
    os << "return";
    switch (returnOp.getNumOperands()) {
    case 0:
      return success();
    case 1:
      os << " " << emitter.getOrCreateName(returnOp.getOperand(0));
      return success(emitter.hasValueInScope(returnOp.getOperand(0)));
    default:
      os << " std::make_tuple(";
      if (failed(emitter.emitOperandsAndAttributes(*returnOp.getOperation())))
        return failure();
      os << ")";
      return success();
    }
  }
  
  static LogicalResult printOperation(CppEmitter &emitter, ModuleOp moduleOp) {
    CppEmitter::Scope scope(emitter);
  
    for (Operation &op : moduleOp) {
      if (failed(emitter.emitOperation(op, /*trailingSemicolon=*/false)))
        return failure();
    }
    return success();
  }
  
  static LogicalResult printOperation(CppEmitter &emitter, FuncOp functionOp) {
    // We need to declare variables at top if the function has multiple blocks.
    if (!emitter.shouldDeclareVariablesAtTop() &&
        functionOp.getBlocks().size() > 1) {
      return functionOp.emitOpError(
          "with multiple blocks needs variables declared at top");
    }
  
    CppEmitter::Scope scope(emitter);
    raw_indented_ostream &os = emitter.ostream();
    if (failed(emitter.emitTypes(functionOp.getLoc(),
                                 functionOp.getType().getResults())))
      return failure();
    os << " " << functionOp.getName();
  
    os << "(";
    if (failed(interleaveCommaWithError(
            functionOp.getArguments(), os,
            [&](BlockArgument arg) -> LogicalResult {
              if (failed(emitter.emitType(functionOp.getLoc(), arg.getType())))
                return failure();
              os << " " << emitter.getOrCreateName(arg);
              return success();
            })))
      return failure();
    os << ") {\n";
    os.indent();
    if (emitter.shouldDeclareVariablesAtTop()) {
      // Declare all variables that hold op results including those from nested
      // regions.
      WalkResult result =
          functionOp.walk<WalkOrder::PreOrder>([&](Operation *op) -> WalkResult {
            for (OpResult result : op->getResults()) {
              if (failed(emitter.emitVariableDeclaration(
                      result, /*trailingSemicolon=*/true))) {
                return WalkResult(
                    op->emitError("unable to declare result variable for op"));
              }
            }
            return WalkResult::advance();
          });
      if (result.wasInterrupted())
        return failure();
    }
  
    Region::BlockListType &blocks = functionOp.getBlocks();
    // Create label names for basic blocks.
    for (Block &block : blocks) {
      emitter.getOrCreateName(block);
    }
  
    // Declare variables for basic block arguments.
    for (auto it = std::next(blocks.begin()); it != blocks.end(); ++it) {
      Block &block = *it;
      for (BlockArgument &arg : block.getArguments()) {
        if (emitter.hasValueInScope(arg))
          return functionOp.emitOpError(" block argument #")
                 << arg.getArgNumber() << " is out of scope";
        if (failed(
                emitter.emitType(block.getParentOp()->getLoc(), arg.getType()))) {
          return failure();
        }
        os << " " << emitter.getOrCreateName(arg) << ";\n";
      }
    }
  
    for (Block &block : blocks) {
      // Only print a label if there is more than one block.
      if (blocks.size() > 1) {
        if (failed(emitter.emitLabel(block)))
          return failure();
      }
      for (Operation &op : block.getOperations()) {
        // When generating code for an scf.if or std.cond_br op no semicolon needs
        // to be printed after the closing brace.
        // When generating code for an scf.for op, printing a trailing semicolon
        // is handled within the printOperation function.
        bool trailingSemicolon = !isa<scf::IfOp, scf::ForOp, CondBranchOp>(op);
  
        if (failed(emitter.emitOperation(
                op, /*trailingSemicolon=*/trailingSemicolon)))
          return failure();
      }
    }
    os.unindent() << "}\n";
    return success();
  }
  
  CppEmitter::CppEmitter(raw_ostream &os, bool declareVariablesAtTop)
      : os(os), declareVariablesAtTop(declareVariablesAtTop) {
    valueInScopeCount.push(0);
    labelInScopeCount.push(0);
  }
  
  /// Return the existing or a new name for a Value.
  StringRef CppEmitter::getOrCreateName(Value val) {
    if (!valueMapper.count(val))
      valueMapper.insert(val, formatv("v{0}", ++valueInScopeCount.top()));
    return *valueMapper.begin(val);
  }
  
  /// Return the existing or a new label for a Block.
  StringRef CppEmitter::getOrCreateName(Block &block) {
    if (!blockMapper.count(&block))
      blockMapper.insert(&block, formatv("label{0}", ++labelInScopeCount.top()));
    return *blockMapper.begin(&block);
  }
  
  bool CppEmitter::shouldMapToUnsigned(IntegerType::SignednessSemantics val) {
    switch (val) {
    case IntegerType::Signless:
      return false;
    case IntegerType::Signed:
      return false;
    case IntegerType::Unsigned:
      return true;
    }
  }
  
  bool CppEmitter::hasValueInScope(Value val) { return valueMapper.count(val); }
  
  bool CppEmitter::hasBlockLabel(Block &block) {
    return blockMapper.count(&block);
  }
  
  LogicalResult CppEmitter::emitAttribute(Location loc, Attribute attr) {
    auto printInt = [&](APInt val, bool isSigned) {
      if (val.getBitWidth() == 1) {
        if (val.getBoolValue())
          os << "true";
        else
          os << "false";
      } else {
        val.print(os, isSigned);
      }
    };
  
    auto printFloat = [&](APFloat val) {
      if (val.isFinite()) {
        SmallString<128> strValue;
        // Use default values of toString except don't truncate zeros.
        val.toString(strValue, 0, 0, false);
        switch (llvm::APFloatBase::SemanticsToEnum(val.getSemantics())) {
        case llvm::APFloatBase::S_IEEEsingle:
          os << "(float)";
          break;
        case llvm::APFloatBase::S_IEEEdouble:
          os << "(double)";
          break;
        default:
          break;
        };
        os << strValue;
      } else if (val.isNaN()) {
        os << "NAN";
      } else if (val.isInfinity()) {
        if (val.isNegative())
          os << "-";
        os << "INFINITY";
      }
    };
  
    // Print floating point attributes.
    if (auto fAttr = attr.dyn_cast<FloatAttr>()) {
      printFloat(fAttr.getValue());
      return success();
    }
    if (auto dense = attr.dyn_cast<DenseFPElementsAttr>()) {
      os << '{';
      interleaveComma(dense, os, [&](APFloat val) { printFloat(val); });
      os << '}';
      return success();
    }
  
    // Print integer attributes.
    if (auto iAttr = attr.dyn_cast<IntegerAttr>()) {
      if (auto iType = iAttr.getType().dyn_cast<IntegerType>()) {
        printInt(iAttr.getValue(), shouldMapToUnsigned(iType.getSignedness()));
        return success();
      }
      if (auto iType = iAttr.getType().dyn_cast<IndexType>()) {
        printInt(iAttr.getValue(), false);
        return success();
      }
    }
    if (auto dense = attr.dyn_cast<DenseIntElementsAttr>()) {
      if (auto iType = dense.getType()
                           .cast<TensorType>()
                           .getElementType()
                           .dyn_cast<IntegerType>()) {
        os << '{';
        interleaveComma(dense, os, [&](APInt val) {
          printInt(val, shouldMapToUnsigned(iType.getSignedness()));
        });
        os << '}';
        return success();
      }
      if (auto iType = dense.getType()
                           .cast<TensorType>()
                           .getElementType()
                           .dyn_cast<IndexType>()) {
        os << '{';
        interleaveComma(dense, os, [&](APInt val) { printInt(val, false); });
        os << '}';
        return success();
      }
    }
  
    // Print opaque attributes.
    if (auto oAttr = attr.dyn_cast<emitc::OpaqueAttr>()) {
      os << oAttr.getValue();
      return success();
    }
  
    // Print symbolic reference attributes.
    if (auto sAttr = attr.dyn_cast<SymbolRefAttr>()) {
      if (sAttr.getNestedReferences().size() > 1)
        return emitError(loc, "attribute has more than 1 nested reference");
      os << sAttr.getRootReference();
      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);
  }
  