//===- Operator.cpp - Operator class --------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Operator wrapper to simplify using TableGen Record defining a MLIR Op.
//
//===----------------------------------------------------------------------===//

#include "mlir/TableGen/Operator.h"
#include "mlir/TableGen/Argument.h"
#include "mlir/TableGen/Predicate.h"
#include "mlir/TableGen/Trait.h"
#include "mlir/TableGen/Type.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/Sequence.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/TypeSwitch.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/TableGen/Error.h"
#include "llvm/TableGen/Record.h"

#define DEBUG_TYPE "mlir-tblgen-operator"

using namespace mlir;
using namespace mlir::tblgen;

using llvm::DagInit;
using llvm::DefInit;
using llvm::Init;
using llvm::ListInit;
using llvm::Record;
using llvm::StringInit;

Operator::Operator(const Record &def)
    : dialect(def.getValueAsDef("opDialect")), def(def) {
  // The first `_` in the op's TableGen def name is treated as separating the
  // dialect prefix and the op class name. The dialect prefix will be ignored if
  // not empty. Otherwise, if def name starts with a `_`, the `_` is considered
  // as part of the class name.
  StringRef prefix;
  std::tie(prefix, cppClassName) = def.getName().split('_');
  if (prefix.empty()) {
    // Class name with a leading underscore and without dialect prefix
    cppClassName = def.getName();
  } else if (cppClassName.empty()) {
    // Class name without dialect prefix
    cppClassName = prefix;
  }

  cppNamespace = def.getValueAsString("cppNamespace");

  populateOpStructure();
  assertInvariants();
}

std::string Operator::getOperationName() const {
  auto prefix = dialect.getName();
  auto opName = def.getValueAsString("opName");
  if (prefix.empty())
    return std::string(opName);
  return std::string(llvm::formatv("{0}.{1}", prefix, opName));
}

std::string Operator::getAdaptorName() const {
  return std::string(llvm::formatv("{0}Adaptor", getCppClassName()));
}

std::string Operator::getGenericAdaptorName() const {
  return std::string(llvm::formatv("{0}GenericAdaptor", getCppClassName()));
}

/// Assert the invariants of accessors generated for the given name.
static void assertAccessorInvariants(const Operator &op, StringRef name) {
  std::string accessorName =
      convertToCamelFromSnakeCase(name, /*capitalizeFirst=*/true);

  // Functor used to detect when an accessor will cause an overlap with an
  // operation API.
  //
  // There are a little bit more invasive checks possible for cases where not
  // all ops have the trait that would cause overlap. For many cases here,
  // renaming would be better (e.g., we can only guard in limited manner
  // against methods from traits and interfaces here, so avoiding these in op
  // definition is safer).
  auto nameOverlapsWithOpAPI = [&](StringRef newName) {
    if (newName == "AttributeNames" || newName == "Attributes" ||
        newName == "Operation")
      return true;
    if (newName == "Operands")
      return op.getNumOperands() != 1 || op.getNumVariableLengthOperands() != 1;
    if (newName == "Regions")
      return op.getNumRegions() != 1 || op.getNumVariadicRegions() != 1;
    if (newName == "Type")
      return op.getNumResults() != 1;
    return false;
  };
  if (nameOverlapsWithOpAPI(accessorName)) {
    // This error could be avoided in situations where the final function is
    // identical, but preferably the op definition should avoid using generic
    // names.
    PrintFatalError(op.getLoc(), "generated accessor for `" + name +
                                     "` overlaps with a default one; please "
                                     "rename to avoid overlap");
  }
}

