Skip to content

[mlir] Allow using non-attribute properties in declarative rewrite patterns #143071

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions mlir/docs/DeclarativeRewrites.md
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,11 @@ template. The string can be an arbitrary C++ expression that evaluates into some
C++ object expected at the `NativeCodeCall` site (here it would be expecting an
array attribute). Typically the string should be a function call.

In the case of properties, the return value of the `NativeCodeCall` should
be in terms of the _interface_ type of a property. For example, the `NativeCodeCall`
for a `StringProp` should return a `StringRef`, which will copied into the underlying
`std::string`, just as if it were an argument to the operation's builder.

##### `NativeCodeCall` placeholders

In `NativeCodeCall`, we can use placeholders like `$_builder`, `$N` and `$N...`.
Expand Down Expand Up @@ -416,14 +421,20 @@ must be either passed by reference or pointer to the variable used as argument
so that the matched value can be returned. In the same example, `$val` will be
bound to a variable with `Attribute` type (as `I32Attr`) and the type of the
second argument in `Foo()` could be `Attribute&` or `Attribute*`. Names with
attribute constraints will be captured as `Attribute`s while everything else
will be treated as `Value`s.
attribute constraints will be captured as `Attribute`s, names with
property constraints (which must have a concrete interface type) will be treated
as that type, and everything else will be treated as `Value`s.

Positional placeholders will be substituted by the `dag` object parameters at
the `NativeCodeCall` use site. For example, if we define `SomeCall :
NativeCodeCall<"someFn($1, $2, $0)">` and use it like `(SomeCall $in0, $in1,
$in2)`, then this will be translated into C++ call `someFn($in1, $in2, $in0)`.

In the case of properties, the placeholder will be bound to a value of the _interface_
type of the property. For example, passing in a `StringProp` as an argument to a `NativeCodeCall` will pass a `StringRef` (as if the getter of the matched
operation were called) and not a `std::string`. See
`mlir/include/mlir/IR/Properties.td` for details on interface vs. storage type.

