Program Listing for File pattern_match.h

Program Listing for File pattern_match.h#

↰ Return to documentation for file (tvm/ir/prim/pattern_match.h)

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#ifndef TVM_IR_PRIM_PATTERN_MATCH_H_
#define TVM_IR_PRIM_PATTERN_MATCH_H_

#include <tvm/ir/pattern_match.h>
#include <tvm/ir/prim/const_fold.h>
#include <tvm/ir/prim/expr.h>
#include <tvm/ir/prim/op.h>

#include <cmath>
#include <tuple>

namespace tvm {
template <>
class PEqualChecker<PrimExpr> {
 public:
  bool operator()(const PrimExpr& lhs, const PrimExpr& rhs) const {
    if (lhs.same_as(rhs)) return true;
    return prim::ExprDeepEqual()(lhs, rhs);
  }
};

template <>
class PEqualChecker<IntImm> {
 public:
  bool operator()(const IntImm& lhs, const IntImm& rhs) const { return lhs->value == rhs->value; }
};

template <>
class PEqualChecker<FloatImm> {
 public:
  bool operator()(const FloatImm& lhs, const FloatImm& rhs) const {
    return std::fabs(lhs->value - rhs->value) < 1e-20;
  }
};

template <>
class PEqualChecker<Var> {
 public:
  bool operator()(const Var& lhs, const Var& rhs) const { return lhs.same_as(rhs); }
};

template <typename T, typename DType,
          typename = std::enable_if<std::is_base_of<T, PrimExpr>::value>>
class PVarWithDataType : public PVarWithCheck<PVarWithDataType<T, DType>, T> {
 public:
  explicit PVarWithDataType(const DType& dtype) : dtype_(dtype) {}

  bool Match_(const T& value) const { return dtype_.Match_(value.ty()->dtype); }

 protected:
  typename DType::Nested dtype_;
};

class PVecDataType : public PVarWithCheck<PVecDataType, DLDataType> {
 public:
  explicit PVecDataType(DLDataType elem_dtype) : elem_dtype_(elem_dtype) {}

  bool Match_(DLDataType dtype) const { return dtype.code == elem_dtype_.code; }

 protected:
  DLDataType elem_dtype_;
};

template <typename OpType, typename TA, typename TB, bool kFoldConstants = true>
class PBinaryExpr : public Pattern<PBinaryExpr<OpType, TA, TB, kFoldConstants>> {
 public:
  PBinaryExpr(const TA& a, const TB& b) : a_(a), b_(b) {}

  void InitMatch_() const {
    a_.InitMatch_();
    b_.InitMatch_();
  }

  bool Match_(const ffi::ObjectRef& node) const {
    using NodeType = typename OpType::ContainerType;
    if (const NodeType* ptr = node.as<NodeType>()) {
      if (!a_.Match_(ptr->a)) return false;
      if (!b_.Match_(ptr->b)) return false;
      return true;
    } else {
      return false;
    }
  }

  PrimExpr Eval() const {
    PrimExpr lhs = a_.Eval();
    PrimExpr rhs = b_.Eval();
    if constexpr (kFoldConstants) {
      if (auto ret = prim::TryConstFold<OpType>(lhs, rhs)) return ret.value();
    }
    return OpType(lhs, rhs);
  }

 private:
  typename TA::Nested a_;
  typename TB::Nested b_;
};

template <typename TA>
class PConstWithTypeLike : public Pattern<PConstWithTypeLike<TA>> {
 public:
  PConstWithTypeLike(const TA& ref, int64_t value) : ref_(ref), value_(value) {}

  void InitMatch_() const {}

  bool Match_(const ffi::ObjectRef& node) const {
    if (const IntImmObj* ptr = node.as<IntImmObj>()) {
      return ptr->value == value_;
    } else {
      return false;
    }
  }

  PrimExpr Eval() const { return prim::MakeConst(ref_.Eval().ty(), value_); }