void Operator::assertInvariants() const {
  // Check that the name of arguments/results/regions/successors don't overlap.
  DenseMap<StringRef, StringRef> existingNames;
  auto checkName = [&](StringRef name, StringRef entity) {
    if (name.empty())
      return;
    auto insertion = existingNames.insert({name, entity});
    if (insertion.second) {
      // Assert invariants for accessors generated for this name.
      assertAccessorInvariants(*this, name);
      return;
    }
    if (entity == insertion.first->second)
      PrintFatalError(getLoc(), "op has a conflict with two " + entity +
                                    " having the same name '" + name + "'");
    PrintFatalError(getLoc(), "op has a conflict with " +
                                  insertion.first->second + " and " + entity +
                                  " both having an entry with the name '" +
                                  name + "'");
  };
  // Check operands amongst themselves.
  for (int i : llvm::seq<int>(0, getNumOperands()))
    checkName(getOperand(i).name, "operands");

  // Check results amongst themselves and against operands.
  for (int i : llvm::seq<int>(0, getNumResults()))
    checkName(getResult(i).name, "results");

  // Check regions amongst themselves and against operands and results.
  for (int i : llvm::seq<int>(0, getNumRegions()))
    checkName(getRegion(i).name, "regions");

  // Check successors amongst themselves and against operands, results, and
  // regions.
  for (int i : llvm::seq<int>(0, getNumSuccessors()))
    checkName(getSuccessor(i).name, "successors");
}

StringRef Operator::getDialectName() const { return dialect.getName(); }

StringRef Operator::getCppClassName() const { return cppClassName; }

std::string Operator::getQualCppClassName() const {
  if (cppNamespace.empty())
    return std::string(cppClassName);
  return std::string(llvm::formatv("{0}::{1}", cppNamespace, cppClassName));
}

StringRef Operator::getCppNamespace() const { return cppNamespace; }

int Operator::getNumResults() const {
  const DagInit *results = def.getValueAsDag("results");
  return results->getNumArgs();
}

StringRef Operator::getExtraClassDeclaration() const {
  constexpr auto attr = "extraClassDeclaration";
  if (def.isValueUnset(attr))
    return {};
  return def.getValueAsString(attr);
}

StringRef Operator::getExtraClassDefinition() const {
  constexpr auto attr = "extraClassDefinition";
  if (def.isValueUnset(attr))
    return {};
  return def.getValueAsString(attr);
}

const Record &Operator::getDef() const { return def; }

bool Operator::skipDefaultBuilders() const {
  return def.getValueAsBit("skipDefaultBuilders");
}

auto Operator::result_begin() const -> const_value_iterator {
  return results.begin();
}

auto Operator::result_end() const -> const_value_iterator {
  return results.end();
}

auto Operator::getResults() const -> const_value_range {
  return {result_begin(), result_end()};
}

TypeConstraint Operator::getResultTypeConstraint(int index) const {
  const DagInit *results = def.getValueAsDag("results");
  return TypeConstraint(cast<DefInit>(results->getArg(index)));
}

StringRef Operator::getResultName(int index) const {
  const DagInit *results = def.getValueAsDag("results");
  return results->getArgNameStr(index);
}

auto Operator::getResultDecorators(int index) const -> var_decorator_range {
  const Record *result =
      cast<DefInit>(def.getValueAsDag("results")->getArg(index))->getDef();
  if (!result->isSubClassOf("OpVariable"))
    return var_decorator_range(nullptr, nullptr);
  return *result->getValueAsListInit("decorators");
}

unsigned Operator::getNumVariableLengthResults() const {
  return llvm::count_if(results, [](const NamedTypeConstraint &c) {
    return c.constraint.isVariableLength();
  });
}

unsigned Operator::getNumVariableLengthOperands() const {
  return llvm::count_if(operands, [](const NamedTypeConstraint &c) {
    return c.constraint.isVariableLength();
  });
}

bool Operator::hasSingleVariadicArg() const {
  return getNumArgs() == 1 && isa<NamedTypeConstraint *>(getArg(0)) &&
         getOperand(0).isVariadic();
}

Operator::arg_iterator Operator::arg_begin() const { return arguments.begin(); }

Operator::arg_iterator Operator::arg_end() const { return arguments.end(); }

Operator::arg_range Operator::getArgs() const {
  return {arg_begin(), arg_end()};
}

StringRef Operator::getArgName(int index) const {
  const DagInit *argumentValues = def.getValueAsDag("arguments");
  return argumentValues->getArgNameStr(index);
}

auto Operator::getArgDecorators(int index) const -> var_decorator_range {
  const Record *arg =
      cast<DefInit>(def.getValueAsDag("arguments")->getArg(index))->getDef();
  if (!arg->isSubClassOf("OpVariable"))
    return var_decorator_range(nullptr, nullptr);
  return *arg->getValueAsListInit("decorators");
}

