forked from OSchip/llvm-project
2634 lines
101 KiB
C++
2634 lines
101 KiB
C++
//===- OpDefinitionsGen.cpp - MLIR op definitions generator ---------------===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// OpDefinitionsGen uses the description of operations to generate C++
|
|
// definitions for ops.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "OpFormatGen.h"
|
|
#include "OpGenHelpers.h"
|
|
#include "mlir/TableGen/CodeGenHelpers.h"
|
|
#include "mlir/TableGen/Format.h"
|
|
#include "mlir/TableGen/GenInfo.h"
|
|
#include "mlir/TableGen/Interfaces.h"
|
|
#include "mlir/TableGen/OpClass.h"
|
|
#include "mlir/TableGen/Operator.h"
|
|
#include "mlir/TableGen/SideEffects.h"
|
|
#include "mlir/TableGen/Trait.h"
|
|
#include "llvm/ADT/MapVector.h"
|
|
#include "llvm/ADT/Sequence.h"
|
|
#include "llvm/ADT/StringExtras.h"
|
|
#include "llvm/Support/Path.h"
|
|
#include "llvm/Support/Signals.h"
|
|
#include "llvm/TableGen/Error.h"
|
|
#include "llvm/TableGen/Record.h"
|
|
#include "llvm/TableGen/TableGenBackend.h"
|
|
|
|
#define DEBUG_TYPE "mlir-tblgen-opdefgen"
|
|
|
|
using namespace llvm;
|
|
using namespace mlir;
|
|
using namespace mlir::tblgen;
|
|
|
|
static const char *const tblgenNamePrefix = "tblgen_";
|
|
static const char *const generatedArgName = "odsArg";
|
|
static const char *const odsBuilder = "odsBuilder";
|
|
static const char *const builderOpState = "odsState";
|
|
|
|
// The logic to calculate the actual value range for a declared operand/result
|
|
// of an op with variadic operands/results. Note that this logic is not for
|
|
// general use; it assumes all variadic operands/results must have the same
|
|
// number of values.
|
|
//
|
|
// {0}: The list of whether each declared operand/result is variadic.
|
|
// {1}: The total number of non-variadic operands/results.
|
|
// {2}: The total number of variadic operands/results.
|
|
// {3}: The total number of actual values.
|
|
// {4}: "operand" or "result".
|
|
const char *sameVariadicSizeValueRangeCalcCode = R"(
|
|
bool isVariadic[] = {{{0}};
|
|
int prevVariadicCount = 0;
|
|
for (unsigned i = 0; i < index; ++i)
|
|
if (isVariadic[i]) ++prevVariadicCount;
|
|
|
|
// Calculate how many dynamic values a static variadic {4} corresponds to.
|
|
// This assumes all static variadic {4}s have the same dynamic value count.
|
|
int variadicSize = ({3} - {1}) / {2};
|
|
// `index` passed in as the parameter is the static index which counts each
|
|
// {4} (variadic or not) as size 1. So here for each previous static variadic
|
|
// {4}, we need to offset by (variadicSize - 1) to get where the dynamic
|
|
// value pack for this static {4} starts.
|
|
int start = index + (variadicSize - 1) * prevVariadicCount;
|
|
int size = isVariadic[index] ? variadicSize : 1;
|
|
return {{start, size};
|
|
)";
|
|
|
|
// The logic to calculate the actual value range for a declared operand/result
|
|
// of an op with variadic operands/results. Note that this logic is assumes
|
|
// the op has an attribute specifying the size of each operand/result segment
|
|
// (variadic or not).
|
|
//
|
|
// {0}: The name of the attribute specifying the segment sizes.
|
|
const char *adapterSegmentSizeAttrInitCode = R"(
|
|
assert(odsAttrs && "missing segment size attribute for op");
|
|
auto sizeAttr = odsAttrs.get("{0}").cast<::mlir::DenseIntElementsAttr>();
|
|
)";
|
|
const char *opSegmentSizeAttrInitCode = R"(
|
|
auto sizeAttr = (*this)->getAttr({0}).cast<::mlir::DenseIntElementsAttr>();
|
|
)";
|
|
const char *attrSizedSegmentValueRangeCalcCode = R"(
|
|
auto sizeAttrValues = sizeAttr.getValues<uint32_t>();
|
|
unsigned start = 0;
|
|
for (unsigned i = 0; i < index; ++i)
|
|
start += *(sizeAttrValues.begin() + i);
|
|
unsigned size = *(sizeAttrValues.begin() + index);
|
|
return {start, size};
|
|
)";
|
|
|
|
// The logic to build a range of either operand or result values.
|
|
//
|
|
// {0}: The begin iterator of the actual values.
|
|
// {1}: The call to generate the start and length of the value range.
|
|
const char *valueRangeReturnCode = R"(
|
|
auto valueRange = {1};
|
|
return {{std::next({0}, valueRange.first),
|
|
std::next({0}, valueRange.first + valueRange.second)};
|
|
)";
|
|
|
|
static const char *const opCommentHeader = R"(
|
|
//===----------------------------------------------------------------------===//
|
|
// {0} {1}
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
)";
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// StaticVerifierFunctionEmitter
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
/// This class deduplicates shared operation verification code by emitting
|
|
/// static functions alongside the op definitions. These methods are local to
|
|
/// the definition file, and are invoked within the operation verify methods.
|
|
/// An example is shown below:
|
|
///
|
|
/// static LogicalResult localVerify(...)
|
|
///
|
|
/// LogicalResult OpA::verify(...) {
|
|
/// if (failed(localVerify(...)))
|
|
/// return failure();
|
|
/// ...
|
|
/// }
|
|
///
|
|
/// LogicalResult OpB::verify(...) {
|
|
/// if (failed(localVerify(...)))
|
|
/// return failure();
|
|
/// ...
|
|
/// }
|
|
///
|
|
class StaticVerifierFunctionEmitter {
|
|
public:
|
|
StaticVerifierFunctionEmitter(const llvm::RecordKeeper &records,
|
|
ArrayRef<llvm::Record *> opDefs,
|
|
raw_ostream &os, bool emitDecl);
|
|
|
|
/// Get the name of the local function used for the given type constraint.
|
|
/// These functions are used for operand and result constraints and have the
|
|
/// form:
|
|
/// LogicalResult(Operation *op, Type type, StringRef valueKind,
|
|
/// unsigned valueGroupStartIndex);
|
|
StringRef getTypeConstraintFn(const Constraint &constraint) const {
|
|
auto it = localTypeConstraints.find(constraint.getAsOpaquePointer());
|
|
assert(it != localTypeConstraints.end() && "expected valid constraint fn");
|
|
return it->second;
|
|
}
|
|
|
|
private:
|
|
/// Returns a unique name to use when generating local methods.
|
|
static std::string getUniqueName(const llvm::RecordKeeper &records);
|
|
|
|
/// Emit local methods for the type constraints used within the provided op
|
|
/// definitions.
|
|
void emitTypeConstraintMethods(ArrayRef<llvm::Record *> opDefs,
|
|
raw_ostream &os, bool emitDecl);
|
|
|
|
/// A unique label for the file currently being generated. This is used to
|
|
/// ensure that the local functions have a unique name.
|
|
std::string uniqueOutputLabel;
|
|
|
|
/// A set of functions implementing type constraints, used for operand and
|
|
/// result verification.
|
|
llvm::DenseMap<const void *, std::string> localTypeConstraints;
|
|
};
|
|
} // namespace
|
|
|
|
StaticVerifierFunctionEmitter::StaticVerifierFunctionEmitter(
|
|
const llvm::RecordKeeper &records, ArrayRef<llvm::Record *> opDefs,
|
|
raw_ostream &os, bool emitDecl)
|
|
: uniqueOutputLabel(getUniqueName(records)) {
|
|
llvm::Optional<NamespaceEmitter> namespaceEmitter;
|
|
if (!emitDecl) {
|
|
os << formatv(opCommentHeader, "Local Utility Method", "Definitions");
|
|
namespaceEmitter.emplace(os, Operator(*opDefs[0]).getCppNamespace());
|
|
}
|
|
|
|
emitTypeConstraintMethods(opDefs, os, emitDecl);
|
|
}
|
|
|
|
std::string StaticVerifierFunctionEmitter::getUniqueName(
|
|
const llvm::RecordKeeper &records) {
|
|
// Use the input file name when generating a unique name.
|
|
std::string inputFilename = records.getInputFilename();
|
|
|
|
// Drop all but the base filename.
|
|
StringRef nameRef = llvm::sys::path::filename(inputFilename);
|
|
nameRef.consume_back(".td");
|
|
|
|
// Sanitize any invalid characters.
|
|
std::string uniqueName;
|
|
for (char c : nameRef) {
|
|
if (llvm::isAlnum(c) || c == '_')
|
|
uniqueName.push_back(c);
|
|
else
|
|
uniqueName.append(llvm::utohexstr((unsigned char)c));
|
|
}
|
|
return uniqueName;
|
|
}
|
|
|
|
void StaticVerifierFunctionEmitter::emitTypeConstraintMethods(
|
|
ArrayRef<llvm::Record *> opDefs, raw_ostream &os, bool emitDecl) {
|
|
// Collect a set of all of the used type constraints within the operation
|
|
// definitions.
|
|
llvm::SetVector<const void *> typeConstraints;
|
|
for (Record *def : opDefs) {
|
|
Operator op(*def);
|
|
for (NamedTypeConstraint &operand : op.getOperands())
|
|
if (operand.hasPredicate())
|
|
typeConstraints.insert(operand.constraint.getAsOpaquePointer());
|
|
for (NamedTypeConstraint &result : op.getResults())
|
|
if (result.hasPredicate())
|
|
typeConstraints.insert(result.constraint.getAsOpaquePointer());
|
|
}
|
|
|
|
// Record the mapping from predicate to constraint. If two constraints has the
|
|
// same predicate and constraint summary, they can share the same verification
|
|
// function.
|
|
llvm::DenseMap<Pred, const void *> predToConstraint;
|
|
FmtContext fctx;
|
|
for (auto it : llvm::enumerate(typeConstraints)) {
|
|
std::string name;
|
|
Constraint constraint = Constraint::getFromOpaquePointer(it.value());
|
|
Pred pred = constraint.getPredicate();
|
|
auto iter = predToConstraint.find(pred);
|
|
if (iter != predToConstraint.end()) {
|
|
do {
|
|
Constraint built = Constraint::getFromOpaquePointer(iter->second);
|
|
// We may have the different constraints but have the same predicate,
|
|
// for example, ConstraintA and Variadic<ConstraintA>, note that
|
|
// Variadic<> doesn't introduce new predicate. In this case, we can
|
|
// share the same predicate function if they also have consistent
|
|
// summary, otherwise we may report the wrong message while verification
|
|
// fails.
|
|
if (constraint.getSummary() == built.getSummary()) {
|
|
name = getTypeConstraintFn(built).str();
|
|
break;
|
|
}
|
|
++iter;
|
|
} while (iter != predToConstraint.end() && iter->first == pred);
|
|
}
|
|
|
|
if (!name.empty()) {
|
|
localTypeConstraints.try_emplace(it.value(), name);
|
|
continue;
|
|
}
|
|
|
|
// Generate an obscure and unique name for this type constraint.
|
|
name = (Twine("__mlir_ods_local_type_constraint_") + uniqueOutputLabel +
|
|
Twine(it.index()))
|
|
.str();
|
|
predToConstraint.insert(
|
|
std::make_pair(constraint.getPredicate(), it.value()));
|
|
localTypeConstraints.try_emplace(it.value(), name);
|
|
|
|
// Only generate the methods if we are generating definitions.
|
|
if (emitDecl)
|
|
continue;
|
|
|
|
os << "static ::mlir::LogicalResult " << name
|
|
<< "(::mlir::Operation *op, ::mlir::Type type, ::llvm::StringRef "
|
|
"valueKind, unsigned valueGroupStartIndex) {\n";
|
|
|
|
os << " if (!("
|
|
<< tgfmt(constraint.getConditionTemplate(), &fctx.withSelf("type"))
|
|
<< ")) {\n"
|
|
<< formatv(
|
|
" return op->emitOpError(valueKind) << \" #\" << "
|
|
"valueGroupStartIndex << \" must be {0}, but got \" << type;\n",
|
|
constraint.getSummary())
|
|
<< " }\n"
|
|
<< " return ::mlir::success();\n"
|
|
<< "}\n\n";
|
|
}
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Utility structs and functions
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// Replaces all occurrences of `match` in `str` with `substitute`.
|
|
static std::string replaceAllSubstrs(std::string str, const std::string &match,
|
|
const std::string &substitute) {
|
|
std::string::size_type scanLoc = 0, matchLoc = std::string::npos;
|
|
while ((matchLoc = str.find(match, scanLoc)) != std::string::npos) {
|
|
str = str.replace(matchLoc, match.size(), substitute);
|
|
scanLoc = matchLoc + substitute.size();
|
|
}
|
|
return str;
|
|
}
|
|
|
|
// Returns whether the record has a value of the given name that can be returned
|
|
// via getValueAsString.
|
|
static inline bool hasStringAttribute(const Record &record,
|
|
StringRef fieldName) {
|
|
auto valueInit = record.getValueInit(fieldName);
|
|
return isa<StringInit>(valueInit);
|
|
}
|
|
|
|
static std::string getArgumentName(const Operator &op, int index) {
|
|
const auto &operand = op.getOperand(index);
|
|
if (!operand.name.empty())
|
|
return std::string(operand.name);
|
|
else
|
|
return std::string(formatv("{0}_{1}", generatedArgName, index));
|
|
}
|
|
|
|
// Returns true if we can use unwrapped value for the given `attr` in builders.
|
|
static bool canUseUnwrappedRawValue(const tblgen::Attribute &attr) {
|
|
return attr.getReturnType() != attr.getStorageType() &&
|
|
// We need to wrap the raw value into an attribute in the builder impl
|
|
// so we need to make sure that the attribute specifies how to do that.
|
|
!attr.getConstBuilderTemplate().empty();
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Op emitter
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
// Helper class to emit a record into the given output stream.
|
|
class OpEmitter {
|
|
public:
|
|
static void
|
|
emitDecl(const Operator &op, raw_ostream &os,
|
|
const StaticVerifierFunctionEmitter &staticVerifierEmitter);
|
|
static void
|
|
emitDef(const Operator &op, raw_ostream &os,
|
|
const StaticVerifierFunctionEmitter &staticVerifierEmitter);
|
|
|
|
private:
|
|
OpEmitter(const Operator &op,
|
|
const StaticVerifierFunctionEmitter &staticVerifierEmitter);
|
|
|
|
void emitDecl(raw_ostream &os);
|
|
void emitDef(raw_ostream &os);
|
|
|
|
// Generate methods for accessing the attribute names of this operation.
|
|
void genAttrNameGetters();
|
|
|
|
// Return the index of the given attribute name. This is a relative ordering
|
|
// for this name, used in attribute getters.
|
|
unsigned getAttrNameIndex(StringRef attrName) const;
|
|
|
|
// Generates the OpAsmOpInterface for this operation if possible.
|
|
void genOpAsmInterface();
|
|
|
|
// Generates the `getOperationName` method for this op.
|
|
void genOpNameGetter();
|
|
|
|
// Generates getters for the attributes.
|
|
void genAttrGetters();
|
|
|
|
// Generates setter for the attributes.
|
|
void genAttrSetters();
|
|
|
|
// Generates removers for optional attributes.
|
|
void genOptionalAttrRemovers();
|
|
|
|
// Generates getters for named operands.
|
|
void genNamedOperandGetters();
|
|
|
|
// Generates setters for named operands.
|
|
void genNamedOperandSetters();
|
|
|
|
// Generates getters for named results.
|
|
void genNamedResultGetters();
|
|
|
|
// Generates getters for named regions.
|
|
void genNamedRegionGetters();
|
|
|
|
// Generates getters for named successors.
|
|
void genNamedSuccessorGetters();
|
|
|
|
// Generates builder methods for the operation.
|
|
void genBuilder();
|
|
|
|
// Generates the build() method that takes each operand/attribute
|
|
// as a stand-alone parameter.
|
|
void genSeparateArgParamBuilder();
|
|
|
|
// Generates the build() method that takes each operand/attribute as a
|
|
// stand-alone parameter. The generated build() method uses first operand's
|
|
// type as all results' types.
|
|
void genUseOperandAsResultTypeSeparateParamBuilder();
|
|
|
|
// Generates the build() method that takes all operands/attributes
|
|
// collectively as one parameter. The generated build() method uses first
|
|
// operand's type as all results' types.
|
|
void genUseOperandAsResultTypeCollectiveParamBuilder();
|
|
|
|
// Generates the build() method that takes aggregate operands/attributes
|
|
// parameters. This build() method uses inferred types as result types.
|
|
// Requires: The type needs to be inferable via InferTypeOpInterface.
|
|
void genInferredTypeCollectiveParamBuilder();
|
|
|
|
// Generates the build() method that takes each operand/attribute as a
|
|
// stand-alone parameter. The generated build() method uses first attribute's
|
|
// type as all result's types.
|
|
void genUseAttrAsResultTypeBuilder();
|
|
|
|
// Generates the build() method that takes all result types collectively as
|
|
// one parameter. Similarly for operands and attributes.
|
|
void genCollectiveParamBuilder();
|
|
|
|
// The kind of parameter to generate for result types in builders.
|
|
enum class TypeParamKind {
|
|
None, // No result type in parameter list.
|
|
Separate, // A separate parameter for each result type.
|
|
Collective, // An ArrayRef<Type> for all result types.
|
|
};
|
|
|
|
// The kind of parameter to generate for attributes in builders.
|
|
enum class AttrParamKind {
|
|
WrappedAttr, // A wrapped MLIR Attribute instance.
|
|
UnwrappedValue, // A raw value without MLIR Attribute wrapper.
|
|
};
|
|
|
|
// Builds the parameter list for build() method of this op. This method writes
|
|
// to `paramList` the comma-separated parameter list and updates
|
|
// `resultTypeNames` with the names for parameters for specifying result
|
|
// types. The given `typeParamKind` and `attrParamKind` controls how result
|
|
// types and attributes are placed in the parameter list.
|
|
void buildParamList(llvm::SmallVectorImpl<OpMethodParameter> ¶mList,
|
|
SmallVectorImpl<std::string> &resultTypeNames,
|
|
TypeParamKind typeParamKind,
|
|
AttrParamKind attrParamKind = AttrParamKind::WrappedAttr);
|
|
|
|
// Adds op arguments and regions into operation state for build() methods.
|
|
void genCodeForAddingArgAndRegionForBuilder(OpMethodBody &body,
|
|
bool isRawValueAttr = false);
|
|
|
|
// Generates canonicalizer declaration for the operation.
|
|
void genCanonicalizerDecls();
|
|
|
|
// Generates the folder declaration for the operation.
|
|
void genFolderDecls();
|
|
|
|
// Generates the parser for the operation.
|
|
void genParser();
|
|
|
|
// Generates the printer for the operation.
|
|
void genPrinter();
|
|
|
|
// Generates verify method for the operation.
|
|
void genVerifier();
|
|
|
|
// Generates verify statements for operands and results in the operation.
|
|
// The generated code will be attached to `body`.
|
|
void genOperandResultVerifier(OpMethodBody &body,
|
|
Operator::value_range values,
|
|
StringRef valueKind);
|
|
|
|
// Generates verify statements for regions in the operation.
|
|
// The generated code will be attached to `body`.
|
|
void genRegionVerifier(OpMethodBody &body);
|
|
|
|
// Generates verify statements for successors in the operation.
|
|
// The generated code will be attached to `body`.
|
|
void genSuccessorVerifier(OpMethodBody &body);
|
|
|
|
// Generates the traits used by the object.
|
|
void genTraits();
|
|
|
|
// Generate the OpInterface methods for all interfaces.
|
|
void genOpInterfaceMethods();
|
|
|
|
// Generate op interface methods for the given interface.
|
|
void genOpInterfaceMethods(const tblgen::InterfaceTrait *trait);
|
|
|
|
// Generate op interface method for the given interface method. If
|
|
// 'declaration' is true, generates a declaration, else a definition.
|
|
OpMethod *genOpInterfaceMethod(const tblgen::InterfaceMethod &method,
|
|
bool declaration = true);
|
|
|
|
// Generate the side effect interface methods.
|
|
void genSideEffectInterfaceMethods();
|
|
|
|
// Generate the type inference interface methods.
|
|
void genTypeInterfaceMethods();
|
|
|
|
private:
|
|
// The TableGen record for this op.
|
|
// TODO: OpEmitter should not have a Record directly,
|
|
// it should rather go through the Operator for better abstraction.
|
|
const Record &def;
|
|
|
|
// The wrapper operator class for querying information from this op.
|
|
Operator op;
|
|
|
|
// The C++ code builder for this op
|
|
OpClass opClass;
|
|
|
|
// The format context for verification code generation.
|
|
FmtContext verifyCtx;
|
|
|
|
// The emitter containing all of the locally emitted verification functions.
|
|
const StaticVerifierFunctionEmitter &staticVerifierEmitter;
|
|
|
|
// A map of attribute names (including implicit attributes) registered to the
|
|
// current operation, to the relative order in which they were registered.
|
|
llvm::MapVector<StringRef, unsigned> attributeNames;
|
|
};
|
|
} // end anonymous namespace
|
|
|
|
// Populate the format context `ctx` with substitutions of attributes, operands
|
|
// and results.
|
|
// - attrGet corresponds to the name of the function to call to get value of
|
|
// attribute (the generated function call returns an Attribute);
|
|
// - operandGet corresponds to the name of the function with which to retrieve
|
|
// an operand (the generated function call returns an OperandRange);
|
|
// - resultGet corresponds to the name of the function to get an result (the
|
|
// generated function call returns a ValueRange);
|
|
static void populateSubstitutions(const Operator &op, const char *attrGet,
|
|
const char *operandGet, const char *resultGet,
|
|
FmtContext &ctx) {
|
|
// Populate substitutions for attributes and named operands.
|
|
for (const auto &namedAttr : op.getAttributes())
|
|
ctx.addSubst(namedAttr.name,
|
|
formatv("{0}(\"{1}\")", attrGet, namedAttr.name));
|
|
for (int i = 0, e = op.getNumOperands(); i < e; ++i) {
|
|
auto &value = op.getOperand(i);
|
|
if (value.name.empty())
|
|
continue;
|
|
|
|
if (value.isVariadic())
|
|
ctx.addSubst(value.name, formatv("{0}({1})", operandGet, i));
|
|
else
|
|
ctx.addSubst(value.name, formatv("(*{0}({1}).begin())", operandGet, i));
|
|
}
|
|
|
|
// Populate substitutions for results.
|
|
for (int i = 0, e = op.getNumResults(); i < e; ++i) {
|
|
auto &value = op.getResult(i);
|
|
if (value.name.empty())
|
|
continue;
|
|
|
|
if (value.isVariadic())
|
|
ctx.addSubst(value.name, formatv("{0}({1})", resultGet, i));
|
|
else
|
|
ctx.addSubst(value.name, formatv("(*{0}({1}).begin())", resultGet, i));
|
|
}
|
|
}
|
|
|
|
// Generate attribute verification. If emitVerificationRequiringOp is set then
|
|
// only verification for attributes whose value depend on op being known are
|
|
// emitted, else only verification that doesn't depend on the op being known are
|
|
// generated.
|
|
// - emitErrorPrefix is the prefix for the error emitting call which consists
|
|
// of the entire function call up to start of error message fragment;
|
|
// - emitVerificationRequiringOp specifies whether verification should be
|
|
// emitted for verification that require the op to exist;
|
|
static void genAttributeVerifier(const Operator &op, const char *attrGet,
|
|
const Twine &emitErrorPrefix,
|
|
bool emitVerificationRequiringOp,
|
|
FmtContext &ctx, OpMethodBody &body) {
|
|
for (const auto &namedAttr : op.getAttributes()) {
|
|
const auto &attr = namedAttr.attr;
|
|
if (attr.isDerivedAttr())
|
|
continue;
|
|
|
|
auto attrName = namedAttr.name;
|
|
bool allowMissingAttr = attr.hasDefaultValue() || attr.isOptional();
|
|
auto attrPred = attr.getPredicate();
|
|
auto condition = attrPred.isNull() ? "" : attrPred.getCondition();
|
|
// There is a condition to emit only if the use of $_op and whether to
|
|
// emit verifications for op matches.
|
|
bool hasConditionToEmit = (!(condition.find("$_op") != StringRef::npos) ^
|
|
emitVerificationRequiringOp);
|
|
|
|
// Prefix with `tblgen_` to avoid hiding the attribute accessor.
|
|
auto varName = tblgenNamePrefix + attrName;
|
|
|
|
// If the attribute is
|
|
// 1. Required (not allowed missing) and not in op verification, or
|
|
// 2. Has a condition that will get verified
|
|
// then the variable will be used.
|
|
//
|
|
// Therefore, for optional attributes whose verification requires that an
|
|
// op already exists for verification/emitVerificationRequiringOp is set
|
|
// has nothing that can be verified here.
|
|
if ((allowMissingAttr || emitVerificationRequiringOp) &&
|
|
!hasConditionToEmit)
|
|
continue;
|
|
|
|
body << formatv(" {\n auto {0} = {1}(\"{2}\");\n", varName, attrGet,
|
|
attrName);
|
|
|
|
if (!emitVerificationRequiringOp && !allowMissingAttr) {
|
|
body << " if (!" << varName << ") return " << emitErrorPrefix
|
|
<< "\"requires attribute '" << attrName << "'\");\n";
|
|
}
|
|
|
|
if (!hasConditionToEmit) {
|
|
body << " }\n";
|
|
continue;
|
|
}
|
|
|
|
if (allowMissingAttr) {
|
|
// If the attribute has a default value, then only verify the predicate if
|
|
// set. This does effectively assume that the default value is valid.
|
|
// TODO: verify the debug value is valid (perhaps in debug mode only).
|
|
body << " if (" << varName << ") {\n";
|
|
}
|
|
|
|
body << tgfmt(" if (!($0)) return $1\"attribute '$2' "
|
|
"failed to satisfy constraint: $3\");\n",
|
|
/*ctx=*/nullptr, tgfmt(condition, &ctx.withSelf(varName)),
|
|
emitErrorPrefix, attrName, attr.getSummary());
|
|
if (allowMissingAttr)
|
|
body << " }\n";
|
|
body << " }\n";
|
|
}
|
|
}
|
|
|
|
OpEmitter::OpEmitter(const Operator &op,
|
|
const StaticVerifierFunctionEmitter &staticVerifierEmitter)
|
|
: def(op.getDef()), op(op),
|
|
opClass(op.getCppClassName(), op.getExtraClassDeclaration()),
|
|
staticVerifierEmitter(staticVerifierEmitter) {
|
|
verifyCtx.withOp("(*this->getOperation())");
|
|
verifyCtx.addSubst("_ctxt", "this->getOperation()->getContext()");
|
|
|
|
genTraits();
|
|
|
|
// Generate C++ code for various op methods. The order here determines the
|
|
// methods in the generated file.
|
|
genAttrNameGetters();
|
|
genOpAsmInterface();
|
|
genOpNameGetter();
|
|
genNamedOperandGetters();
|
|
genNamedOperandSetters();
|
|
genNamedResultGetters();
|
|
genNamedRegionGetters();
|
|
genNamedSuccessorGetters();
|
|
genAttrGetters();
|
|
genAttrSetters();
|
|
genOptionalAttrRemovers();
|
|
genBuilder();
|
|
genParser();
|
|
genPrinter();
|
|
genVerifier();
|
|
genCanonicalizerDecls();
|
|
genFolderDecls();
|
|
genTypeInterfaceMethods();
|
|
genOpInterfaceMethods();
|
|
generateOpFormat(op, opClass);
|
|
genSideEffectInterfaceMethods();
|
|
}
|
|
|
|
void OpEmitter::emitDecl(
|
|
const Operator &op, raw_ostream &os,
|
|
const StaticVerifierFunctionEmitter &staticVerifierEmitter) {
|
|
OpEmitter(op, staticVerifierEmitter).emitDecl(os);
|
|
}
|
|
|
|
void OpEmitter::emitDef(
|
|
const Operator &op, raw_ostream &os,
|
|
const StaticVerifierFunctionEmitter &staticVerifierEmitter) {
|
|
OpEmitter(op, staticVerifierEmitter).emitDef(os);
|
|
}
|
|
|
|
void OpEmitter::emitDecl(raw_ostream &os) { opClass.writeDeclTo(os); }
|
|
|
|
void OpEmitter::emitDef(raw_ostream &os) { opClass.writeDefTo(os); }
|
|
|
|
void OpEmitter::genAttrNameGetters() {
|
|
// Enumerate the attribute names of this op, assigning each a relative
|
|
// ordering.
|
|
auto addAttrName = [&](StringRef name) {
|
|
unsigned index = attributeNames.size();
|
|
attributeNames.insert({name, index});
|
|
};
|
|
for (const NamedAttribute &namedAttr : op.getAttributes())
|
|
addAttrName(namedAttr.name);
|
|
// Include key attributes from several traits as implicitly registered.
|
|
if (op.getTrait("::mlir::OpTrait::AttrSizedOperandSegments"))
|
|
addAttrName("operand_segment_sizes");
|
|
if (op.getTrait("::mlir::OpTrait::AttrSizedResultSegments"))
|
|
addAttrName("result_segment_sizes");
|
|
|
|
// Emit the getAttributeNames method.
|
|
{
|
|
auto *method = opClass.addMethodAndPrune(
|
|
"::llvm::ArrayRef<::llvm::StringRef>", "getAttributeNames",
|
|
OpMethod::Property(OpMethod::MP_Static | OpMethod::MP_Inline));
|
|
auto &body = method->body();
|
|
if (attributeNames.empty()) {
|
|
body << " return {};";
|
|
} else {
|
|
body << " static ::llvm::StringRef attrNames[] = {";
|
|
llvm::interleaveComma(llvm::make_first_range(attributeNames), body,
|
|
[&](StringRef attrName) {
|
|
body << "::llvm::StringRef(\"" << attrName
|
|
<< "\")";
|
|
});
|
|
body << "};\n return ::llvm::makeArrayRef(attrNames);";
|
|
}
|
|
}
|
|
if (attributeNames.empty())
|
|
return;
|
|
|
|
// Emit the getAttributeNameForIndex methods.
|
|
{
|
|
auto *method = opClass.addMethodAndPrune(
|
|
"::mlir::Identifier", "getAttributeNameForIndex",
|
|
OpMethod::Property(OpMethod::MP_Private | OpMethod::MP_Inline),
|
|
"unsigned", "index");
|
|
method->body()
|
|
<< " return getAttributeNameForIndex((*this)->getName(), index);";
|
|
}
|
|
{
|
|
auto *method = opClass.addMethodAndPrune(
|
|
"::mlir::Identifier", "getAttributeNameForIndex",
|
|
OpMethod::Property(OpMethod::MP_Private | OpMethod::MP_Inline |
|
|
OpMethod::MP_Static),
|
|
"::mlir::OperationName name, unsigned index");
|
|
method->body() << "assert(index < " << attributeNames.size()
|
|
<< " && \"invalid attribute index\");\n"
|
|
" return name.getAbstractOperation()"
|
|
"->getAttributeNames()[index];";
|
|
}
|
|
|
|
// Generate the <attr>AttrName methods, that expose the attribute names to
|
|
// users.
|
|
const char *attrNameMethodBody = " return getAttributeNameForIndex({0});";
|
|
for (const std::pair<StringRef, unsigned> &attrIt : attributeNames) {
|
|
std::string methodName = (attrIt.first + "AttrName").str();
|
|
|
|
// Generate the non-static variant.
|
|
{
|
|
auto *method =
|
|
opClass.addMethodAndPrune("::mlir::Identifier", methodName,
|
|
OpMethod::Property(OpMethod::MP_Inline));
|
|
method->body() << llvm::formatv(attrNameMethodBody, attrIt.second).str();
|
|
}
|
|
|
|
// Generate the static variant.
|
|
{
|
|
auto *method = opClass.addMethodAndPrune(
|
|
"::mlir::Identifier", methodName,
|
|
OpMethod::Property(OpMethod::MP_Inline | OpMethod::MP_Static),
|
|
"::mlir::OperationName", "name");
|
|
method->body() << llvm::formatv(attrNameMethodBody,
|
|
"name, " + Twine(attrIt.second))
|
|
.str();
|
|
}
|
|
}
|
|
}
|
|
|
|
unsigned OpEmitter::getAttrNameIndex(StringRef attrName) const {
|
|
auto it = attributeNames.find(attrName);
|
|
assert(it != attributeNames.end() && "expected attribute name to have been "
|
|
"registered in genAttrNameGetters");
|
|
return it->second;
|
|
}
|
|
|
|
void OpEmitter::genAttrGetters() {
|
|
FmtContext fctx;
|
|
fctx.withBuilder("::mlir::Builder((*this)->getContext())");
|
|
|
|
// Emit the derived attribute body.
|
|
auto emitDerivedAttr = [&](StringRef name, Attribute attr) {
|
|
if (auto *method = opClass.addMethodAndPrune(attr.getReturnType(), name))
|
|
method->body() << " " << attr.getDerivedCodeBody() << "\n";
|
|
};
|
|
|
|
// Emit with return type specified.
|
|
auto emitAttrWithReturnType = [&](StringRef name, Attribute attr) {
|
|
auto *method = opClass.addMethodAndPrune(attr.getReturnType(), name);
|
|
auto &body = method->body();
|
|
body << " auto attr = " << name << "Attr();\n";
|
|
if (attr.hasDefaultValue()) {
|
|
// Returns the default value if not set.
|
|
// TODO: this is inefficient, we are recreating the attribute for every
|
|
// call. This should be set instead.
|
|
std::string defaultValue = std::string(
|
|
tgfmt(attr.getConstBuilderTemplate(), &fctx, attr.getDefaultValue()));
|
|
body << " if (!attr)\n return "
|
|
<< tgfmt(attr.getConvertFromStorageCall(),
|
|
&fctx.withSelf(defaultValue))
|
|
<< ";\n";
|
|
}
|
|
body << " return "
|
|
<< tgfmt(attr.getConvertFromStorageCall(), &fctx.withSelf("attr"))
|
|
<< ";\n";
|
|
};
|
|
|
|
// Generate raw named accessor type. This is a wrapper class that allows
|
|
// referring to the attributes via accessors instead of having to use
|
|
// the string interface for better compile time verification.
|
|
auto emitAttrWithStorageType = [&](StringRef name, Attribute attr) {
|
|
auto *method =
|
|
opClass.addMethodAndPrune(attr.getStorageType(), (name + "Attr").str());
|
|
if (!method)
|
|
return;
|
|
auto &body = method->body();
|
|
body << " return (*this)->getAttr(" << name << "AttrName()).template ";
|
|
if (attr.isOptional() || attr.hasDefaultValue())
|
|
body << "dyn_cast_or_null<";
|
|
else
|
|
body << "cast<";
|
|
body << attr.getStorageType() << ">();";
|
|
};
|
|
|
|
for (const NamedAttribute &namedAttr : op.getAttributes()) {
|
|
if (namedAttr.attr.isDerivedAttr()) {
|
|
emitDerivedAttr(namedAttr.name, namedAttr.attr);
|
|
} else {
|
|
emitAttrWithStorageType(namedAttr.name, namedAttr.attr);
|
|
emitAttrWithReturnType(namedAttr.name, namedAttr.attr);
|
|
}
|
|
}
|
|
|
|
auto derivedAttrs = make_filter_range(op.getAttributes(),
|
|
[](const NamedAttribute &namedAttr) {
|
|
return namedAttr.attr.isDerivedAttr();
|
|
});
|
|
if (!derivedAttrs.empty()) {
|
|
opClass.addTrait("::mlir::DerivedAttributeOpInterface::Trait");
|
|
// Generate helper method to query whether a named attribute is a derived
|
|
// attribute. This enables, for example, avoiding adding an attribute that
|
|
// overlaps with a derived attribute.
|
|
{
|
|
auto *method = opClass.addMethodAndPrune("bool", "isDerivedAttribute",
|
|
OpMethod::MP_Static,
|
|
"::llvm::StringRef", "name");
|
|
auto &body = method->body();
|
|
for (auto namedAttr : derivedAttrs)
|
|
body << " if (name == \"" << namedAttr.name << "\") return true;\n";
|
|
body << " return false;";
|
|
}
|
|
// Generate method to materialize derived attributes as a DictionaryAttr.
|
|
{
|
|
auto *method = opClass.addMethodAndPrune("::mlir::DictionaryAttr",
|
|
"materializeDerivedAttributes");
|
|
auto &body = method->body();
|
|
|
|
auto nonMaterializable =
|
|
make_filter_range(derivedAttrs, [](const NamedAttribute &namedAttr) {
|
|
return namedAttr.attr.getConvertFromStorageCall().empty();
|
|
});
|
|
if (!nonMaterializable.empty()) {
|
|
std::string attrs;
|
|
llvm::raw_string_ostream os(attrs);
|
|
interleaveComma(nonMaterializable, os,
|
|
[&](const NamedAttribute &attr) { os << attr.name; });
|
|
PrintWarning(
|
|
op.getLoc(),
|
|
formatv(
|
|
"op has non-materializable derived attributes '{0}', skipping",
|
|
os.str()));
|
|
body << formatv(" emitOpError(\"op has non-materializable derived "
|
|
"attributes '{0}'\");\n",
|
|
attrs);
|
|
body << " return nullptr;";
|
|
return;
|
|
}
|
|
|
|
body << " ::mlir::MLIRContext* ctx = getContext();\n";
|
|
body << " ::mlir::Builder odsBuilder(ctx); (void)odsBuilder;\n";
|
|
body << " return ::mlir::DictionaryAttr::get(";
|
|
body << " ctx, {\n";
|
|
interleave(
|
|
derivedAttrs, body,
|
|
[&](const NamedAttribute &namedAttr) {
|
|
auto tmpl = namedAttr.attr.getConvertFromStorageCall();
|
|
body << " {" << namedAttr.name << "AttrName(),\n"
|
|
<< tgfmt(tmpl, &fctx.withSelf(namedAttr.name + "()")
|
|
.withBuilder("odsBuilder")
|
|
.addSubst("_ctx", "ctx"))
|
|
<< "}";
|
|
},
|
|
",\n");
|
|
body << "});";
|
|
}
|
|
}
|
|
}
|
|
|
|
void OpEmitter::genAttrSetters() {
|
|
// Generate raw named setter type. This is a wrapper class that allows setting
|
|
// to the attributes via setters instead of having to use the string interface
|
|
// for better compile time verification.
|
|
auto emitAttrWithStorageType = [&](StringRef name, Attribute attr) {
|
|
auto *method = opClass.addMethodAndPrune("void", (name + "Attr").str(),
|
|
attr.getStorageType(), "attr");
|
|
if (method)
|
|
method->body() << " (*this)->setAttr(" << name << "AttrName(), attr);";
|
|
};
|
|
|
|
for (const NamedAttribute &namedAttr : op.getAttributes())
|
|
if (!namedAttr.attr.isDerivedAttr())
|
|
emitAttrWithStorageType(namedAttr.name, namedAttr.attr);
|
|
}
|
|
|
|
void OpEmitter::genOptionalAttrRemovers() {
|
|
// Generate methods for removing optional attributes, instead of having to
|
|
// use the string interface. Enables better compile time verification.
|
|
auto emitRemoveAttr = [&](StringRef name) {
|
|
auto upperInitial = name.take_front().upper();
|
|
auto suffix = name.drop_front();
|
|
auto *method = opClass.addMethodAndPrune(
|
|
"::mlir::Attribute", ("remove" + upperInitial + suffix + "Attr").str());
|
|
if (!method)
|
|
return;
|
|
method->body() << " return (*this)->removeAttr(" << name << "AttrName());";
|
|
};
|
|
|
|
for (const NamedAttribute &namedAttr : op.getAttributes())
|
|
if (namedAttr.attr.isOptional())
|
|
emitRemoveAttr(namedAttr.name);
|
|
}
|
|
|
|
// Generates the code to compute the start and end index of an operand or result
|
|
// range.
|
|
template <typename RangeT>
|
|
static void
|
|
generateValueRangeStartAndEnd(Class &opClass, StringRef methodName,
|
|
int numVariadic, int numNonVariadic,
|
|
StringRef rangeSizeCall, bool hasAttrSegmentSize,
|
|
StringRef sizeAttrInit, RangeT &&odsValues) {
|
|
auto *method = opClass.addMethodAndPrune("std::pair<unsigned, unsigned>",
|
|
methodName, "unsigned", "index");
|
|
if (!method)
|
|
return;
|
|
auto &body = method->body();
|
|
if (numVariadic == 0) {
|
|
body << " return {index, 1};\n";
|
|
} else if (hasAttrSegmentSize) {
|
|
body << sizeAttrInit << attrSizedSegmentValueRangeCalcCode;
|
|
} else {
|
|
// Because the op can have arbitrarily interleaved variadic and non-variadic
|
|
// operands, we need to embed a list in the "sink" getter method for
|
|
// calculation at run-time.
|
|
llvm::SmallVector<StringRef, 4> isVariadic;
|
|
isVariadic.reserve(llvm::size(odsValues));
|
|
for (auto &it : odsValues)
|
|
isVariadic.push_back(it.isVariableLength() ? "true" : "false");
|
|
std::string isVariadicList = llvm::join(isVariadic, ", ");
|
|
body << formatv(sameVariadicSizeValueRangeCalcCode, isVariadicList,
|
|
numNonVariadic, numVariadic, rangeSizeCall, "operand");
|
|
}
|
|
}
|
|
|
|
// Generates the named operand getter methods for the given Operator `op` and
|
|
// puts them in `opClass`. Uses `rangeType` as the return type of getters that
|
|
// return a range of operands (individual operands are `Value ` and each
|
|
// element in the range must also be `Value `); use `rangeBeginCall` to get
|
|
// an iterator to the beginning of the operand range; use `rangeSizeCall` to
|
|
// obtain the number of operands. `getOperandCallPattern` contains the code
|
|
// necessary to obtain a single operand whose position will be substituted
|
|
// instead of
|
|
// "{0}" marker in the pattern. Note that the pattern should work for any kind
|
|
// of ops, in particular for one-operand ops that may not have the
|
|
// `getOperand(unsigned)` method.
|
|
static void generateNamedOperandGetters(const Operator &op, Class &opClass,
|
|
StringRef sizeAttrInit,
|
|
StringRef rangeType,
|
|
StringRef rangeBeginCall,
|
|
StringRef rangeSizeCall,
|
|
StringRef getOperandCallPattern) {
|
|
const int numOperands = op.getNumOperands();
|
|
const int numVariadicOperands = op.getNumVariableLengthOperands();
|
|
const int numNormalOperands = numOperands - numVariadicOperands;
|
|
|
|
const auto *sameVariadicSize =
|
|
op.getTrait("::mlir::OpTrait::SameVariadicOperandSize");
|
|
const auto *attrSizedOperands =
|
|
op.getTrait("::mlir::OpTrait::AttrSizedOperandSegments");
|
|
|
|
if (numVariadicOperands > 1 && !sameVariadicSize && !attrSizedOperands) {
|
|
PrintFatalError(op.getLoc(), "op has multiple variadic operands but no "
|
|
"specification over their sizes");
|
|
}
|
|
|
|
if (numVariadicOperands < 2 && attrSizedOperands) {
|
|
PrintFatalError(op.getLoc(), "op must have at least two variadic operands "
|
|
"to use 'AttrSizedOperandSegments' trait");
|
|
}
|
|
|
|
if (attrSizedOperands && sameVariadicSize) {
|
|
PrintFatalError(op.getLoc(),
|
|
"op cannot have both 'AttrSizedOperandSegments' and "
|
|
"'SameVariadicOperandSize' traits");
|
|
}
|
|
|
|
// First emit a few "sink" getter methods upon which we layer all nicer named
|
|
// getter methods.
|
|
generateValueRangeStartAndEnd(opClass, "getODSOperandIndexAndLength",
|
|
numVariadicOperands, numNormalOperands,
|
|
rangeSizeCall, attrSizedOperands, sizeAttrInit,
|
|
const_cast<Operator &>(op).getOperands());
|
|
|
|
auto *m = opClass.addMethodAndPrune(rangeType, "getODSOperands", "unsigned",
|
|
"index");
|
|
auto &body = m->body();
|
|
body << formatv(valueRangeReturnCode, rangeBeginCall,
|
|
"getODSOperandIndexAndLength(index)");
|
|
|
|
// Then we emit nicer named getter methods by redirecting to the "sink" getter
|
|
// method.
|
|
for (int i = 0; i != numOperands; ++i) {
|
|
const auto &operand = op.getOperand(i);
|
|
if (operand.name.empty())
|
|
continue;
|
|
|
|
if (operand.isOptional()) {
|
|
m = opClass.addMethodAndPrune("::mlir::Value", operand.name);
|
|
m->body()
|
|
<< " auto operands = getODSOperands(" << i << ");\n"
|
|
<< " return operands.empty() ? ::mlir::Value() : *operands.begin();";
|
|
} else if (operand.isVariadic()) {
|
|
m = opClass.addMethodAndPrune(rangeType, operand.name);
|
|
m->body() << " return getODSOperands(" << i << ");";
|
|
} else {
|
|
m = opClass.addMethodAndPrune("::mlir::Value", operand.name);
|
|
m->body() << " return *getODSOperands(" << i << ").begin();";
|
|
}
|
|
}
|
|
}
|
|
|
|
void OpEmitter::genNamedOperandGetters() {
|
|
// Build the code snippet used for initializing the operand_segment_sizes
|
|
// array.
|
|
std::string attrSizeInitCode;
|
|
if (op.getTrait("::mlir::OpTrait::AttrSizedOperandSegments")) {
|
|
attrSizeInitCode =
|
|
formatv(opSegmentSizeAttrInitCode, "operand_segment_sizesAttrName()")
|
|
.str();
|
|
}
|
|
|
|
generateNamedOperandGetters(
|
|
op, opClass,
|
|
/*sizeAttrInit=*/attrSizeInitCode,
|
|
/*rangeType=*/"::mlir::Operation::operand_range",
|
|
/*rangeBeginCall=*/"getOperation()->operand_begin()",
|
|
/*rangeSizeCall=*/"getOperation()->getNumOperands()",
|
|
/*getOperandCallPattern=*/"getOperation()->getOperand({0})");
|
|
}
|
|
|
|
void OpEmitter::genNamedOperandSetters() {
|
|
auto *attrSizedOperands =
|
|
op.getTrait("::mlir::OpTrait::AttrSizedOperandSegments");
|
|
for (int i = 0, e = op.getNumOperands(); i != e; ++i) {
|
|
const auto &operand = op.getOperand(i);
|
|
if (operand.name.empty())
|
|
continue;
|
|
auto *m = opClass.addMethodAndPrune("::mlir::MutableOperandRange",
|
|
(operand.name + "Mutable").str());
|
|
auto &body = m->body();
|
|
body << " auto range = getODSOperandIndexAndLength(" << i << ");\n"
|
|
<< " return ::mlir::MutableOperandRange(getOperation(), "
|
|
"range.first, range.second";
|
|
if (attrSizedOperands)
|
|
body << ", ::mlir::MutableOperandRange::OperandSegment(" << i
|
|
<< "u, *getOperation()->getAttrDictionary().getNamed("
|
|
"operand_segment_sizesAttrName()))";
|
|
body << ");\n";
|
|
}
|
|
}
|
|
|
|
void OpEmitter::genNamedResultGetters() {
|
|
const int numResults = op.getNumResults();
|
|
const int numVariadicResults = op.getNumVariableLengthResults();
|
|
const int numNormalResults = numResults - numVariadicResults;
|
|
|
|
// If we have more than one variadic results, we need more complicated logic
|
|
// to calculate the value range for each result.
|
|
|
|
const auto *sameVariadicSize =
|
|
op.getTrait("::mlir::OpTrait::SameVariadicResultSize");
|
|
const auto *attrSizedResults =
|
|
op.getTrait("::mlir::OpTrait::AttrSizedResultSegments");
|
|
|
|
if (numVariadicResults > 1 && !sameVariadicSize && !attrSizedResults) {
|
|
PrintFatalError(op.getLoc(), "op has multiple variadic results but no "
|
|
"specification over their sizes");
|
|
}
|
|
|
|
if (numVariadicResults < 2 && attrSizedResults) {
|
|
PrintFatalError(op.getLoc(), "op must have at least two variadic results "
|
|
"to use 'AttrSizedResultSegments' trait");
|
|
}
|
|
|
|
if (attrSizedResults && sameVariadicSize) {
|
|
PrintFatalError(op.getLoc(),
|
|
"op cannot have both 'AttrSizedResultSegments' and "
|
|
"'SameVariadicResultSize' traits");
|
|
}
|
|
|
|
// Build the initializer string for the result segment size attribute.
|
|
std::string attrSizeInitCode;
|
|
if (attrSizedResults) {
|
|
attrSizeInitCode =
|
|
formatv(opSegmentSizeAttrInitCode, "result_segment_sizesAttrName()")
|
|
.str();
|
|
}
|
|
|
|
generateValueRangeStartAndEnd(
|
|
opClass, "getODSResultIndexAndLength", numVariadicResults,
|
|
numNormalResults, "getOperation()->getNumResults()", attrSizedResults,
|
|
attrSizeInitCode, op.getResults());
|
|
|
|
auto *m = opClass.addMethodAndPrune("::mlir::Operation::result_range",
|
|
"getODSResults", "unsigned", "index");
|
|
m->body() << formatv(valueRangeReturnCode, "getOperation()->result_begin()",
|
|
"getODSResultIndexAndLength(index)");
|
|
|
|
for (int i = 0; i != numResults; ++i) {
|
|
const auto &result = op.getResult(i);
|
|
if (result.name.empty())
|
|
continue;
|
|
|
|
if (result.isOptional()) {
|
|
m = opClass.addMethodAndPrune("::mlir::Value", result.name);
|
|
m->body()
|
|
<< " auto results = getODSResults(" << i << ");\n"
|
|
<< " return results.empty() ? ::mlir::Value() : *results.begin();";
|
|
} else if (result.isVariadic()) {
|
|
m = opClass.addMethodAndPrune("::mlir::Operation::result_range",
|
|
result.name);
|
|
m->body() << " return getODSResults(" << i << ");";
|
|
} else {
|
|
m = opClass.addMethodAndPrune("::mlir::Value", result.name);
|
|
m->body() << " return *getODSResults(" << i << ").begin();";
|
|
}
|
|
}
|
|
}
|
|
|
|
void OpEmitter::genNamedRegionGetters() {
|
|
unsigned numRegions = op.getNumRegions();
|
|
for (unsigned i = 0; i < numRegions; ++i) {
|
|
const auto ®ion = op.getRegion(i);
|
|
if (region.name.empty())
|
|
continue;
|
|
|
|
// Generate the accessors for a variadic region.
|
|
if (region.isVariadic()) {
|
|
auto *m = opClass.addMethodAndPrune(
|
|
"::mlir::MutableArrayRef<::mlir::Region>", region.name);
|
|
m->body() << formatv(" return (*this)->getRegions().drop_front({0});",
|
|
i);
|
|
continue;
|
|
}
|
|
|
|
auto *m = opClass.addMethodAndPrune("::mlir::Region &", region.name);
|
|
m->body() << formatv(" return (*this)->getRegion({0});", i);
|
|
}
|
|
}
|
|
|
|
void OpEmitter::genNamedSuccessorGetters() {
|
|
unsigned numSuccessors = op.getNumSuccessors();
|
|
for (unsigned i = 0; i < numSuccessors; ++i) {
|
|
const NamedSuccessor &successor = op.getSuccessor(i);
|
|
if (successor.name.empty())
|
|
continue;
|
|
|
|
// Generate the accessors for a variadic successor list.
|
|
if (successor.isVariadic()) {
|
|
auto *m =
|
|
opClass.addMethodAndPrune("::mlir::SuccessorRange", successor.name);
|
|
m->body() << formatv(
|
|
" return {std::next((*this)->successor_begin(), {0}), "
|
|
"(*this)->successor_end()};",
|
|
i);
|
|
continue;
|
|
}
|
|
|
|
auto *m = opClass.addMethodAndPrune("::mlir::Block *", successor.name);
|
|
m->body() << formatv(" return (*this)->getSuccessor({0});", i);
|
|
}
|
|
}
|
|
|
|
static bool canGenerateUnwrappedBuilder(Operator &op) {
|
|
// If this op does not have native attributes at all, return directly to avoid
|
|
// redefining builders.
|
|
if (op.getNumNativeAttributes() == 0)
|
|
return false;
|
|
|
|
bool canGenerate = false;
|
|
// We are generating builders that take raw values for attributes. We need to
|
|
// make sure the native attributes have a meaningful "unwrapped" value type
|
|
// different from the wrapped mlir::Attribute type to avoid redefining
|
|
// builders. This checks for the op has at least one such native attribute.
|
|
for (int i = 0, e = op.getNumNativeAttributes(); i < e; ++i) {
|
|
NamedAttribute &namedAttr = op.getAttribute(i);
|
|
if (canUseUnwrappedRawValue(namedAttr.attr)) {
|
|
canGenerate = true;
|
|
break;
|
|
}
|
|
}
|
|
return canGenerate;
|
|
}
|
|
|
|
static bool canInferType(Operator &op) {
|
|
return op.getTrait("::mlir::InferTypeOpInterface::Trait") &&
|
|
op.getNumRegions() == 0;
|
|
}
|
|
|
|
void OpEmitter::genSeparateArgParamBuilder() {
|
|
SmallVector<AttrParamKind, 2> attrBuilderType;
|
|
attrBuilderType.push_back(AttrParamKind::WrappedAttr);
|
|
if (canGenerateUnwrappedBuilder(op))
|
|
attrBuilderType.push_back(AttrParamKind::UnwrappedValue);
|
|
|
|
// Emit with separate builders with or without unwrapped attributes and/or
|
|
// inferring result type.
|
|
auto emit = [&](AttrParamKind attrType, TypeParamKind paramKind,
|
|
bool inferType) {
|
|
llvm::SmallVector<OpMethodParameter, 4> paramList;
|
|
llvm::SmallVector<std::string, 4> resultNames;
|
|
buildParamList(paramList, resultNames, paramKind, attrType);
|
|
|
|
auto *m = opClass.addMethodAndPrune("void", "build", OpMethod::MP_Static,
|
|
std::move(paramList));
|
|
// If the builder is redundant, skip generating the method.
|
|
if (!m)
|
|
return;
|
|
auto &body = m->body();
|
|
genCodeForAddingArgAndRegionForBuilder(
|
|
body, /*isRawValueAttr=*/attrType == AttrParamKind::UnwrappedValue);
|
|
|
|
// Push all result types to the operation state
|
|
|
|
if (inferType) {
|
|
// Generate builder that infers type too.
|
|
// TODO: Subsume this with general checking if type can be
|
|
// inferred automatically.
|
|
// TODO: Expand to handle regions.
|
|
body << formatv(R"(
|
|
::llvm::SmallVector<::mlir::Type, 2> inferredReturnTypes;
|
|
if (succeeded({0}::inferReturnTypes(odsBuilder.getContext(),
|
|
{1}.location, {1}.operands,
|
|
{1}.attributes.getDictionary({1}.getContext()),
|
|
/*regions=*/{{}, inferredReturnTypes)))
|
|
{1}.addTypes(inferredReturnTypes);
|
|
else
|
|
::llvm::report_fatal_error("Failed to infer result type(s).");)",
|
|
opClass.getClassName(), builderOpState);
|
|
return;
|
|
}
|
|
|
|
switch (paramKind) {
|
|
case TypeParamKind::None:
|
|
return;
|
|
case TypeParamKind::Separate:
|
|
for (int i = 0, e = op.getNumResults(); i < e; ++i) {
|
|
if (op.getResult(i).isOptional())
|
|
body << " if (" << resultNames[i] << ")\n ";
|
|
body << " " << builderOpState << ".addTypes(" << resultNames[i]
|
|
<< ");\n";
|
|
}
|
|
return;
|
|
case TypeParamKind::Collective: {
|
|
int numResults = op.getNumResults();
|
|
int numVariadicResults = op.getNumVariableLengthResults();
|
|
int numNonVariadicResults = numResults - numVariadicResults;
|
|
bool hasVariadicResult = numVariadicResults != 0;
|
|
|
|
// Avoid emitting "resultTypes.size() >= 0u" which is always true.
|
|
if (!(hasVariadicResult && numNonVariadicResults == 0))
|
|
body << " "
|
|
<< "assert(resultTypes.size() "
|
|
<< (hasVariadicResult ? ">=" : "==") << " "
|
|
<< numNonVariadicResults
|
|
<< "u && \"mismatched number of results\");\n";
|
|
body << " " << builderOpState << ".addTypes(resultTypes);\n";
|
|
}
|
|
return;
|
|
}
|
|
llvm_unreachable("unhandled TypeParamKind");
|
|
};
|
|
|
|
// Some of the build methods generated here may be ambiguous, but TableGen's
|
|
// ambiguous function detection will elide those ones.
|
|
for (auto attrType : attrBuilderType) {
|
|
emit(attrType, TypeParamKind::Separate, /*inferType=*/false);
|
|
if (canInferType(op))
|
|
emit(attrType, TypeParamKind::None, /*inferType=*/true);
|
|
emit(attrType, TypeParamKind::Collective, /*inferType=*/false);
|
|
}
|
|
}
|
|
|
|
void OpEmitter::genUseOperandAsResultTypeCollectiveParamBuilder() {
|
|
int numResults = op.getNumResults();
|
|
|
|
// Signature
|
|
llvm::SmallVector<OpMethodParameter, 4> paramList;
|
|
paramList.emplace_back("::mlir::OpBuilder &", "odsBuilder");
|
|
paramList.emplace_back("::mlir::OperationState &", builderOpState);
|
|
paramList.emplace_back("::mlir::ValueRange", "operands");
|
|
// Provide default value for `attributes` when its the last parameter
|
|
StringRef attributesDefaultValue = op.getNumVariadicRegions() ? "" : "{}";
|
|
paramList.emplace_back("::llvm::ArrayRef<::mlir::NamedAttribute>",
|
|
"attributes", attributesDefaultValue);
|
|
if (op.getNumVariadicRegions())
|
|
paramList.emplace_back("unsigned", "numRegions");
|
|
|
|
auto *m = opClass.addMethodAndPrune("void", "build", OpMethod::MP_Static,
|
|
std::move(paramList));
|
|
// If the builder is redundant, skip generating the method
|
|
if (!m)
|
|
return;
|
|
auto &body = m->body();
|
|
|
|
// Operands
|
|
body << " " << builderOpState << ".addOperands(operands);\n";
|
|
|
|
// Attributes
|
|
body << " " << builderOpState << ".addAttributes(attributes);\n";
|
|
|
|
// Create the correct number of regions
|
|
if (int numRegions = op.getNumRegions()) {
|
|
body << llvm::formatv(
|
|
" for (unsigned i = 0; i != {0}; ++i)\n",
|
|
(op.getNumVariadicRegions() ? "numRegions" : Twine(numRegions)));
|
|
body << " (void)" << builderOpState << ".addRegion();\n";
|
|
}
|
|
|
|
// Result types
|
|
SmallVector<std::string, 2> resultTypes(numResults, "operands[0].getType()");
|
|
body << " " << builderOpState << ".addTypes({"
|
|
<< llvm::join(resultTypes, ", ") << "});\n\n";
|
|
}
|
|
|
|
void OpEmitter::genInferredTypeCollectiveParamBuilder() {
|
|
// TODO: Expand to support regions.
|
|
SmallVector<OpMethodParameter, 4> paramList;
|
|
paramList.emplace_back("::mlir::OpBuilder &", "odsBuilder");
|
|
paramList.emplace_back("::mlir::OperationState &", builderOpState);
|
|
paramList.emplace_back("::mlir::ValueRange", "operands");
|
|
paramList.emplace_back("::llvm::ArrayRef<::mlir::NamedAttribute>",
|
|
"attributes", "{}");
|
|
auto *m = opClass.addMethodAndPrune("void", "build", OpMethod::MP_Static,
|
|
std::move(paramList));
|
|
// If the builder is redundant, skip generating the method
|
|
if (!m)
|
|
return;
|
|
auto &body = m->body();
|
|
|
|
int numResults = op.getNumResults();
|
|
int numVariadicResults = op.getNumVariableLengthResults();
|
|
int numNonVariadicResults = numResults - numVariadicResults;
|
|
|
|
int numOperands = op.getNumOperands();
|
|
int numVariadicOperands = op.getNumVariableLengthOperands();
|
|
int numNonVariadicOperands = numOperands - numVariadicOperands;
|
|
|
|
// Operands
|
|
if (numVariadicOperands == 0 || numNonVariadicOperands != 0)
|
|
body << " assert(operands.size()"
|
|
<< (numVariadicOperands != 0 ? " >= " : " == ")
|
|
<< numNonVariadicOperands
|
|
<< "u && \"mismatched number of parameters\");\n";
|
|
body << " " << builderOpState << ".addOperands(operands);\n";
|
|
body << " " << builderOpState << ".addAttributes(attributes);\n";
|
|
|
|
// Create the correct number of regions
|
|
if (int numRegions = op.getNumRegions()) {
|
|
body << llvm::formatv(
|
|
" for (unsigned i = 0; i != {0}; ++i)\n",
|
|
(op.getNumVariadicRegions() ? "numRegions" : Twine(numRegions)));
|
|
body << " (void)" << builderOpState << ".addRegion();\n";
|
|
}
|
|
|
|
// Result types
|
|
body << formatv(R"(
|
|
::mlir::SmallVector<::mlir::Type, 2> inferredReturnTypes;
|
|
if (succeeded({0}::inferReturnTypes(odsBuilder.getContext(),
|
|
{1}.location, operands,
|
|
{1}.attributes.getDictionary({1}.getContext()),
|
|
/*regions=*/{{}, inferredReturnTypes))) {{)",
|
|
opClass.getClassName(), builderOpState);
|
|
if (numVariadicResults == 0 || numNonVariadicResults != 0)
|
|
body << " assert(inferredReturnTypes.size()"
|
|
<< (numVariadicResults != 0 ? " >= " : " == ") << numNonVariadicResults
|
|
<< "u && \"mismatched number of return types\");\n";
|
|
body << " " << builderOpState << ".addTypes(inferredReturnTypes);";
|
|
|
|
body << formatv(R"(
|
|
} else
|
|
::llvm::report_fatal_error("Failed to infer result type(s).");)",
|
|
opClass.getClassName(), builderOpState);
|
|
}
|
|
|
|
void OpEmitter::genUseOperandAsResultTypeSeparateParamBuilder() {
|
|
llvm::SmallVector<OpMethodParameter, 4> paramList;
|
|
llvm::SmallVector<std::string, 4> resultNames;
|
|
buildParamList(paramList, resultNames, TypeParamKind::None);
|
|
|
|
auto *m = opClass.addMethodAndPrune("void", "build", OpMethod::MP_Static,
|
|
std::move(paramList));
|
|
// If the builder is redundant, skip generating the method
|
|
if (!m)
|
|
return;
|
|
auto &body = m->body();
|
|
genCodeForAddingArgAndRegionForBuilder(body);
|
|
|
|
auto numResults = op.getNumResults();
|
|
if (numResults == 0)
|
|
return;
|
|
|
|
// Push all result types to the operation state
|
|
const char *index = op.getOperand(0).isVariadic() ? ".front()" : "";
|
|
std::string resultType =
|
|
formatv("{0}{1}.getType()", getArgumentName(op, 0), index).str();
|
|
body << " " << builderOpState << ".addTypes({" << resultType;
|
|
for (int i = 1; i != numResults; ++i)
|
|
body << ", " << resultType;
|
|
body << "});\n\n";
|
|
}
|
|
|
|
void OpEmitter::genUseAttrAsResultTypeBuilder() {
|
|
SmallVector<OpMethodParameter, 4> paramList;
|
|
paramList.emplace_back("::mlir::OpBuilder &", "odsBuilder");
|
|
paramList.emplace_back("::mlir::OperationState &", builderOpState);
|
|
paramList.emplace_back("::mlir::ValueRange", "operands");
|
|
paramList.emplace_back("::llvm::ArrayRef<::mlir::NamedAttribute>",
|
|
"attributes", "{}");
|
|
auto *m = opClass.addMethodAndPrune("void", "build", OpMethod::MP_Static,
|
|
std::move(paramList));
|
|
// If the builder is redundant, skip generating the method
|
|
if (!m)
|
|
return;
|
|
|
|
auto &body = m->body();
|
|
|
|
// Push all result types to the operation state
|
|
std::string resultType;
|
|
const auto &namedAttr = op.getAttribute(0);
|
|
|
|
body << " auto attrName = " << namedAttr.name << "AttrName("
|
|
<< builderOpState
|
|
<< ".name);\n"
|
|
" for (auto attr : attributes) {\n"
|
|
" if (attr.first != attrName) continue;\n";
|
|
if (namedAttr.attr.isTypeAttr()) {
|
|
resultType = "attr.second.cast<::mlir::TypeAttr>().getValue()";
|
|
} else {
|
|
resultType = "attr.second.getType()";
|
|
}
|
|
|
|
// Operands
|
|
body << " " << builderOpState << ".addOperands(operands);\n";
|
|
|
|
// Attributes
|
|
body << " " << builderOpState << ".addAttributes(attributes);\n";
|
|
|
|
// Result types
|
|
SmallVector<std::string, 2> resultTypes(op.getNumResults(), resultType);
|
|
body << " " << builderOpState << ".addTypes({"
|
|
<< llvm::join(resultTypes, ", ") << "});\n";
|
|
body << " }\n";
|
|
}
|
|
|
|
/// Returns a signature of the builder. Updates the context `fctx` to enable
|
|
/// replacement of $_builder and $_state in the body.
|
|
static std::string getBuilderSignature(const Builder &builder) {
|
|
ArrayRef<Builder::Parameter> params(builder.getParameters());
|
|
|
|
// Inject builder and state arguments.
|
|
llvm::SmallVector<std::string, 8> arguments;
|
|
arguments.reserve(params.size() + 2);
|
|
arguments.push_back(
|
|
llvm::formatv("::mlir::OpBuilder &{0}", odsBuilder).str());
|
|
arguments.push_back(
|
|
llvm::formatv("::mlir::OperationState &{0}", builderOpState).str());
|
|
|
|
for (unsigned i = 0, e = params.size(); i < e; ++i) {
|
|
// If no name is provided, generate one.
|
|
Optional<StringRef> paramName = params[i].getName();
|
|
std::string name =
|
|
paramName ? paramName->str() : "odsArg" + std::to_string(i);
|
|
|
|
std::string defaultValue;
|
|
if (Optional<StringRef> defaultParamValue = params[i].getDefaultValue())
|
|
defaultValue = llvm::formatv(" = {0}", *defaultParamValue).str();
|
|
arguments.push_back(
|
|
llvm::formatv("{0} {1}{2}", params[i].getCppType(), name, defaultValue)
|
|
.str());
|
|
}
|
|
|
|
return llvm::join(arguments, ", ");
|
|
}
|
|
|
|
void OpEmitter::genBuilder() {
|
|
// Handle custom builders if provided.
|
|
for (const Builder &builder : op.getBuilders()) {
|
|
std::string paramStr = getBuilderSignature(builder);
|
|
|
|
Optional<StringRef> body = builder.getBody();
|
|
OpMethod::Property properties =
|
|
body ? OpMethod::MP_Static : OpMethod::MP_StaticDeclaration;
|
|
auto *method =
|
|
opClass.addMethodAndPrune("void", "build", properties, paramStr);
|
|
|
|
FmtContext fctx;
|
|
fctx.withBuilder(odsBuilder);
|
|
fctx.addSubst("_state", builderOpState);
|
|
if (body)
|
|
method->body() << tgfmt(*body, &fctx);
|
|
}
|
|
|
|
// Generate default builders that requires all result type, operands, and
|
|
// attributes as parameters.
|
|
if (op.skipDefaultBuilders())
|
|
return;
|
|
|
|
// We generate three classes of builders here:
|
|
// 1. one having a stand-alone parameter for each operand / attribute, and
|
|
genSeparateArgParamBuilder();
|
|
// 2. one having an aggregated parameter for all result types / operands /
|
|
// attributes, and
|
|
genCollectiveParamBuilder();
|
|
// 3. one having a stand-alone parameter for each operand and attribute,
|
|
// use the first operand or attribute's type as all result types
|
|
// to facilitate different call patterns.
|
|
if (op.getNumVariableLengthResults() == 0) {
|
|
if (op.getTrait("::mlir::OpTrait::SameOperandsAndResultType")) {
|
|
genUseOperandAsResultTypeSeparateParamBuilder();
|
|
genUseOperandAsResultTypeCollectiveParamBuilder();
|
|
}
|
|
if (op.getTrait("::mlir::OpTrait::FirstAttrDerivedResultType"))
|
|
genUseAttrAsResultTypeBuilder();
|
|
}
|
|
}
|
|
|
|
void OpEmitter::genCollectiveParamBuilder() {
|
|
int numResults = op.getNumResults();
|
|
int numVariadicResults = op.getNumVariableLengthResults();
|
|
int numNonVariadicResults = numResults - numVariadicResults;
|
|
|
|
int numOperands = op.getNumOperands();
|
|
int numVariadicOperands = op.getNumVariableLengthOperands();
|
|
int numNonVariadicOperands = numOperands - numVariadicOperands;
|
|
|
|
SmallVector<OpMethodParameter, 4> paramList;
|
|
paramList.emplace_back("::mlir::OpBuilder &", "");
|
|
paramList.emplace_back("::mlir::OperationState &", builderOpState);
|
|
paramList.emplace_back("::mlir::TypeRange", "resultTypes");
|
|
paramList.emplace_back("::mlir::ValueRange", "operands");
|
|
// Provide default value for `attributes` when its the last parameter
|
|
StringRef attributesDefaultValue = op.getNumVariadicRegions() ? "" : "{}";
|
|
paramList.emplace_back("::llvm::ArrayRef<::mlir::NamedAttribute>",
|
|
"attributes", attributesDefaultValue);
|
|
if (op.getNumVariadicRegions())
|
|
paramList.emplace_back("unsigned", "numRegions");
|
|
|
|
auto *m = opClass.addMethodAndPrune("void", "build", OpMethod::MP_Static,
|
|
std::move(paramList));
|
|
// If the builder is redundant, skip generating the method
|
|
if (!m)
|
|
return;
|
|
auto &body = m->body();
|
|
|
|
// Operands
|
|
if (numVariadicOperands == 0 || numNonVariadicOperands != 0)
|
|
body << " assert(operands.size()"
|
|
<< (numVariadicOperands != 0 ? " >= " : " == ")
|
|
<< numNonVariadicOperands
|
|
<< "u && \"mismatched number of parameters\");\n";
|
|
body << " " << builderOpState << ".addOperands(operands);\n";
|
|
|
|
// Attributes
|
|
body << " " << builderOpState << ".addAttributes(attributes);\n";
|
|
|
|
// Create the correct number of regions
|
|
if (int numRegions = op.getNumRegions()) {
|
|
body << llvm::formatv(
|
|
" for (unsigned i = 0; i != {0}; ++i)\n",
|
|
(op.getNumVariadicRegions() ? "numRegions" : Twine(numRegions)));
|
|
body << " (void)" << builderOpState << ".addRegion();\n";
|
|
}
|
|
|
|
// Result types
|
|
if (numVariadicResults == 0 || numNonVariadicResults != 0)
|
|
body << " assert(resultTypes.size()"
|
|
<< (numVariadicResults != 0 ? " >= " : " == ") << numNonVariadicResults
|
|
<< "u && \"mismatched number of return types\");\n";
|
|
body << " " << builderOpState << ".addTypes(resultTypes);\n";
|
|
|
|
// Generate builder that infers type too.
|
|
// TODO: Expand to handle regions and successors.
|
|
if (canInferType(op) && op.getNumSuccessors() == 0)
|
|
genInferredTypeCollectiveParamBuilder();
|
|
}
|
|
|
|
void OpEmitter::buildParamList(SmallVectorImpl<OpMethodParameter> ¶mList,
|
|
SmallVectorImpl<std::string> &resultTypeNames,
|
|
TypeParamKind typeParamKind,
|
|
AttrParamKind attrParamKind) {
|
|
resultTypeNames.clear();
|
|
auto numResults = op.getNumResults();
|
|
resultTypeNames.reserve(numResults);
|
|
|
|
paramList.emplace_back("::mlir::OpBuilder &", "odsBuilder");
|
|
paramList.emplace_back("::mlir::OperationState &", builderOpState);
|
|
|
|
switch (typeParamKind) {
|
|
case TypeParamKind::None:
|
|
break;
|
|
case TypeParamKind::Separate: {
|
|
// Add parameters for all return types
|
|
for (int i = 0; i < numResults; ++i) {
|
|
const auto &result = op.getResult(i);
|
|
std::string resultName = std::string(result.name);
|
|
if (resultName.empty())
|
|
resultName = std::string(formatv("resultType{0}", i));
|
|
|
|
StringRef type =
|
|
result.isVariadic() ? "::mlir::TypeRange" : "::mlir::Type";
|
|
OpMethodParameter::Property properties = OpMethodParameter::PP_None;
|
|
if (result.isOptional())
|
|
properties = OpMethodParameter::PP_Optional;
|
|
|
|
paramList.emplace_back(type, resultName, properties);
|
|
resultTypeNames.emplace_back(std::move(resultName));
|
|
}
|
|
} break;
|
|
case TypeParamKind::Collective: {
|
|
paramList.emplace_back("::mlir::TypeRange", "resultTypes");
|
|
resultTypeNames.push_back("resultTypes");
|
|
} break;
|
|
}
|
|
|
|
// Add parameters for all arguments (operands and attributes).
|
|
|
|
int numOperands = 0;
|
|
int numAttrs = 0;
|
|
|
|
int defaultValuedAttrStartIndex = op.getNumArgs();
|
|
if (attrParamKind == AttrParamKind::UnwrappedValue) {
|
|
// Calculate the start index from which we can attach default values in the
|
|
// builder declaration.
|
|
for (int i = op.getNumArgs() - 1; i >= 0; --i) {
|
|
auto *namedAttr = op.getArg(i).dyn_cast<tblgen::NamedAttribute *>();
|
|
if (!namedAttr || !namedAttr->attr.hasDefaultValue())
|
|
break;
|
|
|
|
if (!canUseUnwrappedRawValue(namedAttr->attr))
|
|
break;
|
|
|
|
// Creating an APInt requires us to provide bitwidth, value, and
|
|
// signedness, which is complicated compared to others. Similarly
|
|
// for APFloat.
|
|
// TODO: Adjust the 'returnType' field of such attributes
|
|
// to support them.
|
|
StringRef retType = namedAttr->attr.getReturnType();
|
|
if (retType == "::llvm::APInt" || retType == "::llvm::APFloat")
|
|
break;
|
|
|
|
defaultValuedAttrStartIndex = i;
|
|
}
|
|
}
|
|
|
|
for (int i = 0, e = op.getNumArgs(); i < e; ++i) {
|
|
auto argument = op.getArg(i);
|
|
if (argument.is<tblgen::NamedTypeConstraint *>()) {
|
|
const auto &operand = op.getOperand(numOperands);
|
|
StringRef type =
|
|
operand.isVariadic() ? "::mlir::ValueRange" : "::mlir::Value";
|
|
OpMethodParameter::Property properties = OpMethodParameter::PP_None;
|
|
if (operand.isOptional())
|
|
properties = OpMethodParameter::PP_Optional;
|
|
|
|
paramList.emplace_back(type, getArgumentName(op, numOperands),
|
|
properties);
|
|
++numOperands;
|
|
} else {
|
|
const auto &namedAttr = op.getAttribute(numAttrs);
|
|
const auto &attr = namedAttr.attr;
|
|
|
|
OpMethodParameter::Property properties = OpMethodParameter::PP_None;
|
|
if (attr.isOptional())
|
|
properties = OpMethodParameter::PP_Optional;
|
|
|
|
StringRef type;
|
|
switch (attrParamKind) {
|
|
case AttrParamKind::WrappedAttr:
|
|
type = attr.getStorageType();
|
|
break;
|
|
case AttrParamKind::UnwrappedValue:
|
|
if (canUseUnwrappedRawValue(attr))
|
|
type = attr.getReturnType();
|
|
else
|
|
type = attr.getStorageType();
|
|
break;
|
|
}
|
|
|
|
std::string defaultValue;
|
|
// Attach default value if requested and possible.
|
|
if (attrParamKind == AttrParamKind::UnwrappedValue &&
|
|
i >= defaultValuedAttrStartIndex) {
|
|
bool isString = attr.getReturnType() == "::llvm::StringRef";
|
|
if (isString)
|
|
defaultValue.append("\"");
|
|
defaultValue += attr.getDefaultValue();
|
|
if (isString)
|
|
defaultValue.append("\"");
|
|
}
|
|
paramList.emplace_back(type, namedAttr.name, defaultValue, properties);
|
|
++numAttrs;
|
|
}
|
|
}
|
|
|
|
/// Insert parameters for each successor.
|
|
for (const NamedSuccessor &succ : op.getSuccessors()) {
|
|
StringRef type =
|
|
succ.isVariadic() ? "::mlir::BlockRange" : "::mlir::Block *";
|
|
paramList.emplace_back(type, succ.name);
|
|
}
|
|
|
|
/// Insert parameters for variadic regions.
|
|
for (const NamedRegion ®ion : op.getRegions())
|
|
if (region.isVariadic())
|
|
paramList.emplace_back("unsigned",
|
|
llvm::formatv("{0}Count", region.name).str());
|
|
}
|
|
|
|
void OpEmitter::genCodeForAddingArgAndRegionForBuilder(OpMethodBody &body,
|
|
bool isRawValueAttr) {
|
|
// Push all operands to the result.
|
|
for (int i = 0, e = op.getNumOperands(); i < e; ++i) {
|
|
std::string argName = getArgumentName(op, i);
|
|
if (op.getOperand(i).isOptional())
|
|
body << " if (" << argName << ")\n ";
|
|
body << " " << builderOpState << ".addOperands(" << argName << ");\n";
|
|
}
|
|
|
|
// If the operation has the operand segment size attribute, add it here.
|
|
if (op.getTrait("::mlir::OpTrait::AttrSizedOperandSegments")) {
|
|
body << " " << builderOpState
|
|
<< ".addAttribute(operand_segment_sizesAttrName(" << builderOpState
|
|
<< ".name), "
|
|
<< "odsBuilder.getI32VectorAttr({";
|
|
interleaveComma(llvm::seq<int>(0, op.getNumOperands()), body, [&](int i) {
|
|
if (op.getOperand(i).isOptional())
|
|
body << "(" << getArgumentName(op, i) << " ? 1 : 0)";
|
|
else if (op.getOperand(i).isVariadic())
|
|
body << "static_cast<int32_t>(" << getArgumentName(op, i) << ".size())";
|
|
else
|
|
body << "1";
|
|
});
|
|
body << "}));\n";
|
|
}
|
|
|
|
// Push all attributes to the result.
|
|
for (const auto &namedAttr : op.getAttributes()) {
|
|
auto &attr = namedAttr.attr;
|
|
if (!attr.isDerivedAttr()) {
|
|
bool emitNotNullCheck = attr.isOptional();
|
|
if (emitNotNullCheck)
|
|
body << formatv(" if ({0}) ", namedAttr.name) << "{\n";
|
|
|
|
if (isRawValueAttr && canUseUnwrappedRawValue(attr)) {
|
|
// If this is a raw value, then we need to wrap it in an Attribute
|
|
// instance.
|
|
FmtContext fctx;
|
|
fctx.withBuilder("odsBuilder");
|
|
|
|
std::string builderTemplate =
|
|
std::string(attr.getConstBuilderTemplate());
|
|
|
|
// For StringAttr, its constant builder call will wrap the input in
|
|
// quotes, which is correct for normal string literals, but incorrect
|
|
// here given we use function arguments. So we need to strip the
|
|
// wrapping quotes.
|
|
if (StringRef(builderTemplate).contains("\"$0\""))
|
|
builderTemplate = replaceAllSubstrs(builderTemplate, "\"$0\"", "$0");
|
|
|
|
std::string value =
|
|
std::string(tgfmt(builderTemplate, &fctx, namedAttr.name));
|
|
body << formatv(" {0}.addAttribute({1}AttrName({0}.name), {2});\n",
|
|
builderOpState, namedAttr.name, value);
|
|
} else {
|
|
body << formatv(" {0}.addAttribute({1}AttrName({0}.name), {1});\n",
|
|
builderOpState, namedAttr.name);
|
|
}
|
|
if (emitNotNullCheck)
|
|
body << " }\n";
|
|
}
|
|
}
|
|
|
|
// Create the correct number of regions.
|
|
for (const NamedRegion ®ion : op.getRegions()) {
|
|
if (region.isVariadic())
|
|
body << formatv(" for (unsigned i = 0; i < {0}Count; ++i)\n ",
|
|
region.name);
|
|
|
|
body << " (void)" << builderOpState << ".addRegion();\n";
|
|
}
|
|
|
|
// Push all successors to the result.
|
|
for (const NamedSuccessor &namedSuccessor : op.getSuccessors()) {
|
|
body << formatv(" {0}.addSuccessors({1});\n", builderOpState,
|
|
namedSuccessor.name);
|
|
}
|
|
}
|
|
|
|
void OpEmitter::genCanonicalizerDecls() {
|
|
bool hasCanonicalizeMethod = def.getValueAsBit("hasCanonicalizeMethod");
|
|
if (hasCanonicalizeMethod) {
|
|
// static LogicResult FooOp::
|
|
// canonicalize(FooOp op, PatternRewriter &rewriter);
|
|
SmallVector<OpMethodParameter, 2> paramList;
|
|
paramList.emplace_back(op.getCppClassName(), "op");
|
|
paramList.emplace_back("::mlir::PatternRewriter &", "rewriter");
|
|
opClass.addMethodAndPrune("::mlir::LogicalResult", "canonicalize",
|
|
OpMethod::MP_StaticDeclaration,
|
|
std::move(paramList));
|
|
}
|
|
|
|
// We get a prototype for 'getCanonicalizationPatterns' if requested directly
|
|
// or if using a 'canonicalize' method.
|
|
bool hasCanonicalizer = def.getValueAsBit("hasCanonicalizer");
|
|
if (!hasCanonicalizeMethod && !hasCanonicalizer)
|
|
return;
|
|
|
|
// We get a body for 'getCanonicalizationPatterns' when using a 'canonicalize'
|
|
// method, but not implementing 'getCanonicalizationPatterns' manually.
|
|
bool hasBody = hasCanonicalizeMethod && !hasCanonicalizer;
|
|
|
|
// Add a signature for getCanonicalizationPatterns if implemented by the
|
|
// dialect or if synthesized to call 'canonicalize'.
|
|
SmallVector<OpMethodParameter, 2> paramList;
|
|
paramList.emplace_back("::mlir::RewritePatternSet &", "results");
|
|
paramList.emplace_back("::mlir::MLIRContext *", "context");
|
|
auto kind = hasBody ? OpMethod::MP_Static : OpMethod::MP_StaticDeclaration;
|
|
auto *method = opClass.addMethodAndPrune(
|
|
"void", "getCanonicalizationPatterns", kind, std::move(paramList));
|
|
|
|
// If synthesizing the method, fill it it.
|
|
if (hasBody)
|
|
method->body() << " results.add(canonicalize);\n";
|
|
}
|
|
|
|
void OpEmitter::genFolderDecls() {
|
|
bool hasSingleResult =
|
|
op.getNumResults() == 1 && op.getNumVariableLengthResults() == 0;
|
|
|
|
if (def.getValueAsBit("hasFolder")) {
|
|
if (hasSingleResult) {
|
|
opClass.addMethodAndPrune(
|
|
"::mlir::OpFoldResult", "fold", OpMethod::MP_Declaration,
|
|
"::llvm::ArrayRef<::mlir::Attribute>", "operands");
|
|
} else {
|
|
SmallVector<OpMethodParameter, 2> paramList;
|
|
paramList.emplace_back("::llvm::ArrayRef<::mlir::Attribute>", "operands");
|
|
paramList.emplace_back("::llvm::SmallVectorImpl<::mlir::OpFoldResult> &",
|
|
"results");
|
|
opClass.addMethodAndPrune("::mlir::LogicalResult", "fold",
|
|
OpMethod::MP_Declaration, std::move(paramList));
|
|
}
|
|
}
|
|
}
|
|
|
|
void OpEmitter::genOpInterfaceMethods(const tblgen::InterfaceTrait *opTrait) {
|
|
Interface interface = opTrait->getInterface();
|
|
|
|
// Get the set of methods that should always be declared.
|
|
auto alwaysDeclaredMethodsVec = opTrait->getAlwaysDeclaredMethods();
|
|
llvm::StringSet<> alwaysDeclaredMethods;
|
|
alwaysDeclaredMethods.insert(alwaysDeclaredMethodsVec.begin(),
|
|
alwaysDeclaredMethodsVec.end());
|
|
|
|
for (const InterfaceMethod &method : interface.getMethods()) {
|
|
// Don't declare if the method has a body.
|
|
if (method.getBody())
|
|
continue;
|
|
// Don't declare if the method has a default implementation and the op
|
|
// didn't request that it always be declared.
|
|
if (method.getDefaultImplementation() &&
|
|
!alwaysDeclaredMethods.count(method.getName()))
|
|
continue;
|
|
genOpInterfaceMethod(method);
|
|
}
|
|
}
|
|
|
|
OpMethod *OpEmitter::genOpInterfaceMethod(const InterfaceMethod &method,
|
|
bool declaration) {
|
|
SmallVector<OpMethodParameter, 4> paramList;
|
|
for (const InterfaceMethod::Argument &arg : method.getArguments())
|
|
paramList.emplace_back(arg.type, arg.name);
|
|
|
|
auto properties = method.isStatic() ? OpMethod::MP_Static : OpMethod::MP_None;
|
|
if (declaration)
|
|
properties =
|
|
static_cast<OpMethod::Property>(properties | OpMethod::MP_Declaration);
|
|
return opClass.addMethodAndPrune(method.getReturnType(), method.getName(),
|
|
properties, std::move(paramList));
|
|
}
|
|
|
|
void OpEmitter::genOpInterfaceMethods() {
|
|
for (const auto &trait : op.getTraits()) {
|
|
if (const auto *opTrait = dyn_cast<tblgen::InterfaceTrait>(&trait))
|
|
if (opTrait->shouldDeclareMethods())
|
|
genOpInterfaceMethods(opTrait);
|
|
}
|
|
}
|
|
|
|
void OpEmitter::genSideEffectInterfaceMethods() {
|
|
enum EffectKind { Operand, Result, Symbol, Static };
|
|
struct EffectLocation {
|
|
/// The effect applied.
|
|
SideEffect effect;
|
|
|
|
/// The index if the kind is not static.
|
|
unsigned index : 30;
|
|
|
|
/// The kind of the location.
|
|
unsigned kind : 2;
|
|
};
|
|
|
|
StringMap<SmallVector<EffectLocation, 1>> interfaceEffects;
|
|
auto resolveDecorators = [&](Operator::var_decorator_range decorators,
|
|
unsigned index, unsigned kind) {
|
|
for (auto decorator : decorators)
|
|
if (SideEffect *effect = dyn_cast<SideEffect>(&decorator)) {
|
|
opClass.addTrait(effect->getInterfaceTrait());
|
|
interfaceEffects[effect->getBaseEffectName()].push_back(
|
|
EffectLocation{*effect, index, kind});
|
|
}
|
|
};
|
|
|
|
// Collect effects that were specified via:
|
|
/// Traits.
|
|
for (const auto &trait : op.getTraits()) {
|
|
const auto *opTrait = dyn_cast<tblgen::SideEffectTrait>(&trait);
|
|
if (!opTrait)
|
|
continue;
|
|
auto &effects = interfaceEffects[opTrait->getBaseEffectName()];
|
|
for (auto decorator : opTrait->getEffects())
|
|
effects.push_back(EffectLocation{cast<SideEffect>(decorator),
|
|
/*index=*/0, EffectKind::Static});
|
|
}
|
|
/// Attributes and Operands.
|
|
for (unsigned i = 0, operandIt = 0, e = op.getNumArgs(); i != e; ++i) {
|
|
Argument arg = op.getArg(i);
|
|
if (arg.is<NamedTypeConstraint *>()) {
|
|
resolveDecorators(op.getArgDecorators(i), operandIt, EffectKind::Operand);
|
|
++operandIt;
|
|
continue;
|
|
}
|
|
const NamedAttribute *attr = arg.get<NamedAttribute *>();
|
|
if (attr->attr.getBaseAttr().isSymbolRefAttr())
|
|
resolveDecorators(op.getArgDecorators(i), i, EffectKind::Symbol);
|
|
}
|
|
/// Results.
|
|
for (unsigned i = 0, e = op.getNumResults(); i != e; ++i)
|
|
resolveDecorators(op.getResultDecorators(i), i, EffectKind::Result);
|
|
|
|
// The code used to add an effect instance.
|
|
// {0}: The effect class.
|
|
// {1}: Optional value or symbol reference.
|
|
// {1}: The resource class.
|
|
const char *addEffectCode =
|
|
" effects.emplace_back({0}::get(), {1}{2}::get());\n";
|
|
|
|
for (auto &it : interfaceEffects) {
|
|
// Generate the 'getEffects' method.
|
|
std::string type = llvm::formatv("::mlir::SmallVectorImpl<::mlir::"
|
|
"SideEffects::EffectInstance<{0}>> &",
|
|
it.first())
|
|
.str();
|
|
auto *getEffects =
|
|
opClass.addMethodAndPrune("void", "getEffects", type, "effects");
|
|
auto &body = getEffects->body();
|
|
|
|
// Add effect instances for each of the locations marked on the operation.
|
|
for (auto &location : it.second) {
|
|
StringRef effect = location.effect.getName();
|
|
StringRef resource = location.effect.getResource();
|
|
if (location.kind == EffectKind::Static) {
|
|
// A static instance has no attached value.
|
|
body << llvm::formatv(addEffectCode, effect, "", resource).str();
|
|
} else if (location.kind == EffectKind::Symbol) {
|
|
// A symbol reference requires adding the proper attribute.
|
|
const auto *attr = op.getArg(location.index).get<NamedAttribute *>();
|
|
if (attr->attr.isOptional()) {
|
|
body << " if (auto symbolRef = " << attr->name << "Attr())\n "
|
|
<< llvm::formatv(addEffectCode, effect, "symbolRef, ", resource)
|
|
.str();
|
|
} else {
|
|
body << llvm::formatv(addEffectCode, effect, attr->name + "(), ",
|
|
resource)
|
|
.str();
|
|
}
|
|
} else {
|
|
// Otherwise this is an operand/result, so we need to attach the Value.
|
|
body << " for (::mlir::Value value : getODS"
|
|
<< (location.kind == EffectKind::Operand ? "Operands" : "Results")
|
|
<< "(" << location.index << "))\n "
|
|
<< llvm::formatv(addEffectCode, effect, "value, ", resource).str();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void OpEmitter::genTypeInterfaceMethods() {
|
|
if (!op.allResultTypesKnown())
|
|
return;
|
|
// Generate 'inferReturnTypes' method declaration using the interface method
|
|
// declared in 'InferTypeOpInterface' op interface.
|
|
const auto *trait = dyn_cast<InterfaceTrait>(
|
|
op.getTrait("::mlir::InferTypeOpInterface::Trait"));
|
|
Interface interface = trait->getInterface();
|
|
OpMethod *method = [&]() -> OpMethod * {
|
|
for (const InterfaceMethod &interfaceMethod : interface.getMethods()) {
|
|
if (interfaceMethod.getName() == "inferReturnTypes") {
|
|
return genOpInterfaceMethod(interfaceMethod, /*declaration=*/false);
|
|
}
|
|
}
|
|
assert(0 && "unable to find inferReturnTypes interface method");
|
|
return nullptr;
|
|
}();
|
|
auto &body = method->body();
|
|
body << " inferredReturnTypes.resize(" << op.getNumResults() << ");\n";
|
|
|
|
FmtContext fctx;
|
|
fctx.withBuilder("odsBuilder");
|
|
body << " ::mlir::Builder odsBuilder(context);\n";
|
|
|
|
auto emitType =
|
|
[&](const tblgen::Operator::ArgOrType &type) -> OpMethodBody & {
|
|
if (type.isArg()) {
|
|
auto argIndex = type.getArg();
|
|
assert(!op.getArg(argIndex).is<NamedAttribute *>());
|
|
auto arg = op.getArgToOperandOrAttribute(argIndex);
|
|
if (arg.kind() == Operator::OperandOrAttribute::Kind::Operand)
|
|
return body << "operands[" << arg.operandOrAttributeIndex()
|
|
<< "].getType()";
|
|
return body << "attributes[" << arg.operandOrAttributeIndex()
|
|
<< "].getType()";
|
|
} else {
|
|
return body << tgfmt(*type.getType().getBuilderCall(), &fctx);
|
|
}
|
|
};
|
|
|
|
for (int i = 0, e = op.getNumResults(); i != e; ++i) {
|
|
body << " inferredReturnTypes[" << i << "] = ";
|
|
auto types = op.getSameTypeAsResult(i);
|
|
emitType(types[0]) << ";\n";
|
|
if (types.size() == 1)
|
|
continue;
|
|
// TODO: We could verify equality here, but skipping that for verification.
|
|
}
|
|
body << " return ::mlir::success();";
|
|
}
|
|
|
|
void OpEmitter::genParser() {
|
|
if (!hasStringAttribute(def, "parser") ||
|
|
hasStringAttribute(def, "assemblyFormat"))
|
|
return;
|
|
|
|
SmallVector<OpMethodParameter, 2> paramList;
|
|
paramList.emplace_back("::mlir::OpAsmParser &", "parser");
|
|
paramList.emplace_back("::mlir::OperationState &", "result");
|
|
auto *method =
|
|
opClass.addMethodAndPrune("::mlir::ParseResult", "parse",
|
|
OpMethod::MP_Static, std::move(paramList));
|
|
|
|
FmtContext fctx;
|
|
fctx.addSubst("cppClass", opClass.getClassName());
|
|
auto parser = def.getValueAsString("parser").ltrim().rtrim(" \t\v\f\r");
|
|
method->body() << " " << tgfmt(parser, &fctx);
|
|
}
|
|
|
|
void OpEmitter::genPrinter() {
|
|
if (hasStringAttribute(def, "assemblyFormat"))
|
|
return;
|
|
|
|
auto valueInit = def.getValueInit("printer");
|
|
StringInit *stringInit = dyn_cast<StringInit>(valueInit);
|
|
if (!stringInit)
|
|
return;
|
|
|
|
auto *method =
|
|
opClass.addMethodAndPrune("void", "print", "::mlir::OpAsmPrinter &", "p");
|
|
FmtContext fctx;
|
|
fctx.addSubst("cppClass", opClass.getClassName());
|
|
auto printer = stringInit->getValue().ltrim().rtrim(" \t\v\f\r");
|
|
method->body() << " " << tgfmt(printer, &fctx);
|
|
}
|
|
|
|
void OpEmitter::genVerifier() {
|
|
auto *method = opClass.addMethodAndPrune("::mlir::LogicalResult", "verify");
|
|
auto &body = method->body();
|
|
body << " if (failed(" << op.getAdaptorName()
|
|
<< "(*this).verify((*this)->getLoc()))) "
|
|
<< "return ::mlir::failure();\n";
|
|
|
|
auto *valueInit = def.getValueInit("verifier");
|
|
StringInit *stringInit = dyn_cast<StringInit>(valueInit);
|
|
bool hasCustomVerify = stringInit && !stringInit->getValue().empty();
|
|
populateSubstitutions(op, "(*this)->getAttr", "this->getODSOperands",
|
|
"this->getODSResults", verifyCtx);
|
|
|
|
genAttributeVerifier(op, "(*this)->getAttr", "emitOpError(",
|
|
/*emitVerificationRequiringOp=*/true, verifyCtx, body);
|
|
genOperandResultVerifier(body, op.getOperands(), "operand");
|
|
genOperandResultVerifier(body, op.getResults(), "result");
|
|
|
|
for (auto &trait : op.getTraits()) {
|
|
if (auto *t = dyn_cast<tblgen::PredTrait>(&trait)) {
|
|
body << tgfmt(" if (!($0))\n "
|
|
"return emitOpError(\"failed to verify that $1\");\n",
|
|
&verifyCtx, tgfmt(t->getPredTemplate(), &verifyCtx),
|
|
t->getSummary());
|
|
}
|
|
}
|
|
|
|
genRegionVerifier(body);
|
|
genSuccessorVerifier(body);
|
|
|
|
if (hasCustomVerify) {
|
|
FmtContext fctx;
|
|
fctx.addSubst("cppClass", opClass.getClassName());
|
|
auto printer = stringInit->getValue().ltrim().rtrim(" \t\v\f\r");
|
|
body << " " << tgfmt(printer, &fctx);
|
|
} else {
|
|
body << " return ::mlir::success();\n";
|
|
}
|
|
}
|
|
|
|
void OpEmitter::genOperandResultVerifier(OpMethodBody &body,
|
|
Operator::value_range values,
|
|
StringRef valueKind) {
|
|
FmtContext fctx;
|
|
|
|
body << " {\n";
|
|
body << " unsigned index = 0; (void)index;\n";
|
|
|
|
for (auto staticValue : llvm::enumerate(values)) {
|
|
bool hasPredicate = staticValue.value().hasPredicate();
|
|
bool isOptional = staticValue.value().isOptional();
|
|
if (!hasPredicate && !isOptional)
|
|
continue;
|
|
body << formatv(" auto valueGroup{2} = getODS{0}{1}s({2});\n",
|
|
// Capitalize the first letter to match the function name
|
|
valueKind.substr(0, 1).upper(), valueKind.substr(1),
|
|
staticValue.index());
|
|
|
|
// If the constraint is optional check that the value group has at most 1
|
|
// value.
|
|
if (isOptional) {
|
|
body << formatv(" if (valueGroup{0}.size() > 1)\n"
|
|
" return emitOpError(\"{1} group starting at #\") "
|
|
"<< index << \" requires 0 or 1 element, but found \" << "
|
|
"valueGroup{0}.size();\n",
|
|
staticValue.index(), valueKind);
|
|
}
|
|
|
|
// Otherwise, if there is no predicate there is nothing left to do.
|
|
if (!hasPredicate)
|
|
continue;
|
|
// Emit a loop to check all the dynamic values in the pack.
|
|
StringRef constraintFn = staticVerifierEmitter.getTypeConstraintFn(
|
|
staticValue.value().constraint);
|
|
body << " for (::mlir::Value v : valueGroup" << staticValue.index()
|
|
<< ") {\n"
|
|
<< " if (::mlir::failed(" << constraintFn
|
|
<< "(getOperation(), v.getType(), \"" << valueKind << "\", index)))\n"
|
|
<< " return ::mlir::failure();\n"
|
|
<< " ++index;\n"
|
|
<< " }\n";
|
|
}
|
|
|
|
body << " }\n";
|
|
}
|
|
|
|
void OpEmitter::genRegionVerifier(OpMethodBody &body) {
|
|
// If we have no regions, there is nothing more to do.
|
|
unsigned numRegions = op.getNumRegions();
|
|
if (numRegions == 0)
|
|
return;
|
|
|
|
body << "{\n";
|
|
body << " unsigned index = 0; (void)index;\n";
|
|
|
|
for (unsigned i = 0; i < numRegions; ++i) {
|
|
const auto ®ion = op.getRegion(i);
|
|
if (region.constraint.getPredicate().isNull())
|
|
continue;
|
|
|
|
body << " for (::mlir::Region ®ion : ";
|
|
body << formatv(region.isVariadic()
|
|
? "{0}()"
|
|
: "::mlir::MutableArrayRef<::mlir::Region>((*this)"
|
|
"->getRegion({1}))",
|
|
region.name, i);
|
|
body << ") {\n";
|
|
auto constraint = tgfmt(region.constraint.getConditionTemplate(),
|
|
&verifyCtx.withSelf("region"))
|
|
.str();
|
|
|
|
body << formatv(" (void)region;\n"
|
|
" if (!({0})) {\n "
|
|
"return emitOpError(\"region #\") << index << \" {1}"
|
|
"failed to "
|
|
"verify constraint: {2}\";\n }\n",
|
|
constraint,
|
|
region.name.empty() ? "" : "('" + region.name + "') ",
|
|
region.constraint.getSummary())
|
|
<< " ++index;\n"
|
|
<< " }\n";
|
|
}
|
|
body << " }\n";
|
|
}
|
|
|
|
void OpEmitter::genSuccessorVerifier(OpMethodBody &body) {
|
|
// If we have no successors, there is nothing more to do.
|
|
unsigned numSuccessors = op.getNumSuccessors();
|
|
if (numSuccessors == 0)
|
|
return;
|
|
|
|
body << "{\n";
|
|
body << " unsigned index = 0; (void)index;\n";
|
|
|
|
for (unsigned i = 0; i < numSuccessors; ++i) {
|
|
const auto &successor = op.getSuccessor(i);
|
|
if (successor.constraint.getPredicate().isNull())
|
|
continue;
|
|
|
|
if (successor.isVariadic()) {
|
|
body << formatv(" for (::mlir::Block *successor : {0}()) {\n",
|
|
successor.name);
|
|
} else {
|
|
body << " {\n";
|
|
body << formatv(" ::mlir::Block *successor = {0}();\n",
|
|
successor.name);
|
|
}
|
|
auto constraint = tgfmt(successor.constraint.getConditionTemplate(),
|
|
&verifyCtx.withSelf("successor"))
|
|
.str();
|
|
|
|
body << formatv(" (void)successor;\n"
|
|
" if (!({0})) {\n "
|
|
"return emitOpError(\"successor #\") << index << \"('{1}') "
|
|
"failed to "
|
|
"verify constraint: {2}\";\n }\n",
|
|
constraint, successor.name,
|
|
successor.constraint.getSummary())
|
|
<< " ++index;\n"
|
|
<< " }\n";
|
|
}
|
|
body << " }\n";
|
|
}
|
|
|
|
/// Add a size count trait to the given operation class.
|
|
static void addSizeCountTrait(OpClass &opClass, StringRef traitKind,
|
|
int numTotal, int numVariadic) {
|
|
if (numVariadic != 0) {
|
|
if (numTotal == numVariadic)
|
|
opClass.addTrait("::mlir::OpTrait::Variadic" + traitKind + "s");
|
|
else
|
|
opClass.addTrait("::mlir::OpTrait::AtLeastN" + traitKind + "s<" +
|
|
Twine(numTotal - numVariadic) + ">::Impl");
|
|
return;
|
|
}
|
|
switch (numTotal) {
|
|
case 0:
|
|
opClass.addTrait("::mlir::OpTrait::Zero" + traitKind);
|
|
break;
|
|
case 1:
|
|
opClass.addTrait("::mlir::OpTrait::One" + traitKind);
|
|
break;
|
|
default:
|
|
opClass.addTrait("::mlir::OpTrait::N" + traitKind + "s<" + Twine(numTotal) +
|
|
">::Impl");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void OpEmitter::genTraits() {
|
|
// Add region size trait.
|
|
unsigned numRegions = op.getNumRegions();
|
|
unsigned numVariadicRegions = op.getNumVariadicRegions();
|
|
addSizeCountTrait(opClass, "Region", numRegions, numVariadicRegions);
|
|
|
|
// Add result size traits.
|
|
int numResults = op.getNumResults();
|
|
int numVariadicResults = op.getNumVariableLengthResults();
|
|
addSizeCountTrait(opClass, "Result", numResults, numVariadicResults);
|
|
|
|
// For single result ops with a known specific type, generate a OneTypedResult
|
|
// trait.
|
|
if (numResults == 1 && numVariadicResults == 0) {
|
|
auto cppName = op.getResults().begin()->constraint.getCPPClassName();
|
|
opClass.addTrait("::mlir::OpTrait::OneTypedResult<" + cppName + ">::Impl");
|
|
}
|
|
|
|
// Add successor size trait.
|
|
unsigned numSuccessors = op.getNumSuccessors();
|
|
unsigned numVariadicSuccessors = op.getNumVariadicSuccessors();
|
|
addSizeCountTrait(opClass, "Successor", numSuccessors, numVariadicSuccessors);
|
|
|
|
// Add variadic size trait and normal op traits.
|
|
int numOperands = op.getNumOperands();
|
|
int numVariadicOperands = op.getNumVariableLengthOperands();
|
|
|
|
// Add operand size trait.
|
|
if (numVariadicOperands != 0) {
|
|
if (numOperands == numVariadicOperands)
|
|
opClass.addTrait("::mlir::OpTrait::VariadicOperands");
|
|
else
|
|
opClass.addTrait("::mlir::OpTrait::AtLeastNOperands<" +
|
|
Twine(numOperands - numVariadicOperands) + ">::Impl");
|
|
} else {
|
|
switch (numOperands) {
|
|
case 0:
|
|
opClass.addTrait("::mlir::OpTrait::ZeroOperands");
|
|
break;
|
|
case 1:
|
|
opClass.addTrait("::mlir::OpTrait::OneOperand");
|
|
break;
|
|
default:
|
|
opClass.addTrait("::mlir::OpTrait::NOperands<" + Twine(numOperands) +
|
|
">::Impl");
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Add the native and interface traits.
|
|
for (const auto &trait : op.getTraits()) {
|
|
if (auto opTrait = dyn_cast<tblgen::NativeTrait>(&trait))
|
|
opClass.addTrait(opTrait->getFullyQualifiedTraitName());
|
|
else if (auto opTrait = dyn_cast<tblgen::InterfaceTrait>(&trait))
|
|
opClass.addTrait(opTrait->getFullyQualifiedTraitName());
|
|
}
|
|
}
|
|
|
|
void OpEmitter::genOpNameGetter() {
|
|
auto *method = opClass.addMethodAndPrune(
|
|
"::llvm::StringLiteral", "getOperationName",
|
|
OpMethod::Property(OpMethod::MP_Static | OpMethod::MP_Constexpr));
|
|
method->body() << " return ::llvm::StringLiteral(\"" << op.getOperationName()
|
|
<< "\");";
|
|
}
|
|
|
|
void OpEmitter::genOpAsmInterface() {
|
|
// If the user only has one results or specifically added the Asm trait,
|
|
// then don't generate it for them. We specifically only handle multi result
|
|
// operations, because the name of a single result in the common case is not
|
|
// interesting(generally 'result'/'output'/etc.).
|
|
// TODO: We could also add a flag to allow operations to opt in to this
|
|
// generation, even if they only have a single operation.
|
|
int numResults = op.getNumResults();
|
|
if (numResults <= 1 || op.getTrait("::mlir::OpAsmOpInterface::Trait"))
|
|
return;
|
|
|
|
SmallVector<StringRef, 4> resultNames(numResults);
|
|
for (int i = 0; i != numResults; ++i)
|
|
resultNames[i] = op.getResultName(i);
|
|
|
|
// Don't add the trait if none of the results have a valid name.
|
|
if (llvm::all_of(resultNames, [](StringRef name) { return name.empty(); }))
|
|
return;
|
|
opClass.addTrait("::mlir::OpAsmOpInterface::Trait");
|
|
|
|
// Generate the right accessor for the number of results.
|
|
auto *method = opClass.addMethodAndPrune(
|
|
"void", "getAsmResultNames", "::mlir::OpAsmSetValueNameFn", "setNameFn");
|
|
auto &body = method->body();
|
|
for (int i = 0; i != numResults; ++i) {
|
|
body << " auto resultGroup" << i << " = getODSResults(" << i << ");\n"
|
|
<< " if (!llvm::empty(resultGroup" << i << "))\n"
|
|
<< " setNameFn(*resultGroup" << i << ".begin(), \""
|
|
<< resultNames[i] << "\");\n";
|
|
}
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// OpOperandAdaptor emitter
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
// Helper class to emit Op operand adaptors to an output stream. Operand
|
|
// adaptors are wrappers around ArrayRef<Value> that provide named operand
|
|
// getters identical to those defined in the Op.
|
|
class OpOperandAdaptorEmitter {
|
|
public:
|
|
static void emitDecl(const Operator &op, raw_ostream &os);
|
|
static void emitDef(const Operator &op, raw_ostream &os);
|
|
|
|
private:
|
|
explicit OpOperandAdaptorEmitter(const Operator &op);
|
|
|
|
// Add verification function. This generates a verify method for the adaptor
|
|
// which verifies all the op-independent attribute constraints.
|
|
void addVerification();
|
|
|
|
const Operator &op;
|
|
Class adaptor;
|
|
};
|
|
} // end namespace
|
|
|
|
OpOperandAdaptorEmitter::OpOperandAdaptorEmitter(const Operator &op)
|
|
: op(op), adaptor(op.getAdaptorName()) {
|
|
adaptor.newField("::mlir::ValueRange", "odsOperands");
|
|
adaptor.newField("::mlir::DictionaryAttr", "odsAttrs");
|
|
adaptor.newField("::mlir::RegionRange", "odsRegions");
|
|
const auto *attrSizedOperands =
|
|
op.getTrait("::mlir::OpTrait::AttrSizedOperandSegments");
|
|
{
|
|
SmallVector<OpMethodParameter, 2> paramList;
|
|
paramList.emplace_back("::mlir::ValueRange", "values");
|
|
paramList.emplace_back("::mlir::DictionaryAttr", "attrs",
|
|
attrSizedOperands ? "" : "nullptr");
|
|
paramList.emplace_back("::mlir::RegionRange", "regions", "{}");
|
|
auto *constructor = adaptor.addConstructorAndPrune(std::move(paramList));
|
|
|
|
constructor->addMemberInitializer("odsOperands", "values");
|
|
constructor->addMemberInitializer("odsAttrs", "attrs");
|
|
constructor->addMemberInitializer("odsRegions", "regions");
|
|
}
|
|
|
|
{
|
|
auto *constructor = adaptor.addConstructorAndPrune(
|
|
llvm::formatv("{0}&", op.getCppClassName()).str(), "op");
|
|
constructor->addMemberInitializer("odsOperands", "op->getOperands()");
|
|
constructor->addMemberInitializer("odsAttrs", "op->getAttrDictionary()");
|
|
constructor->addMemberInitializer("odsRegions", "op->getRegions()");
|
|
}
|
|
|
|
{
|
|
auto *m = adaptor.addMethodAndPrune("::mlir::ValueRange", "getOperands");
|
|
m->body() << " return odsOperands;";
|
|
}
|
|
std::string sizeAttrInit =
|
|
formatv(adapterSegmentSizeAttrInitCode, "operand_segment_sizes");
|
|
generateNamedOperandGetters(op, adaptor, sizeAttrInit,
|
|
/*rangeType=*/"::mlir::ValueRange",
|
|
/*rangeBeginCall=*/"odsOperands.begin()",
|
|
/*rangeSizeCall=*/"odsOperands.size()",
|
|
/*getOperandCallPattern=*/"odsOperands[{0}]");
|
|
|
|
FmtContext fctx;
|
|
fctx.withBuilder("::mlir::Builder(odsAttrs.getContext())");
|
|
|
|
auto emitAttr = [&](StringRef name, Attribute attr) {
|
|
auto &body = adaptor.addMethodAndPrune(attr.getStorageType(), name)->body();
|
|
body << " assert(odsAttrs && \"no attributes when constructing adapter\");"
|
|
<< "\n " << attr.getStorageType() << " attr = "
|
|
<< "odsAttrs.get(\"" << name << "\").";
|
|
if (attr.hasDefaultValue() || attr.isOptional())
|
|
body << "dyn_cast_or_null<";
|
|
else
|
|
body << "cast<";
|
|
body << attr.getStorageType() << ">();\n";
|
|
|
|
if (attr.hasDefaultValue()) {
|
|
// Use the default value if attribute is not set.
|
|
// TODO: this is inefficient, we are recreating the attribute for every
|
|
// call. This should be set instead.
|
|
std::string defaultValue = std::string(
|
|
tgfmt(attr.getConstBuilderTemplate(), &fctx, attr.getDefaultValue()));
|
|
body << " if (!attr)\n attr = " << defaultValue << ";\n";
|
|
}
|
|
body << " return attr;\n";
|
|
};
|
|
|
|
{
|
|
auto *m =
|
|
adaptor.addMethodAndPrune("::mlir::DictionaryAttr", "getAttributes");
|
|
m->body() << " return odsAttrs;";
|
|
}
|
|
for (auto &namedAttr : op.getAttributes()) {
|
|
const auto &name = namedAttr.name;
|
|
const auto &attr = namedAttr.attr;
|
|
if (!attr.isDerivedAttr())
|
|
emitAttr(name, attr);
|
|
}
|
|
|
|
unsigned numRegions = op.getNumRegions();
|
|
if (numRegions > 0) {
|
|
auto *m = adaptor.addMethodAndPrune("::mlir::RegionRange", "getRegions");
|
|
m->body() << " return odsRegions;";
|
|
}
|
|
for (unsigned i = 0; i < numRegions; ++i) {
|
|
const auto ®ion = op.getRegion(i);
|
|
if (region.name.empty())
|
|
continue;
|
|
|
|
// Generate the accessors for a variadic region.
|
|
if (region.isVariadic()) {
|
|
auto *m = adaptor.addMethodAndPrune("::mlir::RegionRange", region.name);
|
|
m->body() << formatv(" return odsRegions.drop_front({0});", i);
|
|
continue;
|
|
}
|
|
|
|
auto *m = adaptor.addMethodAndPrune("::mlir::Region &", region.name);
|
|
m->body() << formatv(" return *odsRegions[{0}];", i);
|
|
}
|
|
|
|
// Add verification function.
|
|
addVerification();
|
|
}
|
|
|
|
void OpOperandAdaptorEmitter::addVerification() {
|
|
auto *method = adaptor.addMethodAndPrune("::mlir::LogicalResult", "verify",
|
|
"::mlir::Location", "loc");
|
|
auto &body = method->body();
|
|
|
|
const char *checkAttrSizedValueSegmentsCode = R"(
|
|
{
|
|
auto sizeAttr = odsAttrs.get("{0}").cast<::mlir::DenseIntElementsAttr>();
|
|
auto numElements = sizeAttr.getType().cast<::mlir::ShapedType>().getNumElements();
|
|
if (numElements != {1})
|
|
return emitError(loc, "'{0}' attribute for specifying {2} segments "
|
|
"must have {1} elements, but got ") << numElements;
|
|
}
|
|
)";
|
|
|
|
// Verify a few traits first so that we can use
|
|
// getODSOperands()/getODSResults() in the rest of the verifier.
|
|
for (auto &trait : op.getTraits()) {
|
|
if (auto *t = dyn_cast<tblgen::NativeTrait>(&trait)) {
|
|
if (t->getFullyQualifiedTraitName() ==
|
|
"::mlir::OpTrait::AttrSizedOperandSegments") {
|
|
body << formatv(checkAttrSizedValueSegmentsCode,
|
|
"operand_segment_sizes", op.getNumOperands(),
|
|
"operand");
|
|
} else if (t->getFullyQualifiedTraitName() ==
|
|
"::mlir::OpTrait::AttrSizedResultSegments") {
|
|
body << formatv(checkAttrSizedValueSegmentsCode, "result_segment_sizes",
|
|
op.getNumResults(), "result");
|
|
}
|
|
}
|
|
}
|
|
|
|
FmtContext verifyCtx;
|
|
populateSubstitutions(op, "odsAttrs.get", "getODSOperands",
|
|
"<no results should be generated>", verifyCtx);
|
|
genAttributeVerifier(op, "odsAttrs.get",
|
|
Twine("emitError(loc, \"'") + op.getOperationName() +
|
|
"' op \"",
|
|
/*emitVerificationRequiringOp*/ false, verifyCtx, body);
|
|
|
|
body << " return ::mlir::success();";
|
|
}
|
|
|
|
void OpOperandAdaptorEmitter::emitDecl(const Operator &op, raw_ostream &os) {
|
|
OpOperandAdaptorEmitter(op).adaptor.writeDeclTo(os);
|
|
}
|
|
|
|
void OpOperandAdaptorEmitter::emitDef(const Operator &op, raw_ostream &os) {
|
|
OpOperandAdaptorEmitter(op).adaptor.writeDefTo(os);
|
|
}
|
|
|
|
// Emits the opcode enum and op classes.
|
|
static void emitOpClasses(const RecordKeeper &recordKeeper,
|
|
const std::vector<Record *> &defs, raw_ostream &os,
|
|
bool emitDecl) {
|
|
// First emit forward declaration for each class, this allows them to refer
|
|
// to each others in traits for example.
|
|
if (emitDecl) {
|
|
os << "#if defined(GET_OP_CLASSES) || defined(GET_OP_FWD_DEFINES)\n";
|
|
os << "#undef GET_OP_FWD_DEFINES\n";
|
|
for (auto *def : defs) {
|
|
Operator op(*def);
|
|
NamespaceEmitter emitter(os, op.getCppNamespace());
|
|
os << "class " << op.getCppClassName() << ";\n";
|
|
}
|
|
os << "#endif\n\n";
|
|
}
|
|
|
|
IfDefScope scope("GET_OP_CLASSES", os);
|
|
if (defs.empty())
|
|
return;
|
|
|
|
// Generate all of the locally instantiated methods first.
|
|
StaticVerifierFunctionEmitter staticVerifierEmitter(recordKeeper, defs, os,
|
|
emitDecl);
|
|
for (auto *def : defs) {
|
|
Operator op(*def);
|
|
NamespaceEmitter emitter(os, op.getCppNamespace());
|
|
if (emitDecl) {
|
|
os << formatv(opCommentHeader, op.getQualCppClassName(), "declarations");
|
|
OpOperandAdaptorEmitter::emitDecl(op, os);
|
|
OpEmitter::emitDecl(op, os, staticVerifierEmitter);
|
|
} else {
|
|
os << formatv(opCommentHeader, op.getQualCppClassName(), "definitions");
|
|
OpOperandAdaptorEmitter::emitDef(op, os);
|
|
OpEmitter::emitDef(op, os, staticVerifierEmitter);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Emits a comma-separated list of the ops.
|
|
static void emitOpList(const std::vector<Record *> &defs, raw_ostream &os) {
|
|
IfDefScope scope("GET_OP_LIST", os);
|
|
|
|
interleave(
|
|
// TODO: We are constructing the Operator wrapper instance just for
|
|
// getting it's qualified class name here. Reduce the overhead by having a
|
|
// lightweight version of Operator class just for that purpose.
|
|
defs, [&os](Record *def) { os << Operator(def).getQualCppClassName(); },
|
|
[&os]() { os << ",\n"; });
|
|
}
|
|
|
|
static bool emitOpDecls(const RecordKeeper &recordKeeper, raw_ostream &os) {
|
|
emitSourceFileHeader("Op Declarations", os);
|
|
|
|
std::vector<Record *> defs = getRequestedOpDefinitions(recordKeeper);
|
|
emitOpClasses(recordKeeper, defs, os, /*emitDecl=*/true);
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool emitOpDefs(const RecordKeeper &recordKeeper, raw_ostream &os) {
|
|
emitSourceFileHeader("Op Definitions", os);
|
|
|
|
std::vector<Record *> defs = getRequestedOpDefinitions(recordKeeper);
|
|
emitOpList(defs, os);
|
|
emitOpClasses(recordKeeper, defs, os, /*emitDecl=*/false);
|
|
|
|
return false;
|
|
}
|
|
|
|
static mlir::GenRegistration
|
|
genOpDecls("gen-op-decls", "Generate op declarations",
|
|
[](const RecordKeeper &records, raw_ostream &os) {
|
|
return emitOpDecls(records, os);
|
|
});
|
|
|
|
static mlir::GenRegistration genOpDefs("gen-op-defs", "Generate op definitions",
|
|
[](const RecordKeeper &records,
|
|
raw_ostream &os) {
|
|
return emitOpDefs(records, os);
|
|
});
|