 private:
  typename TA::Nested ref_;
  int64_t value_;
};

#define TVM_PATTERN_BINARY_OP_EX(FuncName, NodeName, CheckStep)                               \
  template <typename TA, typename TB>                                                         \
  inline PBinaryExpr<NodeName, TA, TB> FuncName(const Pattern<TA>& a, const Pattern<TB>& b) { \
    CheckStep;                                                                                \
    return PBinaryExpr<NodeName, TA, TB>(a.derived(), b.derived());                           \
  }                                                                                           \
  template <typename TA>                                                                      \
  inline PBinaryExpr<NodeName, TA, PConstWithTypeLike<TA>> FuncName(const Pattern<TA>& a,     \
                                                                    int64_t b) {              \
    CheckStep;                                                                                \
    return FuncName(a, PConstWithTypeLike<TA>(a.derived(), b));                               \
  }                                                                                           \
  template <typename TA>                                                                      \
  inline PBinaryExpr<NodeName, PConstWithTypeLike<TA>, TA> FuncName(int64_t b,                \
                                                                    const Pattern<TA>& a) {   \
    CheckStep;                                                                                \
    return FuncName(PConstWithTypeLike<TA>(a.derived(), b), a);                               \
  }

#define TVM_PATTERN_BINARY_OP(FuncName, NodeName) TVM_PATTERN_BINARY_OP_EX(FuncName, NodeName, )

// raise ambiguity error for operator overload of / and %
TVM_PATTERN_BINARY_OP_EX(operator/, prim::Div, DivAmbiguityError(a));
TVM_PATTERN_BINARY_OP_EX(operator%, prim::Mod, DivAmbiguityError(a));

// arithmetic expressions
TVM_PATTERN_BINARY_OP(operator+, prim::Add);
TVM_PATTERN_BINARY_OP(operator-, prim::Sub);
TVM_PATTERN_BINARY_OP(operator*, prim::Mul);
TVM_PATTERN_BINARY_OP(min, prim::Min);
TVM_PATTERN_BINARY_OP(max, prim::Max);
TVM_PATTERN_BINARY_OP(div, prim::Div);
TVM_PATTERN_BINARY_OP(truncdiv, prim::Div);
TVM_PATTERN_BINARY_OP(truncmod, prim::Mod);
TVM_PATTERN_BINARY_OP(floordiv, prim::FloorDiv);
TVM_PATTERN_BINARY_OP(floormod, prim::FloorMod);

// Bitwise patterns preserve nonfolding construction during evaluation.
#define TVM_PATTERN_BITWISE_OP(FuncName, NodeName)                             \
  template <typename TA, typename TB>                                          \
  inline PBinaryExpr<NodeName, TA, TB, false> FuncName(const Pattern<TA>& a,   \
                                                       const Pattern<TB>& b) { \
    return PBinaryExpr<NodeName, TA, TB, false>(a.derived(), b.derived());     \
  }

TVM_PATTERN_BITWISE_OP(operator<<, prim::LShift);
TVM_PATTERN_BITWISE_OP(operator>>, prim::RShift);
TVM_PATTERN_BITWISE_OP(operator&, prim::BitwiseAnd);
TVM_PATTERN_BITWISE_OP(operator|, prim::BitwiseOr);
TVM_PATTERN_BITWISE_OP(operator^, prim::BitwiseXor);

#undef TVM_PATTERN_BITWISE_OP

// logical expressions
TVM_PATTERN_BINARY_OP(operator>, prim::GT);
TVM_PATTERN_BINARY_OP(operator>=, prim::GE);
TVM_PATTERN_BINARY_OP(operator<, prim::LT);
TVM_PATTERN_BINARY_OP(operator<=, prim::LE);
TVM_PATTERN_BINARY_OP(operator==, prim::EQ);
TVM_PATTERN_BINARY_OP(operator!=, prim::NE);
TVM_PATTERN_BINARY_OP(operator&&, prim::And);
TVM_PATTERN_BINARY_OP(operator||, prim::Or);

template <typename TA>
class PNotExpr : public Pattern<PNotExpr<TA>> {
 public:
  explicit PNotExpr(const TA& value) : value_(value) {}

  void InitMatch_() const { value_.InitMatch_(); }

  bool Match_(const ffi::ObjectRef& node) const {
    if (const prim::NotObj* ptr = node.as<prim::NotObj>()) {
      if (!value_.Match_(ptr->a)) return false;
      return true;
    } else {
      return false;
    }
  }

  PrimExpr Eval() const { return prim::Not(value_.Eval()); }

 private:
  typename TA::Nested value_;
};

template <typename TA>
inline PNotExpr<TA> operator!(const Pattern<TA>& value) {
  return PNotExpr<TA>(value.derived());
}

template <typename TA>
class PBitwiseNotExpr : public Pattern<PBitwiseNotExpr<TA>> {
 public:
  explicit PBitwiseNotExpr(const TA& value) : value_(value) {}

  void InitMatch_() const { value_.InitMatch_(); }

  bool Match_(const ffi::ObjectRef& node) const {
    if (const prim::BitwiseNotObj* ptr = node.as<prim::BitwiseNotObj>()) {
      if (!value_.Match_(ptr->a)) return false;
      return true;
    } else {
      return false;
    }
  }

  PrimExpr Eval() const { return prim::BitwiseNot(value_.Eval()); }