const Trait *Operator::getTrait(StringRef trait) const {
  for (const auto &t : traits) {
    if (const auto *traitDef = dyn_cast<NativeTrait>(&t)) {
      if (traitDef->getFullyQualifiedTraitName() == trait)
        return traitDef;
    } else if (const auto *traitDef = dyn_cast<InternalTrait>(&t)) {
      if (traitDef->getFullyQualifiedTraitName() == trait)
        return traitDef;
    } else if (const auto *traitDef = dyn_cast<InterfaceTrait>(&t)) {
      if (traitDef->getFullyQualifiedTraitName() == trait)
        return traitDef;
    }
  }
  return nullptr;
}

auto Operator::region_begin() const -> const_region_iterator {
  return regions.begin();
}
auto Operator::region_end() const -> const_region_iterator {
  return regions.end();
}
auto Operator::getRegions() const
    -> llvm::iterator_range<const_region_iterator> {
  return {region_begin(), region_end()};
}

unsigned Operator::getNumRegions() const { return regions.size(); }

const NamedRegion &Operator::getRegion(unsigned index) const {
  return regions[index];
}

unsigned Operator::getNumVariadicRegions() const {
  return llvm::count_if(regions,
                        [](const NamedRegion &c) { return c.isVariadic(); });
}

auto Operator::successor_begin() const -> const_successor_iterator {
  return successors.begin();
}
auto Operator::successor_end() const -> const_successor_iterator {
  return successors.end();
}
auto Operator::getSuccessors() const
    -> llvm::iterator_range<const_successor_iterator> {
  return {successor_begin(), successor_end()};
}

unsigned Operator::getNumSuccessors() const { return successors.size(); }

const NamedSuccessor &Operator::getSuccessor(unsigned index) const {
  return successors[index];
}

unsigned Operator::getNumVariadicSuccessors() const {
  return llvm::count_if(successors,
                        [](const NamedSuccessor &c) { return c.isVariadic(); });
}

auto Operator::trait_begin() const -> const_trait_iterator {
  return traits.begin();
}
auto Operator::trait_end() const -> const_trait_iterator {
  return traits.end();
}
auto Operator::getTraits() const -> llvm::iterator_range<const_trait_iterator> {
  return {trait_begin(), trait_end()};
}

auto Operator::attribute_begin() const -> const_attribute_iterator {
  return attributes.begin();
}
auto Operator::attribute_end() const -> const_attribute_iterator {
  return attributes.end();
}
auto Operator::getAttributes() const
    -> llvm::iterator_range<const_attribute_iterator> {
  return {attribute_begin(), attribute_end()};
}
auto Operator::attribute_begin() -> attribute_iterator {
  return attributes.begin();
}
auto Operator::attribute_end() -> attribute_iterator {
  return attributes.end();
}
auto Operator::getAttributes() -> llvm::iterator_range<attribute_iterator> {
  return {attribute_begin(), attribute_end()};
}

auto Operator::operand_begin() const -> const_value_iterator {
  return operands.begin();
}
auto Operator::operand_end() const -> const_value_iterator {
  return operands.end();
}
auto Operator::getOperands() const -> const_value_range {
  return {operand_begin(), operand_end()};
}

auto Operator::getArg(int index) const -> Argument { return arguments[index]; }

bool Operator::isVariadic() const {
  return any_of(llvm::concat<const NamedTypeConstraint>(operands, results),
                [](const NamedTypeConstraint &op) { return op.isVariadic(); });
}

