//===- 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/EquivalenceClasses.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" #include #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 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(0, getNumOperands())) checkName(getOperand(i).name, "operands"); // Check results amongst themselves and against operands. for (int i : llvm::seq(0, getNumResults())) checkName(getResult(i).name, "results"); // Check regions amongst themselves and against operands and results. for (int i : llvm::seq(0, getNumRegions())) checkName(getRegion(i).name, "regions"); // Check successors amongst themselves and against operands, results, and // regions. for (int i : llvm::seq(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(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(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(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(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(&t)) { if (traitDef->getFullyQualifiedTraitName() == trait) return traitDef; } else if (const auto *traitDef = dyn_cast(&t)) { if (traitDef->getFullyQualifiedTraitName() == trait) return traitDef; } else if (const auto *traitDef = dyn_cast(&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 { 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 { 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 { 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 { 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 { 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(operands, results), [](const NamedTypeConstraint &op) { return op.isVariadic(); }); } void Operator::populateTypeInferenceInfo( const llvm::StringMap &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(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 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 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(&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; } 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 fullyInferredIndex; SmallVector 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 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 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(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(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(val.getNameInit())->getValue(), Attribute(cast(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(argumentValues->getArg(i))->getDef(); if (argDef->isSubClassOf(opVarClass)) argDef = argDef->getValueAsDef("constraint"); if (argDef->isSubClassOf(typeConstraintClass)) { attrOrOperandMapping.push_back( {OperandOrAttribute::Kind::Operand, operandIndex}); arguments.emplace_back(&operands[operandIndex++]); } else if (argDef->isSubClassOf(attrClass)) { attrOrOperandMapping.push_back( {OperandOrAttribute::Kind::Attribute, attrIndex}); arguments.emplace_back(&attributes[attrIndex++]); } else { assert(argDef->isSubClassOf(propertyClass)); arguments.emplace_back(&properties[propIndex++]); } } auto *resultsDag = def.getValueAsDag("results"); auto *outsOp = dyn_cast(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(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(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(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 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(traitInit)->getDef()->getValueAsString( "trait") + " to precede it in traits list"); }; std::function insert; insert = [&](const ListInit *traitList) { for (auto *traitInit : *traitList) { auto *def = cast(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(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(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(def.getValueInit("builders")); if (builderList && !builderList->empty()) { for (const Init *init : builderList->getValues()) builders.emplace_back(cast(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 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(valueInit); } StringRef Operator::getAssemblyFormat() const { return TypeSwitch(def.getValueInit("assemblyFormat")) .Case([&](auto *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(arg)) os << "[attribute] " << attr->name << '\n'; else os << "[operand] " << cast(arg)->name << '\n'; } } auto Operator::VariableDecoratorIterator::unwrap(const Init *init) -> VariableDecorator { return VariableDecorator(cast(init)->getDef()); } auto Operator::getArgToOperandOrAttribute(int index) const -> OperandOrAttribute { return attrOrOperandMapping[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"); }