 private:
  typename TA::Nested value_;
};

template <typename TA>
inline PBitwiseNotExpr<TA> operator~(const Pattern<TA>& value) {
  return PBitwiseNotExpr<TA>(value.derived());
}

// select
template <typename TCond, typename TA, typename TB>
class PSelectExpr : public Pattern<PSelectExpr<TCond, TA, TB>> {
 public:
  PSelectExpr(const TCond& condition, const TA& true_value, const TB& false_value)
      : condition_(condition), true_value_(true_value), false_value_(false_value) {}

  void InitMatch_() const {
    condition_.InitMatch_();
    true_value_.InitMatch_();
    false_value_.InitMatch_();
  }

  bool Match_(const ffi::ObjectRef& node) const {
    if (const prim::SelectObj* ptr = node.as<prim::SelectObj>()) {
      if (!condition_.Match_(ptr->condition)) return false;
      if (!true_value_.Match_(ptr->true_value)) return false;
      if (!false_value_.Match_(ptr->false_value)) return false;
      return true;
    } else {
      return false;
    }
  }

  PrimExpr Eval() const {
    return prim::Select(condition_.Eval(), true_value_.Eval(), false_value_.Eval());
  }

 private:
  typename TCond::Nested condition_;
  typename TA::Nested true_value_;
  typename TB::Nested false_value_;
};

template <typename TCond, typename TA, typename TB>
inline PSelectExpr<TCond, TA, TB> select(const Pattern<TCond>& condition,
                                         const Pattern<TA>& true_value,
                                         const Pattern<TB>& false_value) {
  return PSelectExpr<TCond, TA, TB>(condition.derived(), true_value.derived(),
                                    false_value.derived());
}

template <typename DType, typename TA>
class PCastExpr : public Pattern<PCastExpr<DType, TA>> {
 public:
  PCastExpr(const TA& value, const DType& dtype) : dtype_(dtype), value_(value) {}

  void InitMatch_() const {
    dtype_.InitMatch_();
    value_.InitMatch_();
  }

  bool Match_(const ffi::ObjectRef& node) const {
    if (const prim::CastObj* ptr = node.as<prim::CastObj>()) {
      if (!dtype_.Match_(ptr->ty.as_or_throw<PrimType>()->dtype)) return false;
      if (!value_.Match_(ptr->value)) return false;
      return true;
    } else {
      return false;
    }
  }

  PrimExpr Eval() const { return prim::Cast(PrimType(dtype_.Eval()), value_.Eval()); }

 private:
  typename DType::Nested dtype_;
  typename TA::Nested value_;
};

template <typename DType, typename TA>
inline PCastExpr<DType, TA> cast(const Pattern<TA>& value, const Pattern<DType>& dtype) {
  return PCastExpr<DType, TA>(value.derived(), dtype.derived());
}

template <typename TBase, typename TStride, typename TLanes>
class PRampExpr : public Pattern<PRampExpr<TBase, TStride, TLanes>> {
 public:
  PRampExpr(const TBase& base, const TStride& stride, const TLanes& lanes)
      : base_(base), stride_(stride), lanes_(lanes) {}

  void InitMatch_() const {
    base_.InitMatch_();
    stride_.InitMatch_();
    lanes_.InitMatch_();
  }

  bool Match_(const ffi::ObjectRef& node) const {
    if (const prim::RampObj* ptr = node.as<prim::RampObj>()) {
      if (!base_.Match_(ptr->base)) return false;
      if (!stride_.Match_(ptr->stride)) return false;
      if (!lanes_.Match_(ptr->lanes)) return false;
      return true;
    } else {
      return false;
    }
  }

  PrimExpr Eval() const { return prim::Ramp(base_.Eval(), stride_.Eval(), lanes_.Eval()); }

 private:
  typename TBase::Nested base_;
  typename TStride::Nested stride_;
  typename TLanes::Nested lanes_;
};

template <typename TBase, typename TStride, typename TLanes>
inline PRampExpr<TBase, TStride, TLanes> ramp(const Pattern<TBase>& base,
                                              const Pattern<TStride>& stride,
                                              const Pattern<TLanes>& lanes) {
  return PRampExpr<TBase, TStride, TLanes>(base.derived(), stride.derived(), lanes.derived());
}

template <typename TBase>
inline PRampExpr<TBase, PConstWithTypeLike<TBase>, PConstWithTypeLike<TBase>> ramp(
    const Pattern<TBase>& base, int stride, int lanes) {
  return PRampExpr<TBase, PConstWithTypeLike<TBase>, PConstWithTypeLike<TBase>>(
      base.derived(), PConstWithTypeLike<TBase>(base.derived(), stride),
      PConstWithTypeLike<TBase>(base.derived(), lanes));
}

template <typename TA, typename TLanes>
class PBroadcastExpr : public Pattern<PBroadcastExpr<TA, TLanes>> {
 public:
  PBroadcastExpr(const TA& value, const TLanes& lanes) : value_(value), lanes_(lanes) {}