void Operator::populateTypeInferenceInfo(
    const llvm::StringMap<int> &argumentsAndResultsIndex) {
  // If the type inference op interface is not registered, then do not attempt
  // to determine if the result types an be inferred.
  auto &recordKeeper = def.getRecords();
  auto *inferTrait = recordKeeper.getDef(inferTypeOpInterface);
  allResultsHaveKnownTypes = false;
  if (!inferTrait)
    return;

  // If there are no results, the skip this else the build method generated
  // overlaps with another autogenerated builder.
  if (getNumResults() == 0)
    return;

  // Skip ops with variadic or optional results.
  if (getNumVariableLengthResults() > 0)
    return;

  // Skip cases currently being custom generated.
  // TODO: Remove special cases.
  if (getTrait("::mlir::OpTrait::SameOperandsAndResultType")) {
    // Check for a non-variable length operand to use as the type anchor.
    auto *operandI = llvm::find_if(arguments, [](const Argument &arg) {
      NamedTypeConstraint *operand =
          llvm::dyn_cast_if_present<NamedTypeConstraint *>(arg);
      return operand && !operand->isVariableLength();
    });
    if (operandI == arguments.end())
      return;

    // All result types are inferred from the operand type.
    int operandIdx = operandI - arguments.begin();
    for (int i = 0; i < getNumResults(); ++i)
      resultTypeMapping.emplace_back(operandIdx, "$_self");

    allResultsHaveKnownTypes = true;
    traits.push_back(Trait::create(inferTrait->getDefInit()));
    return;
  }

  /// This struct represents a node in this operation's result type inferenece
  /// graph. Each node has a list of incoming type inference edges `sources`.
  /// Each edge represents a "source" from which the result type can be
  /// inferred, either an operand (leaf) or another result (node). When a node
  /// is known to have a fully-inferred type, `inferred` is set to true.
  struct ResultTypeInference {
    /// The list of incoming type inference edges.
    SmallVector<InferredResultType> sources;
    /// This flag is set to true when the result type is known to be inferrable.
    bool inferred = false;
  };

  // This vector represents the type inference graph, with one node for each
  // operation result. The nth element is the node for the nth result.
  SmallVector<ResultTypeInference> inference(getNumResults(), {});

  // For all results whose types are buildable, initialize their type inference
  // nodes with an edge to themselves. Mark those nodes are fully-inferred.
  for (auto [idx, infer] : llvm::enumerate(inference)) {
    if (getResult(idx).constraint.getBuilderCall()) {
      infer.sources.emplace_back(InferredResultType::mapResultIndex(idx),
                                 "$_self");
      infer.inferred = true;
    }
  }

  // Use `AllTypesMatch` and `TypesMatchWith` operation traits to build the
  // result type inference graph.
  for (const Trait &trait : traits) {
    const Record &def = trait.getDef();

    // If the infer type op interface was manually added, then treat it as
    // intention that the op needs special handling.
    // TODO: Reconsider whether to always generate, this is more conservative
    // and keeps existing behavior so starting that way for now.
    if (def.isSubClassOf(
            llvm::formatv("{0}::Trait", inferTypeOpInterface).str()))
      return;
    if (const auto *traitDef = dyn_cast<InterfaceTrait>(&trait))
      if (&traitDef->getDef() == inferTrait)
        return;

    // The `TypesMatchWith` trait represents a 1 -> 1 type inference edge with a
    // type transformer.
    if (def.isSubClassOf("TypesMatchWith")) {
      int target = argumentsAndResultsIndex.lookup(def.getValueAsString("rhs"));
      // Ignore operand type inference.
      if (InferredResultType::isArgIndex(target))
        continue;
      int resultIndex = InferredResultType::unmapResultIndex(target);
      ResultTypeInference &infer = inference[resultIndex];
      // If the type of the result has already been inferred, do nothing.
      if (infer.inferred)
        continue;
      int sourceIndex =
          argumentsAndResultsIndex.lookup(def.getValueAsString("lhs"));
      infer.sources.emplace_back(sourceIndex,
                                 def.getValueAsString("transformer").str());
      // Locally propagate inferredness.
      infer.inferred =
          InferredResultType::isArgIndex(sourceIndex) ||
          inference[InferredResultType::unmapResultIndex(sourceIndex)].inferred;
      continue;
    }

    // The `ShapedTypeMatchesElementCountAndTypes` trait represents a 1 -> 1
    // type inference edge where a shaped type matches element count and types
    // of variadic elements.
    if (def.isSubClassOf("ShapedTypeMatchesElementCountAndTypes")) {
      StringRef shapedArg = def.getValueAsString("shaped");
      StringRef elementsArg = def.getValueAsString("elements");

      int shapedIndex = argumentsAndResultsIndex.lookup(shapedArg);
      int elementsIndex = argumentsAndResultsIndex.lookup(elementsArg);

      // Handle result type inference from shaped type to variadic elements.
      if (InferredResultType::isResultIndex(elementsIndex) &&
          InferredResultType::isArgIndex(shapedIndex)) {
        int resultIndex = InferredResultType::unmapResultIndex(elementsIndex);
        ResultTypeInference &infer = inference[resultIndex];
        if (!infer.inferred) {
          infer.sources.emplace_back(
              shapedIndex,
              "::llvm::SmallVector<::mlir::Type>(::llvm::cast<::mlir::"
              "ShapedType>($_self).getNumElements(), "
              "::llvm::cast<::mlir::ShapedType>($_self).getElementType())");
          infer.inferred = true;
        }
      }

      // Type inference in the opposite direction is not possible as the actual
      // shaped type can't be inferred from the variadic elements.

      continue;
    }

    if (!def.isSubClassOf("AllTypesMatch"))
      continue;

    auto values = def.getValueAsListOfStrings("values");
    // The `AllTypesMatch` trait represents an N <-> N fanin and fanout. That
    // is, every result type has an edge from every other type. However, if any
    // one of the values refers to an operand or a result with a fully-inferred
    // type, we can infer all other types from that value. Try to find a
    // fully-inferred type in the list.
    std::optional<int> fullyInferredIndex;
    SmallVector<int> resultIndices;
    for (StringRef name : values) {
      int index = argumentsAndResultsIndex.lookup(name);
      if (InferredResultType::isResultIndex(index))
        resultIndices.push_back(InferredResultType::unmapResultIndex(index));
      if (InferredResultType::isArgIndex(index) ||
          inference[InferredResultType::unmapResultIndex(index)].inferred)
        fullyInferredIndex = index;
    }
    if (fullyInferredIndex) {
      // Make the fully-inferred type the only source for all results that
      // aren't already inferred -- a 1 -> N fanout.
      for (int resultIndex : resultIndices) {
        ResultTypeInference &infer = inference[resultIndex];
        if (!infer.inferred) {
          infer.sources.assign(1, {*fullyInferredIndex, "$_self"});
          infer.inferred = true;
        }
      }
    } else {
      // Add an edge between every result and every other type; N <-> N.
      for (int resultIndex : resultIndices) {
        for (int otherResultIndex : resultIndices) {
          if (resultIndex == otherResultIndex)
            continue;
          inference[resultIndex].sources.emplace_back(
              InferredResultType::unmapResultIndex(otherResultIndex), "$_self");
        }
      }
    }
  }

  // Propagate inferredness until a fixed point.
  std::vector<ResultTypeInference *> worklist;
  for (ResultTypeInference &infer : inference)
    if (!infer.inferred)
      worklist.push_back(&infer);
  bool changed;
  do {
    changed = false;
    for (auto cur = worklist.begin(); cur != worklist.end();) {
      ResultTypeInference &infer = **cur;

      InferredResultType *iter =
          llvm::find_if(infer.sources, [&](const InferredResultType &source) {
            assert(InferredResultType::isResultIndex(source.getIndex()));
            return inference[InferredResultType::unmapResultIndex(
                                 source.getIndex())]
                .inferred;
          });
      if (iter == infer.sources.end()) {
        ++cur;
        continue;
      }

      changed = true;
      infer.inferred = true;
      // Make this the only source for the result. This breaks any cycles.
      infer.sources.assign(1, *iter);
      cur = worklist.erase(cur);
    }
  } while (changed);

  allResultsHaveKnownTypes = worklist.empty();

  // If the types could be computed, then add type inference trait.
  if (allResultsHaveKnownTypes) {
    traits.push_back(Trait::create(inferTrait->getDefInit()));
    for (const ResultTypeInference &infer : inference)
      resultTypeMapping.push_back(infer.sources.front());
  }
}

