//===- Matchers.h - Various common matchers ---------------------*- C++ -*-===// // // 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 // //===----------------------------------------------------------------------===// // // This file provides a simple and efficient mechanism for performing general // tree-based pattern matching over MLIR. This mechanism is inspired by LLVM's // include/llvm/IR/PatternMatch.h. // //===----------------------------------------------------------------------===// #ifndef MLIR_IR_MATCHERS_H #define MLIR_IR_MATCHERS_H #include "mlir/IR/BuiltinAttributes.h" #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/OpDefinition.h" #include "mlir/Interfaces/InferIntRangeInterface.h" namespace mlir { namespace detail { /// The matcher that matches a certain kind of Attribute and binds the value /// inside the Attribute. template < typename AttrClass, // Require AttrClass to be a derived class from Attribute and get its // value type typename ValueType = typename std::enable_if_t< std::is_base_of::value, AttrClass>::ValueType, // Require the ValueType is not void typename = std::enable_if_t::value>> struct attr_value_binder { ValueType *bind_value; /// Creates a matcher instance that binds the value to bv if match succeeds. attr_value_binder(ValueType *bv) : bind_value(bv) {} bool match(Attribute attr) { if (auto intAttr = llvm::dyn_cast(attr)) { *bind_value = intAttr.getValue(); return true; } return false; } }; /// The matcher that matches operations that have the `ConstantLike` trait. struct constant_op_matcher { bool match(Operation *op) { return op->hasTrait(); } }; /// The matcher that matches operations that have the specified op name. struct NameOpMatcher { NameOpMatcher(StringRef name) : name(name) {} bool match(Operation *op) { return op->getName().getStringRef() == name; } StringRef name; }; /// The matcher that matches operations that have the specified attribute name. struct AttrOpMatcher { AttrOpMatcher(StringRef attrName) : attrName(attrName) {} bool match(Operation *op) { return op->hasAttr(attrName); } StringRef attrName; }; /// The matcher that matches operations that have the `ConstantLike` trait, and /// binds the folded attribute value. template struct constant_op_binder { AttrT *bind_value; /// Creates a matcher instance that binds the constant attribute value to /// bind_value if match succeeds. constant_op_binder(AttrT *bind_value) : bind_value(bind_value) {} /// Creates a matcher instance that doesn't bind if match succeeds. constant_op_binder() : bind_value(nullptr) {} bool match(Operation *op) { if (!op->hasTrait()) return false; // Fold the constant to an attribute. SmallVector foldedOp; LogicalResult result = op->fold(/*operands=*/{}, foldedOp); (void)result; assert(succeeded(result) && "expected ConstantLike op to be foldable"); if (auto attr = llvm::dyn_cast(cast(foldedOp.front()))) { if (bind_value) *bind_value = attr; return true; } return false; } }; /// A matcher that matches operations that implement the /// `InferIntRangeInterface` interface, and binds the inferred range. struct infer_int_range_op_binder { IntegerValueRange *bind_value; explicit infer_int_range_op_binder(IntegerValueRange *bind_value) : bind_value(bind_value) {} bool match(Operation *op) { auto inferIntRangeOp = dyn_cast(op); if (!inferIntRangeOp) return false; // Set the range of all integer operands to the maximal range. SmallVector argRanges = llvm::map_to_vector(op->getOperands(), IntegerValueRange::getMaxRange); // Infer the result result range if possible. bool matched = false; auto setResultRanges = [&](Value value, const IntegerValueRange &argRanges) { if (argRanges.isUninitialized()) return; if (value != op->getResult(0)) return; *bind_value = argRanges; matched = true; }; inferIntRangeOp.inferResultRangesFromOptional(argRanges, setResultRanges); return matched; } }; /// The matcher that matches operations that have the specified attribute /// name, and binds the attribute value. template struct AttrOpBinder { /// Creates a matcher instance that binds the attribute value to /// bind_value if match succeeds. AttrOpBinder(StringRef attrName, AttrT *bindValue) : attrName(attrName), bindValue(bindValue) {} /// Creates a matcher instance that doesn't bind if match succeeds. AttrOpBinder(StringRef attrName) : attrName(attrName), bindValue(nullptr) {} bool match(Operation *op) { if (auto attr = op->getAttrOfType(attrName)) { if (bindValue) *bindValue = attr; return true; } return false; } StringRef attrName; AttrT *bindValue; }; /// The matcher that matches a constant scalar / vector splat / tensor splat /// float Attribute or Operation and binds the constant float value. struct constant_float_value_binder { FloatAttr::ValueType *bind_value; /// Creates a matcher instance that binds the value to bv if match succeeds. constant_float_value_binder(FloatAttr::ValueType *bv) : bind_value(bv) {} bool match(Attribute attr) { attr_value_binder matcher(bind_value); if (matcher.match(attr)) return true; if (auto splatAttr = dyn_cast(attr)) return matcher.match(splatAttr.getSplatValue()); return false; } bool match(Operation *op) { Attribute attr; if (!constant_op_binder(&attr).match(op)) return false; Type type = op->getResult(0).getType(); if (isa(type)) return match(attr); return false; } }; /// The matcher that matches a given target constant scalar / vector splat / /// tensor splat float value that fulfills a predicate. struct constant_float_predicate_matcher { bool (*predicate)(const APFloat &); bool match(Attribute attr) { APFloat value(APFloat::Bogus()); return constant_float_value_binder(&value).match(attr) && predicate(value); } bool match(Operation *op) { APFloat value(APFloat::Bogus()); return constant_float_value_binder(&value).match(op) && predicate(value); } }; /// The matcher that matches a constant scalar / vector splat / tensor splat /// integer Attribute or Operation and binds the constant integer value. struct constant_int_value_binder { IntegerAttr::ValueType *bind_value; /// Creates a matcher instance that binds the value to bv if match succeeds. constant_int_value_binder(IntegerAttr::ValueType *bv) : bind_value(bv) {} bool match(Attribute attr) { attr_value_binder matcher(bind_value); if (matcher.match(attr)) return true; if (auto splatAttr = dyn_cast(attr)) return matcher.match(splatAttr.getSplatValue()); return false; } bool match(Operation *op) { Attribute attr; if (!constant_op_binder(&attr).match(op)) return false; Type type = op->getResult(0).getType(); if (isa(type)) return match(attr); return false; } }; /// The matcher that matches a given target constant scalar / vector splat / /// tensor splat integer value that fulfills a predicate. struct constant_int_predicate_matcher { bool (*predicate)(const APInt &); bool match(Attribute attr) { APInt value; return constant_int_value_binder(&value).match(attr) && predicate(value); } bool match(Operation *op) { APInt value; return constant_int_value_binder(&value).match(op) && predicate(value); } }; /// A matcher that matches a given a constant scalar / vector splat / tensor /// splat integer value or a constant integer range that fulfills a predicate. struct constant_int_range_predicate_matcher { bool (*predicate)(const ConstantIntRanges &); bool match(Attribute attr) { APInt value; return constant_int_value_binder(&value).match(attr) && predicate(ConstantIntRanges::constant(value)); } bool match(Operation *op) { // Try to match a constant integer value first. APInt value; if (constant_int_value_binder(&value).match(op)) return predicate(ConstantIntRanges::constant(value)); // Otherwise, try to match an operation that implements the // `InferIntRangeInterface` interface. IntegerValueRange range; return infer_int_range_op_binder(&range).match(op) && predicate(range.getValue()); } }; /// The matcher that matches a certain kind of op. template struct op_matcher { bool match(Operation *op) { return isa(op); } }; /// Trait to check whether T provides a 'match' method with type /// `MatchTarget` (Value, Operation, or Attribute). template using has_compatible_matcher_t = decltype(std::declval().match(std::declval())); /// Statically switch to a Value matcher. template std::enable_if_t::value, bool> matchOperandOrValueAtIndex(Operation *op, unsigned idx, MatcherClass &matcher) { return matcher.match(op->getOperand(idx)); } /// Statically switch to an Operation matcher. template std::enable_if_t::value, bool> matchOperandOrValueAtIndex(Operation *op, unsigned idx, MatcherClass &matcher) { if (auto *defOp = op->getOperand(idx).getDefiningOp()) return matcher.match(defOp); return false; } /// Terminal matcher, always returns true. struct AnyValueMatcher { bool match(Value op) const { return true; } }; /// Terminal matcher, always returns true. struct AnyCapturedValueMatcher { Value *what; AnyCapturedValueMatcher(Value *what) : what(what) {} bool match(Value op) const { *what = op; return true; } }; /// Binds to a specific value and matches it. struct PatternMatcherValue { PatternMatcherValue(Value val) : value(val) {} bool match(Value val) const { return val == value; } Value value; }; template constexpr void enumerateImpl(TupleT &&tuple, CallbackT &&callback, std::index_sequence) { (callback(std::integral_constant{}, std::get(tuple)), ...); } template constexpr void enumerate(std::tuple &tuple, CallbackT &&callback) { detail::enumerateImpl(tuple, std::forward(callback), std::make_index_sequence{}); } /// RecursivePatternMatcher that composes. template struct RecursivePatternMatcher { RecursivePatternMatcher(OperandMatchers... matchers) : operandMatchers(matchers...) {} bool match(Operation *op) { if (!isa(op) || op->getNumOperands() != sizeof...(OperandMatchers)) return false; bool res = true; enumerate(operandMatchers, [&](size_t index, auto &matcher) { res &= matchOperandOrValueAtIndex(op, index, matcher); }); return res; } std::tuple operandMatchers; }; } // namespace detail /// Matches a constant foldable operation. inline detail::constant_op_matcher m_Constant() { return detail::constant_op_matcher(); } /// Matches a named attribute operation. inline detail::AttrOpMatcher m_Attr(StringRef attrName) { return detail::AttrOpMatcher(attrName); } /// Matches a named operation. inline detail::NameOpMatcher m_Op(StringRef opName) { return detail::NameOpMatcher(opName); } /// Matches a value from a constant foldable operation and writes the value to /// bind_value. template inline detail::constant_op_binder m_Constant(AttrT *bind_value) { return detail::constant_op_binder(bind_value); } /// Matches a named attribute operation and writes the value to bind_value. template inline detail::AttrOpBinder m_Attr(StringRef attrName, AttrT *bindValue) { return detail::AttrOpBinder(attrName, bindValue); } /// Matches a constant scalar / vector splat / tensor splat float (both positive /// and negative) zero. inline detail::constant_float_predicate_matcher m_AnyZeroFloat() { return {[](const APFloat &value) { return value.isZero(); }}; } /// Matches a constant scalar / vector splat / tensor splat float positive zero. inline detail::constant_float_predicate_matcher m_PosZeroFloat() { return {[](const APFloat &value) { return value.isPosZero(); }}; } /// Matches a constant scalar / vector splat / tensor splat float negative zero. inline detail::constant_float_predicate_matcher m_NegZeroFloat() { return {[](const APFloat &value) { return value.isNegZero(); }}; } /// Matches a constant scalar / vector splat / tensor splat float ones. inline detail::constant_float_predicate_matcher m_OneFloat() { return {[](const APFloat &value) { return APFloat(value.getSemantics(), 1) == value; }}; } /// Matches a constant scalar / vector splat / tensor splat float ones. inline detail::constant_float_predicate_matcher m_NaNFloat() { return {[](const APFloat &value) { return value.isNaN(); }}; } /// Matches a constant scalar / vector splat / tensor splat float positive /// infinity. inline detail::constant_float_predicate_matcher m_PosInfFloat() { return {[](const APFloat &value) { return !value.isNegative() && value.isInfinity(); }}; } /// Matches a constant scalar / vector splat / tensor splat float negative /// infinity. inline detail::constant_float_predicate_matcher m_NegInfFloat() { return {[](const APFloat &value) { return value.isNegative() && value.isInfinity(); }}; } /// Matches a constant scalar / vector splat / tensor splat integer zero. inline detail::constant_int_predicate_matcher m_Zero() { return {[](const APInt &value) { return 0 == value; }}; } /// Matches a constant scalar / vector splat / tensor splat integer that is any /// non-zero value. inline detail::constant_int_predicate_matcher m_NonZero() { return {[](const APInt &value) { return 0 != value; }}; } /// Matches a constant scalar / vector splat / tensor splat integer or a /// unsigned integer range that does not contain zero. Note that this matcher /// interprets the target value as an unsigned integer. inline detail::constant_int_range_predicate_matcher m_IntRangeWithoutZeroU() { return {[](const ConstantIntRanges &range) { return range.umin().ugt(0); }}; } /// Matches a constant scalar / vector splat / tensor splat integer or a /// signed integer range that does not contain zero. Note that this matcher /// interprets the target value as a signed integer. inline detail::constant_int_range_predicate_matcher m_IntRangeWithoutZeroS() { return {[](const ConstantIntRanges &range) { return range.smin().sgt(0) || range.smax().slt(0); }}; } /// Matches a constant scalar / vector splat / tensor splat integer or a /// signed integer range that does not contain minus one. Note /// that this matcher interprets the target value as a signed integer. inline detail::constant_int_range_predicate_matcher m_IntRangeWithoutNegOneS() { return {[](const ConstantIntRanges &range) { return range.smin().sgt(-1) || range.smax().slt(-1); }}; } /// Matches a constant scalar / vector splat / tensor splat integer one. inline detail::constant_int_predicate_matcher m_One() { return {[](const APInt &value) { return 1 == value; }}; } /// Matches the given OpClass. template inline detail::op_matcher m_Op() { return detail::op_matcher(); } /// Entry point for matching a pattern over a Value. template inline bool matchPattern(Value value, const Pattern &pattern) { assert(value); // TODO: handle other cases if (auto *op = value.getDefiningOp()) return const_cast(pattern).match(op); return false; } /// Entry point for matching a pattern over an Operation. template inline bool matchPattern(Operation *op, const Pattern &pattern) { assert(op); return const_cast(pattern).match(op); } /// Entry point for matching a pattern over an Attribute. Returns `false` /// when `attr` is null. template inline bool matchPattern(Attribute attr, const Pattern &pattern) { static_assert(llvm::is_detected::value, "Pattern does not support matching Attributes"); if (!attr) return false; return const_cast(pattern).match(attr); } /// Matches a constant holding a scalar/vector/tensor float (splat) and /// writes the float value to bind_value. inline detail::constant_float_value_binder m_ConstantFloat(FloatAttr::ValueType *bind_value) { return detail::constant_float_value_binder(bind_value); } /// Matches a constant holding a scalar/vector/tensor integer (splat) and /// writes the integer value to bind_value. inline detail::constant_int_value_binder m_ConstantInt(IntegerAttr::ValueType *bind_value) { return detail::constant_int_value_binder(bind_value); } template auto m_Op(Matchers... matchers) { return detail::RecursivePatternMatcher(matchers...); } namespace matchers { inline auto m_Any() { return detail::AnyValueMatcher(); } inline auto m_Any(Value *val) { return detail::AnyCapturedValueMatcher(val); } inline auto m_Val(Value v) { return detail::PatternMatcherValue(v); } } // namespace matchers } // namespace mlir #endif // MLIR_IR_MATCHERS_H