  void InitMatch_() const {
    value_.InitMatch_();
    lanes_.InitMatch_();
  }

  bool Match_(const ffi::ObjectRef& node) const {
    if (const prim::BroadcastObj* ptr = node.as<prim::BroadcastObj>()) {
      if (!value_.Match_(ptr->value)) return false;
      if (!lanes_.Match_(ptr->lanes)) return false;
      return true;
    } else {
      return false;
    }
  }

  PrimExpr Eval() const { return prim::Broadcast(value_.Eval(), lanes_.Eval()); }

 private:
  typename TA::Nested value_;
  typename TLanes::Nested lanes_;
};

template <typename TA, typename TLanes>
inline PBroadcastExpr<TA, TLanes> broadcast(const Pattern<TA>& value,
                                            const Pattern<TLanes>& lanes) {
  return PBroadcastExpr<TA, TLanes>(value.derived(), lanes.derived());
}

// internal namespace
namespace detail {
// implementation details for  CallExpr
template <bool stop, std::size_t I, typename F>
struct tuple_for_each_dispatcher {
  template <typename TTuple>
  static void run(F& f, const TTuple& tuple) {  // NOLINT(*)
    f(I, std::get<I>(tuple));
    tuple_for_each_dispatcher<(I + 1) == std::tuple_size<TTuple>::value, (I + 1), F>::run(f, tuple);
  }
};

template <std::size_t I, typename F>
struct tuple_for_each_dispatcher<true, I, F> {
  template <typename TTuple>
  static void run(F& f, const TTuple& tuple) {}  // NOLINT(*)
};

template <typename F, typename TTuple>
inline void tuple_for_each(F& f, const TTuple& tuple) {  // NOLINT(*)
  tuple_for_each_dispatcher<std::tuple_size<TTuple>::value == 0, 0, F>::run(f, tuple);
}

struct PCallExprInitMatchFunctor {
  template <typename T>
  void operator()(size_t i, const T& pattern) const {
    pattern.InitMatch_();
  }
};

struct PCallExprMatchFunctor {
  const CallObj* call_;
  bool matched_{true};

  explicit PCallExprMatchFunctor(const CallObj* call) : call_(call) {}

  template <typename T>
  void operator()(size_t i, const T& pattern) {
    matched_ = matched_ && pattern.Match_(call_->args[i]);
  }
};

struct PCallExprEvalArgsFunctor {
  ffi::Array<PrimExpr> args_;

  template <typename T>
  void operator()(size_t i, const T& pattern) {
    args_.push_back(pattern.Eval());
  }
};
}  // namespace detail

template <typename Op, typename... TArgs>
class PCallExpr : public Pattern<PCallExpr<Op, TArgs...>> {
 public:
  explicit PCallExpr(const TArgs&... args) : args_(args...) {}

  void InitMatch_() const {
    detail::PCallExprInitMatchFunctor finit;
    detail::tuple_for_each(finit, args_);
  }

  bool Match_(const ffi::ObjectRef& node) const {
    if (const CallObj* ptr = node.as<CallObj>()) {
      if (ptr->args.size() != sizeof...(TArgs)) return false;
      if (!ptr->op.same_as(Op::GetOp())) return false;
      detail::PCallExprMatchFunctor fmatch(ptr);
      detail::tuple_for_each(fmatch, args_);
      return fmatch.matched_;
    } else {
      return false;
    }
  }

  PrimExpr Eval() const {
    detail::PCallExprEvalArgsFunctor feval_args;
    detail::tuple_for_each(feval_args, args_);
    return Op::Eval(feval_args.args_);
  }

 private:
  std::tuple<typename TArgs::Nested...> args_;
};

// if_then_else
struct PIfThenElseOp {
  static PrimExpr Eval(ffi::Array<PrimExpr> args) {
    return Call(args[1].ty(), GetOp(), args).as_or_throw<PrimExpr>();
  }
  static const Op& GetOp() { return prim::if_then_else_op(); }
};

template <typename TCond, typename TA, typename TB>
inline PCallExpr<PIfThenElseOp, TCond, TA, TB> if_then_else(const Pattern<TCond>& cond,
                                                            const Pattern<TA>& true_value,
                                                            const Pattern<TB>& false_value) {
  return PCallExpr<PIfThenElseOp, TCond, TA, TB>(cond.derived(), true_value.derived(),
                                                 false_value.derived());
}

}  // namespace tvm
#endif  // TVM_IR_PRIM_PATTERN_MATCH_H_