void Operator::populateOpStructure() {
  auto &recordKeeper = def.getRecords();
  auto *typeConstraintClass = recordKeeper.getClass("TypeConstraint");
  auto *attrClass = recordKeeper.getClass("Attr");
  auto *propertyClass = recordKeeper.getClass("Property");
  auto *derivedAttrClass = recordKeeper.getClass("DerivedAttr");
  auto *opVarClass = recordKeeper.getClass("OpVariable");
  numNativeAttributes = 0;

  const DagInit *argumentValues = def.getValueAsDag("arguments");
  unsigned numArgs = argumentValues->getNumArgs();

  // Mapping from name of to argument or result index. Arguments are indexed
  // to match getArg index, while the results are negatively indexed.
  llvm::StringMap<int> argumentsAndResultsIndex;

  // Handle operands and native attributes.
  for (unsigned i = 0; i != numArgs; ++i) {
    auto *arg = argumentValues->getArg(i);
    auto givenName = argumentValues->getArgNameStr(i);
    auto *argDefInit = dyn_cast<DefInit>(arg);
    if (!argDefInit)
      PrintFatalError(def.getLoc(),
                      Twine("undefined type for argument #") + Twine(i));
    const Record *argDef = argDefInit->getDef();
    if (argDef->isSubClassOf(opVarClass))
      argDef = argDef->getValueAsDef("constraint");

    if (argDef->isSubClassOf(typeConstraintClass)) {
      operands.push_back(
          NamedTypeConstraint{givenName, TypeConstraint(argDef)});
    } else if (argDef->isSubClassOf(attrClass)) {
      if (givenName.empty())
        PrintFatalError(argDef->getLoc(), "attributes must be named");
      if (argDef->isSubClassOf(derivedAttrClass))
        PrintFatalError(argDef->getLoc(),
                        "derived attributes not allowed in argument list");
      attributes.push_back({givenName, Attribute(argDef)});
      ++numNativeAttributes;
    } else if (argDef->isSubClassOf(propertyClass)) {
      if (givenName.empty())
        PrintFatalError(argDef->getLoc(), "properties must be named");
      properties.push_back({givenName, Property(argDef)});
    } else {
      PrintFatalError(def.getLoc(),
                      "unexpected def type; only defs deriving "
                      "from TypeConstraint or Attr or Property are allowed");
    }
    if (!givenName.empty())
      argumentsAndResultsIndex[givenName] = i;
  }

  // Handle derived attributes.
  for (const auto &val : def.getValues()) {
    if (auto *record = dyn_cast<llvm::RecordRecTy>(val.getType())) {
      if (!record->isSubClassOf(attrClass))
        continue;
      if (!record->isSubClassOf(derivedAttrClass))
        PrintFatalError(def.getLoc(),
                        "unexpected Attr where only DerivedAttr is allowed");

      if (record->getClasses().size() != 1) {
        PrintFatalError(
            def.getLoc(),
            "unsupported attribute modelling, only single class expected");
      }
      attributes.push_back({cast<StringInit>(val.getNameInit())->getValue(),
                            Attribute(cast<DefInit>(val.getValue()))});
    }
  }

  // Populate `arguments`. This must happen after we've finalized `operands` and
  // `attributes` because we will put their elements' pointers in `arguments`.
  // SmallVector may perform re-allocation under the hood when adding new
  // elements.
  int operandIndex = 0, attrIndex = 0, propIndex = 0;
  for (unsigned i = 0; i != numArgs; ++i) {
    const Record *argDef =
        dyn_cast<DefInit>(argumentValues->getArg(i))->getDef();
    if (argDef->isSubClassOf(opVarClass))
      argDef = argDef->getValueAsDef("constraint");

    if (argDef->isSubClassOf(typeConstraintClass)) {
      attrPropOrOperandMapping.push_back(
          {OperandAttrOrProp::Kind::Operand, operandIndex});
      arguments.emplace_back(&operands[operandIndex++]);
    } else if (argDef->isSubClassOf(attrClass)) {
      attrPropOrOperandMapping.push_back(
          {OperandAttrOrProp::Kind::Attribute, attrIndex});
      arguments.emplace_back(&attributes[attrIndex++]);
    } else {
      assert(argDef->isSubClassOf(propertyClass));
      attrPropOrOperandMapping.push_back(
          {OperandAttrOrProp::Kind::Property, propIndex});
      arguments.emplace_back(&properties[propIndex++]);
    }
  }

  auto *resultsDag = def.getValueAsDag("results");
  auto *outsOp = dyn_cast<DefInit>(resultsDag->getOperator());
  if (!outsOp || outsOp->getDef()->getName() != "outs") {
    PrintFatalError(def.getLoc(), "'results' must have 'outs' directive");
  }

  // Handle results.
  for (unsigned i = 0, e = resultsDag->getNumArgs(); i < e; ++i) {
    auto name = resultsDag->getArgNameStr(i);
    auto *resultInit = dyn_cast<DefInit>(resultsDag->getArg(i));
    if (!resultInit) {
      PrintFatalError(def.getLoc(),
                      Twine("undefined type for result #") + Twine(i));
    }
    auto *resultDef = resultInit->getDef();
    if (resultDef->isSubClassOf(opVarClass))
      resultDef = resultDef->getValueAsDef("constraint");
    results.push_back({name, TypeConstraint(resultDef)});
    if (!name.empty())
      argumentsAndResultsIndex[name] = InferredResultType::mapResultIndex(i);

    // We currently only support VariadicOfVariadic operands.
    if (results.back().constraint.isVariadicOfVariadic()) {
      PrintFatalError(
          def.getLoc(),
          "'VariadicOfVariadic' results are currently not supported");
    }
  }

  // Handle successors
  auto *successorsDag = def.getValueAsDag("successors");
  auto *successorsOp = dyn_cast<DefInit>(successorsDag->getOperator());
  if (!successorsOp || successorsOp->getDef()->getName() != "successor") {
    PrintFatalError(def.getLoc(),
                    "'successors' must have 'successor' directive");
  }

  for (unsigned i = 0, e = successorsDag->getNumArgs(); i < e; ++i) {
    auto name = successorsDag->getArgNameStr(i);
    auto *successorInit = dyn_cast<DefInit>(successorsDag->getArg(i));
    if (!successorInit) {
      PrintFatalError(def.getLoc(),
                      Twine("undefined kind for successor #") + Twine(i));
    }
    Successor successor(successorInit->getDef());

    // Only support variadic successors if it is the last one for now.
    if (i != e - 1 && successor.isVariadic())
      PrintFatalError(def.getLoc(), "only the last successor can be variadic");
    successors.push_back({name, successor});
  }

  // Create list of traits, skipping over duplicates: appending to lists in
  // tablegen is easy, making them unique less so, so dedupe here.
  if (auto *traitList = def.getValueAsListInit("traits")) {
    // This is uniquing based on pointers of the trait.
    SmallPtrSet<const Init *, 32> traitSet;
    traits.reserve(traitSet.size());

    // The declaration order of traits imply the verification order of traits.
    // Some traits may require other traits to be verified first then they can
    // do further verification based on those verified facts. If you see this
    // error, fix the traits declaration order by checking the `dependentTraits`
    // field.
    auto verifyTraitValidity = [&](const Record *trait) {
      auto *dependentTraits = trait->getValueAsListInit("dependentTraits");
      for (auto *traitInit : *dependentTraits)
        if (!traitSet.contains(traitInit))
          PrintFatalError(
              def.getLoc(),
              trait->getValueAsString("trait") + " requires " +
                  cast<DefInit>(traitInit)->getDef()->getValueAsString(
                      "trait") +
                  " to precede it in traits list");
    };

    std::function<void(const ListInit *)> insert;
    insert = [&](const ListInit *traitList) {
      for (auto *traitInit : *traitList) {
        auto *def = cast<DefInit>(traitInit)->getDef();
        if (def->isSubClassOf("TraitList")) {
          insert(def->getValueAsListInit("traits"));
          continue;
        }

        // Ignore duplicates.
        if (!traitSet.insert(traitInit).second)
          continue;

        // If this is an interface with base classes, add the bases to the
        // trait list.
        if (def->isSubClassOf("Interface"))
          insert(def->getValueAsListInit("baseInterfaces"));

        // Verify if the trait has all the dependent traits declared before
        // itself.
        verifyTraitValidity(def);
        traits.push_back(Trait::create(traitInit));
      }
    };
    insert(traitList);
  }

  populateTypeInferenceInfo(argumentsAndResultsIndex);

  // Handle regions
  auto *regionsDag = def.getValueAsDag("regions");
  auto *regionsOp = dyn_cast<DefInit>(regionsDag->getOperator());
  if (!regionsOp || regionsOp->getDef()->getName() != "region") {
    PrintFatalError(def.getLoc(), "'regions' must have 'region' directive");
  }

  for (unsigned i = 0, e = regionsDag->getNumArgs(); i < e; ++i) {
    auto name = regionsDag->getArgNameStr(i);
    auto *regionInit = dyn_cast<DefInit>(regionsDag->getArg(i));
    if (!regionInit) {
      PrintFatalError(def.getLoc(),
                      Twine("undefined kind for region #") + Twine(i));
    }
    Region region(regionInit->getDef());
    if (region.isVariadic()) {
      // Only support variadic regions if it is the last one for now.
      if (i != e - 1)
        PrintFatalError(def.getLoc(), "only the last region can be variadic");
      if (name.empty())
        PrintFatalError(def.getLoc(), "variadic regions must be named");
    }

    regions.push_back({name, region});
  }

  // Populate the builders.
  auto *builderList = dyn_cast_or_null<ListInit>(def.getValueInit("builders"));
  if (builderList && !builderList->empty()) {
    for (const Init *init : builderList->getElements())
      builders.emplace_back(cast<DefInit>(init)->getDef(), def.getLoc());
  } else if (skipDefaultBuilders()) {
    PrintFatalError(
        def.getLoc(),
        "default builders are skipped and no custom builders provided");
  }

  LLVM_DEBUG(print(llvm::dbgs()));
}