Positional range placeholders will be substituted by multiple `dag` object
parameters at the `NativeCodeCall` use site. For example, if we define
`SomeCall : NativeCodeCall<"someFn($1...)">` and use it like `(SomeCall $in0,
Expand Down
15 changes: 15 additions & 0 deletions mlir/include/mlir/IR/Properties.td
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,21 @@ class ConfinedProperty<Property p, Pred pred, string newSummary = "">
: ConfinedProp<p, pred, newSummary>,
Deprecated<"moved to shorter name ConfinedProp">;

/// Defines a constant value of type `prop` to be used in pattern matching.
/// When used as a constraint, forms a matcher that tests that the property is
/// equal to the given value (and matches any other constraints on the property).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this uses/requires that the interface type has an equality operation.

Does this also work in templated cases?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs updated

/// The constant value is given as a string and should be of the _interface_ type
/// of the attribute.
///
/// This requires that the given property's inference type be comparable to the
/// given value with `==`, and does require specify a concrete property type.
class ConstantProp<Property prop, string val>
: ConfinedProp<prop,
CPred<"$_self == " # val>,
"constant '" # prop.summary # "': " # val> {
string value = val;
}

//===----------------------------------------------------------------------===//
// Primitive property combinators
//===----------------------------------------------------------------------===//
Expand Down
48 changes: 47 additions & 1 deletion mlir/include/mlir/TableGen/Pattern.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,23 @@ class DagLeaf {
// specifies an attribute constraint.
bool isAttrMatcher() const;

// Returns true if this DAG leaf is matching a property. That is, it
// specifies a property constraint.
bool isPropMatcher() const;

// Returns true if this DAG leaf is describing a property. That is, it
// is a subclass of `Property` in tablegen.
bool isPropDefinition() const;

// Returns true if this DAG leaf is wrapping native code call.
bool isNativeCodeCall() const;

// Returns true if this DAG leaf is specifying a constant attribute.
bool isConstantAttr() const;

// Returns true if this DAG leaf is specifying a constant property.
bool isConstantProp() const;

// Returns true if this DAG leaf is specifying an enum case.
bool isEnumCase() const;

Expand All @@ -88,9 +99,19 @@ class DagLeaf {
// Returns this DAG leaf as a constraint. Asserts if fails.
Constraint getAsConstraint() const;

// Returns this DAG leaf as a property constraint. Asserts if fails. This
// allows access to the interface type.
PropConstraint getAsPropConstraint() const;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does properties need to be treated specially compared to other constraints?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They've got an interface type attached


// Returns this DAG leaf as a property definition. Asserts if fails.
Property getAsProperty() const;

// Returns this DAG leaf as an constant attribute. Asserts if fails.
ConstantAttr getAsConstantAttr() const;

// Returns this DAG leaf as an constant property. Asserts if fails.
ConstantProp getAsConstantProp() const;

// Returns this DAG leaf as an enum case.
// Precondition: isEnumCase()
EnumCase getAsEnumCase() const;
Expand Down Expand Up @@ -279,6 +300,10 @@ class SymbolInfoMap {
// the DAG of the operation, `operandIndexOrNumValues` specifies the
// operand index, and `variadicSubIndex` must be set to `std::nullopt`.
//
// * Properties not associated with an operation (e.g. as arguments to
// native code) have their corresponding PropConstraint stored in the
// `dag` field. This constraint is only used when
//
// * If a symbol is defined in a `variadic` DAG, `dag` specifies the DAG
// of the parent operation, `operandIndexOrNumValues` specifies the
// declared operand index of the variadic operand in the parent
Expand Down Expand Up @@ -364,12 +389,20 @@ class SymbolInfoMap {

// What kind of entity this symbol represents:
// * Attr: op attribute
// * Prop: op property
// * Operand: op operand
// * Result: op result
// * Value: a value not attached to an op (e.g., from NativeCodeCall)
// * MultipleValues: a pack of values not attached to an op (e.g., from
// NativeCodeCall). This kind supports indexing.
enum class Kind : uint8_t { Attr, Operand, Result, Value, MultipleValues };
enum class Kind : uint8_t {
Attr,
Prop,
Operand,
Result,
Value,
MultipleValues
};

// Creates a SymbolInfo instance. `dagAndConstant` is only used for `Attr`
// and `Operand` so should be std::nullopt for `Result` and `Value` kind.
Expand All @@ -384,6 +417,15 @@ class SymbolInfoMap {
static SymbolInfo getAttr() {
return SymbolInfo(nullptr, Kind::Attr, std::nullopt);
}
static SymbolInfo getProp(const Operator *op, int index) {
return SymbolInfo(op, Kind::Prop,
DagAndConstant(nullptr, index, std::nullopt));
}
static SymbolInfo getProp(const PropConstraint *constraint) {
// -1 for anthe `operandIndexOrNumValues` is a sentinel value.
return SymbolInfo(nullptr, Kind::Prop,
DagAndConstant(constraint, -1, std::nullopt));
}
static SymbolInfo
getOperand(DagNode node, const Operator *op, int operandIndex,
std::optional<int> variadicSubIndex = std::nullopt) {
Expand Down Expand Up @@ -488,6 +530,10 @@ class SymbolInfoMap {
// is already bound.
bool bindAttr(StringRef symbol);

// Registers the given `symbol` as bound to a property that satisfies the
// given `constraint`. `constraint` must name a concrete interface type.
bool bindProp(StringRef symbol, const PropConstraint &constraint);

// Returns true if the given `symbol` is bound.
bool contains(StringRef symbol) const;

Expand Down
21 changes: 20 additions & 1 deletion mlir/include/mlir/TableGen/Property.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ class Pred;
// Wrapper class providing helper methods for accesing property constraint
// values.
class PropConstraint : public Constraint {
public:
using Constraint::Constraint;

public:
static bool classof(const Constraint *c) { return c->getKind() == CK_Prop; }

StringRef getInterfaceType() const;
Expand Down Expand Up @@ -143,6 +143,10 @@ class Property : public PropConstraint {
// property constraints, this function is added for future-proofing)
Property getBaseProperty() const;

// Returns true if this property is backed by a TableGen definition and that
// definition is a subclass of `className`.
bool isSubClassOf(StringRef className) const;

private:
// Elements describing a Property, in general fetched from the record.
StringRef summary;
Expand All @@ -169,6 +173,21 @@ struct NamedProperty {
Property prop;
};

// Wrapper class providing helper methods for processing constant property
// values defined using the `ConstantProp` subclass of `Property`
// in TableGen.
class ConstantProp : public Property {
public:
explicit ConstantProp(const llvm::DefInit *def) : Property(def) {
assert(isSubClassOf("ConstantProp"));
}

static bool classof(Property *p) { return p->isSubClassOf("ConstantProp"); }

// Return the constant value of the property as an expression
// that produces an interface-type constant.
StringRef getValue() const;
};
} // namespace tblgen
} // namespace mlir

Expand Down
47 changes: 36 additions & 11 deletions mlir/lib/TableGen/CodeGenHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,10 +205,14 @@ static ::llvm::LogicalResult {0}(

/// Code for a pattern type or attribute constraint.
///
/// {3}: "Type type" or "Attribute attr".
static const char *const patternAttrOrTypeConstraintCode = R"(
/// {0}: name of function
/// {1}: Condition template
/// {2}: Constraint summary
/// {3}: "::mlir::Type type" or "::mlirAttribute attr" or "propType prop".
/// Can be "T prop" for generic property constraints.
static const char *const patternConstraintCode = R"(
static ::llvm::LogicalResult {0}(
::mlir::PatternRewriter &rewriter, ::mlir::Operation *op, ::mlir::{3},
::mlir::PatternRewriter &rewriter, ::mlir::Operation *op, {3},
::llvm::StringRef failureStr) {
if (!({1})) {
return rewriter.notifyMatchFailure(op, [&](::mlir::Diagnostic &diag) {
Expand Down Expand Up @@ -265,15 +269,31 @@ void StaticVerifierFunctionEmitter::emitPatternConstraints() {
FmtContext ctx;
ctx.addSubst("_op", "*op").withBuilder("rewriter").withSelf("type");
for (auto &it : typeConstraints) {
os << formatv(patternAttrOrTypeConstraintCode, it.second,
os << formatv(patternConstraintCode, it.second,
tgfmt(it.first.getConditionTemplate(), &ctx),
escapeString(it.first.getSummary()), "Type type");
escapeString(it.first.getSummary()), "::mlir::Type type");
}
ctx.withSelf("attr");
for (auto &it : attrConstraints) {
os << formatv(patternAttrOrTypeConstraintCode, it.second,
os << formatv(patternConstraintCode, it.second,
tgfmt(it.first.getConditionTemplate(), &ctx),
escapeString(it.first.getSummary()), "Attribute attr");
escapeString(it.first.getSummary()),
"::mlir::Attribute attr");
}
ctx.withSelf("prop");
for (auto &it : propConstraints) {
PropConstraint propConstraint = cast<PropConstraint>(it.first);
StringRef interfaceType = propConstraint.getInterfaceType();
// Constraints that are generic over multiple interface types are
// templatized under the assumption that they'll be used correctly.
if (interfaceType.empty()) {
interfaceType = "T";
os << "template <typename T>";
}
os << formatv(patternConstraintCode, it.second,
tgfmt(propConstraint.getConditionTemplate(), &ctx),
escapeString(propConstraint.getSummary()),
Twine(interfaceType) + " prop");
}
}

Expand Down Expand Up @@ -367,10 +387,15 @@ void StaticVerifierFunctionEmitter::collectOpConstraints(
void StaticVerifierFunctionEmitter::collectPatternConstraints(
const ArrayRef<DagLeaf> constraints) {
for (auto &leaf : constraints) {
assert(leaf.isOperandMatcher() || leaf.isAttrMatcher());
collectConstraint(
leaf.isOperandMatcher() ? typeConstraints : attrConstraints,
leaf.isOperandMatcher() ? "type" : "attr", leaf.getAsConstraint());
assert(leaf.isOperandMatcher() || leaf.isAttrMatcher() ||
leaf.isPropMatcher());
Constraint constraint = leaf.getAsConstraint();
if (leaf.isOperandMatcher())
collectConstraint(typeConstraints, "type", constraint);
else if (leaf.isAttrMatcher())
collectConstraint(attrConstraints, "attr", constraint);
else if (leaf.isPropMatcher())
collectConstraint(propConstraints, "prop", constraint);
}
}

Expand Down
Loading
Loading