#include "mlir/Dialect/PDL/IR/PDL.h"
#include "mlir/Dialect/PDL/IR/PDLOps.h"
#include "mlir/Dialect/PDL/IR/PDLTypes.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/Interfaces/InferTypeOpInterface.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/TypeSwitch.h"
using namespace mlir;
using namespace mlir::pdl;
#include "mlir/Dialect/PDL/IR/PDLOpsDialect.cpp.inc"
void PDLDialect::initialize() {
addOperations<
#define GET_OP_LIST
#include "mlir/Dialect/PDL/IR/PDLOps.cpp.inc"
>();
registerTypes();
}
static bool hasBindingUse(Operation *op) {
for (Operation *user : op->getUsers())
if (!isa<ResultOp, ResultsOp>(user) || hasBindingUse(user))
return true;
return false;
}
static LogicalResult verifyHasBindingUse(Operation *op) {
if (!isa<PatternOp>(op->getParentOp()))
return success();
if (hasBindingUse(op))
return success();
return op->emitOpError(
"expected a bindable user when defined in the matcher body of a "
"`pdl.pattern`");
}
static void visit(Operation *op, DenseSet<Operation *> &visited) {
if (!isa<PatternOp>(op->getParentOp()) || isa<RewriteOp>(op))
return;
if (visited.contains(op))
return;
visited.insert(op);
TypeSwitch<Operation *>(op)
.Case<OperationOp>([&visited](auto operation) {
for (Value operand : operation.operands())
visit(operand.getDefiningOp(), visited);
})
.Case<ResultOp, ResultsOp>([&visited](auto result) {
visit(result.parent().getDefiningOp(), visited);
});
for (Operation *user : op->getUsers())
visit(user, visited);
}
LogicalResult ApplyNativeConstraintOp::verify() {
if (getNumOperands() == 0)
return emitOpError("expected at least one argument");
return success();
}
LogicalResult ApplyNativeRewriteOp::verify() {
if (getNumOperands() == 0 && getNumResults() == 0)
return emitOpError("expected at least one argument or result");
return success();
}
LogicalResult AttributeOp::verify() {
Value attrType = type();
Optional<Attribute> attrValue = value();
if (!attrValue) {
if (isa<RewriteOp>((*this)->getParentOp()))
return emitOpError(
"expected constant value when specified within a `pdl.rewrite`");
return verifyHasBindingUse(*this);
}
if (attrType)
return emitOpError("expected only one of [`type`, `value`] to be set");
return success();
}
LogicalResult OperandOp::verify() { return verifyHasBindingUse(*this); }
LogicalResult OperandsOp::verify() { return verifyHasBindingUse(*this); }
static ParseResult parseOperationOpAttributes(
OpAsmParser &p,
SmallVectorImpl<OpAsmParser::UnresolvedOperand> &attrOperands,
ArrayAttr &attrNamesAttr) {
Builder &builder = p.getBuilder();
SmallVector<Attribute, 4> attrNames;
if (succeeded(p.parseOptionalLBrace())) {
auto parseOperands = [&]() {
StringAttr nameAttr;
OpAsmParser::UnresolvedOperand operand;
if (p.parseAttribute(nameAttr) || p.parseEqual() ||
p.parseOperand(operand))
return failure();
attrNames.push_back(nameAttr);
attrOperands.push_back(operand);
return success();
};
if (p.parseCommaSeparatedList(parseOperands) || p.parseRBrace())
return failure();
}
attrNamesAttr = builder.getArrayAttr(attrNames);
return success();
}
static void printOperationOpAttributes(OpAsmPrinter &p, OperationOp op,
OperandRange attrArgs,
ArrayAttr attrNames) {
if (attrNames.empty())
return;
p << " {";
interleaveComma(llvm::seq<int>(0, attrNames.size()), p,
[&](int i) { p << attrNames[i] << " = " << attrArgs[i]; });
p << '}';
}
static LogicalResult verifyResultTypesAreInferrable(OperationOp op,
OperandRange resultTypes) {
Block *rewriterBlock = op->getBlock();
auto canInferTypeFromUse = [&](OpOperand &use) {
ReplaceOp replOpUser = dyn_cast<ReplaceOp>(use.getOwner());
if (!replOpUser || use.getOperandNumber() == 0)
return false;
Operation *replacedOp = replOpUser.operation().getDefiningOp();
return replacedOp->getBlock() != rewriterBlock ||
replacedOp->isBeforeInBlock(op);
};
if (llvm::any_of(op.op().getUses(), canInferTypeFromUse))
return success();
if (resultTypes.empty()) {
Optional<StringRef> rawOpName = op.name();
if (!rawOpName)
return success();
Optional<RegisteredOperationName> opName =
RegisteredOperationName::lookup(*rawOpName, op.getContext());
if (!opName)
return success();
bool expectedAtLeastOneResult =
!opName->hasTrait<OpTrait::ZeroResults>() &&
!opName->hasTrait<OpTrait::VariadicResults>();
if (expectedAtLeastOneResult) {
return op
.emitOpError("must have inferable or constrained result types when "
"nested within `pdl.rewrite`")
.attachNote()
.append("operation is created in a non-inferrable context, but '",
*opName, "' does not implement InferTypeOpInterface");
}
return success();
}
for (const auto &it : llvm::enumerate(resultTypes)) {
Operation *resultTypeOp = it.value().getDefiningOp();
assert(resultTypeOp && "expected valid result type operation");
if (isa<ApplyNativeRewriteOp>(resultTypeOp))
continue;
auto constrainsInput = [rewriterBlock](Operation *user) {
return user->getBlock() != rewriterBlock &&
isa<OperandOp, OperandsOp, OperationOp>(user);
};
if (TypeOp typeOp = dyn_cast<TypeOp>(resultTypeOp)) {
if (typeOp.type() || llvm::any_of(typeOp->getUsers(), constrainsInput))
continue;
} else if (TypesOp typeOp = dyn_cast<TypesOp>(resultTypeOp)) {
if (typeOp.types() || llvm::any_of(typeOp->getUsers(), constrainsInput))
continue;
}
return op
.emitOpError("must have inferable or constrained result types when "
"nested within `pdl.rewrite`")
.attachNote()
.append("result type #", it.index(), " was not constrained");
}
return success();
}
LogicalResult OperationOp::verify() {
bool isWithinRewrite = isa<RewriteOp>((*this)->getParentOp());
if (isWithinRewrite && !name())
return emitOpError("must have an operation name when nested within "
"a `pdl.rewrite`");
ArrayAttr attributeNames = attributeNamesAttr();
auto attributeValues = attributes();
if (attributeNames.size() != attributeValues.size()) {
return emitOpError()
<< "expected the same number of attribute values and attribute "
"names, got "
<< attributeNames.size() << " names and " << attributeValues.size()
<< " values";
}
if (isWithinRewrite && !mightHaveTypeInference()) {
if (failed(verifyResultTypesAreInferrable(*this, types())))
return failure();
}
return verifyHasBindingUse(*this);
}
bool OperationOp::hasTypeInference() {
if (Optional<StringRef> rawOpName = name()) {
OperationName opName(*rawOpName, getContext());
return opName.hasInterface<InferTypeOpInterface>();
}
return false;
}
bool OperationOp::mightHaveTypeInference() {
if (Optional<StringRef> rawOpName = name()) {
OperationName opName(*rawOpName, getContext());
return opName.mightHaveInterface<InferTypeOpInterface>();
}
return false;
}
LogicalResult PatternOp::verifyRegions() {
Region &body = getBodyRegion();
Operation *term = body.front().getTerminator();
auto rewriteOp = dyn_cast<RewriteOp>(term);
if (!rewriteOp) {
return emitOpError("expected body to terminate with `pdl.rewrite`")
.attachNote(term->getLoc())
.append("see terminator defined here");
}
WalkResult result = body.walk([&](Operation *op) -> WalkResult {
if (!isa_and_nonnull<PDLDialect>(op->getDialect())) {
emitOpError("expected only `pdl` operations within the pattern body")
.attachNote(op->getLoc())
.append("see non-`pdl` operation defined here");
return WalkResult::interrupt();
}
return WalkResult::advance();
});
if (result.wasInterrupted())
return failure();
if (body.front().getOps<OperationOp>().empty())
return emitOpError("the pattern must contain at least one `pdl.operation`");
bool first = true;
DenseSet<Operation *> visited;
for (Operation &op : body.front()) {
if (!isa<OperandOp, OperandsOp, ResultOp, ResultsOp, OperationOp>(op))
continue;
bool hasUserInRewrite = false;
for (Operation *user : op.getUsers()) {
Region *region = user->getParentRegion();
if (isa<RewriteOp>(user) ||
(region && isa<RewriteOp>(region->getParentOp()))) {
hasUserInRewrite = true;
break;
}
}
if (!hasUserInRewrite)
continue;
if (first) {
visit(&op, visited);
first = false;
} else if (!visited.count(&op)) {
return emitOpError("the operations must form a connected component")
.attachNote(op.getLoc())
.append("see a disconnected value / operation here");
}
}
return success();
}
void PatternOp::build(OpBuilder &builder, OperationState &state,
Optional<uint16_t> benefit, Optional<StringRef> name) {
build(builder, state, builder.getI16IntegerAttr(benefit ? *benefit : 0),
name ? builder.getStringAttr(*name) : StringAttr());
state.regions[0]->emplaceBlock();
}
RewriteOp PatternOp::getRewriter() {
return cast<RewriteOp>(body().front().getTerminator());
}
StringRef PatternOp::getDefaultDialect() {
return PDLDialect::getDialectNamespace();
}
LogicalResult ReplaceOp::verify() {
if (replOperation() && !replValues().empty())
return emitOpError() << "expected no replacement values to be provided"
" when the replacement operation is present";
return success();
}
static ParseResult parseResultsValueType(OpAsmParser &p, IntegerAttr index,
Type &resultType) {
if (!index) {
resultType = RangeType::get(p.getBuilder().getType<ValueType>());
return success();
}
if (p.parseArrow() || p.parseType(resultType))
return failure();
return success();
}
static void printResultsValueType(OpAsmPrinter &p, ResultsOp op,
IntegerAttr index, Type resultType) {
if (index)
p << " -> " << resultType;
}
LogicalResult ResultsOp::verify() {
if (!index() && getType().isa<pdl::ValueType>()) {
return emitOpError() << "expected `pdl.range<value>` result type when "
"no index is specified, but got: "
<< getType();
}
return success();
}
LogicalResult RewriteOp::verifyRegions() {
Region &rewriteRegion = body();
if (name()) {
if (!rewriteRegion.empty()) {
return emitOpError()
<< "expected rewrite region to be empty when rewrite is external";
}
return success();
}
if (rewriteRegion.empty()) {
return emitOpError() << "expected rewrite region to be non-empty if "
"external name is not specified";
}
if (!externalArgs().empty()) {
return emitOpError() << "expected no external arguments when the "
"rewrite is specified inline";
}
return success();
}
StringRef RewriteOp::getDefaultDialect() {
return PDLDialect::getDialectNamespace();
}
LogicalResult TypeOp::verify() {
if (!typeAttr())
return verifyHasBindingUse(*this);
return success();
}
LogicalResult TypesOp::verify() {
if (!typesAttr())
return verifyHasBindingUse(*this);
return success();
}
#define GET_OP_CLASSES
#include "mlir/Dialect/PDL/IR/PDLOps.cpp.inc"