const InferredResultType &Operator::getInferredResultType(int index) const {
  assert(allResultTypesKnown());
  return resultTypeMapping[index];
}

ArrayRef<SMLoc> Operator::getLoc() const { return def.getLoc(); }

bool Operator::hasDescription() const {
  return !getDescription().trim().empty();
}

StringRef Operator::getDescription() const {
  return def.getValueAsString("description");
}

bool Operator::hasSummary() const { return !getSummary().trim().empty(); }

StringRef Operator::getSummary() const {
  return def.getValueAsString("summary");
}

bool Operator::hasAssemblyFormat() const {
  auto *valueInit = def.getValueInit("assemblyFormat");
  return isa<StringInit>(valueInit);
}

StringRef Operator::getAssemblyFormat() const {
  return TypeSwitch<const Init *, StringRef>(def.getValueInit("assemblyFormat"))
      .Case([&](const StringInit *init) { return init->getValue(); });
}

void Operator::print(llvm::raw_ostream &os) const {
  os << "op '" << getOperationName() << "'\n";
  for (Argument arg : arguments) {
    if (auto *attr = llvm::dyn_cast_if_present<NamedAttribute *>(arg))
      os << "[attribute] " << attr->name << '\n';
    else
      os << "[operand] " << cast<NamedTypeConstraint *>(arg)->name << '\n';
  }
}

auto Operator::VariableDecoratorIterator::unwrap(const Init *init)
    -> VariableDecorator {
  return VariableDecorator(cast<DefInit>(init)->getDef());
}

auto Operator::getArgToOperandAttrOrProp(int index) const -> OperandAttrOrProp {
  return attrPropOrOperandMapping[index];
}

std::string Operator::getGetterName(StringRef name) const {
  return "get" + convertToCamelFromSnakeCase(name, /*capitalizeFirst=*/true);
}

std::string Operator::getSetterName(StringRef name) const {
  return "set" + convertToCamelFromSnakeCase(name, /*capitalizeFirst=*/true);
}

std::string Operator::getRemoverName(StringRef name) const {
  return "remove" + convertToCamelFromSnakeCase(name, /*capitalizeFirst=*/true);
}

bool Operator::hasFolder() const { return def.getValueAsBit("hasFolder"); }

bool Operator::useCustomPropertiesEncoding() const {
  return def.getValueAsBit("useCustomPropertiesEncoding");
}
