Skip to content
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

[EXPR] Expression-template based pattern matching. #2589

Merged
merged 5 commits into from
Feb 18, 2019

Conversation

tqchen
Copy link
Member

@tqchen tqchen commented Feb 12, 2019

Pattern matching and rewriting is a common task in compiler analysis. So far, we manually write code that detects the patterns and rewrite them. This PR introduces a new utility to use expression-template for writing pattern matching code in C++.

As an example, here are two versions of the code to rewrite max(x + z, y + z) => max(x, y) + z

Manual Pattern Matching

Expr MaxRewrite(Expr expr) {
   auto* pmax = expr.as<ir::Max>();
   if (pmax == nullptr) return expr;
   auto* pa = pmax->a.as<ir::Add>();
   auto* pb = pmax->b.as<ir::Add>();
   if (pa == nullptr || pb == nullptr) return expr;
   if (!ir::Equal(pa->b, pb->b)) return expr;
   return max(pa->a, pb->a) + pa->b;
}

Use Pattern Match Util

Expr MaxRewrite(Expr expr) {
  using namespace arith; 
  PVar<Expr> x, y, z;
  if (max(x + z, y + z).Match(expr)) {
    return (max(x, y) + z).Eval();
  } else {
    return expr;
  }
}

The template inlining will generate the same code as if we are doing manual pattern matching.

See pattern_match_test.cc for more examples.

@tqchen
Copy link
Member Author

tqchen commented Feb 12, 2019

@sgrechanik-h @ZihengJiang @antinucleon @jroesch please help review the PR

CHECK(!(px + (px + px)).Match(r));
CHECK(!(px + (py + py)).Match(r));
CHECK((px + (py + pz)).Match(r));
{
Copy link
Contributor

Choose a reason for hiding this comment

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

Does it make sense to make changes such that (px+(px+py)) too matches 1+ (y+1), that is, to take into consideration the +'s commutativity? Or is that commutativity something that needs to be taken care of independently?

@tqchen
Copy link
Member Author

tqchen commented Feb 12, 2019

@derisavi brings up the question on whether do we need to support commutativity so that
(px+(px+py)) also matches 1+ (y+1). Since this is a topic that affect a general design choice, I put the reply here for the record.

My feeling is that we should not. Mainly because pattern matching in traditional sense only matches the structure. Supporting commutativity in + will make the time complexity blow up if we have a deep nest of adds, which may also be not desirable.

* so the result code should be as efficient as manually write pattern match code
* using as cast and checks.
*
* The code below gives shows how to use the pattern matcher.
Copy link
Contributor

Choose a reason for hiding this comment

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

remove "gives"

* It helps to simplify pattern matching and rewrites.
* All the patterns are generated via expression template during compile time,
* so the result code should be as efficient as manually write pattern match code
* using as cast and checks.
Copy link
Contributor

Choose a reason for hiding this comment

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

Some typos, I guess it should be "written", and "as" seems redundant.

* arith::PVar<Expr> x, y, z;
*
* // The following code tries to match the declared pattern.
* // Match will fill the result of match into PVar if successful.
Copy link
Contributor

Choose a reason for hiding this comment

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

Destructively filling the PVars doesn't feel right. It will cause problems with multithreading and with some cases when we want to match several expressions against the same pattern. So I suggest that we think in advance about these problems and how to prevent them.

Copy link
Member Author

Choose a reason for hiding this comment

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

The current mechanism is certainly not thread-safe. I will add a comments on that

Copy link
Contributor

Choose a reason for hiding this comment

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

I think my main concern is the possibility of writing something like this:

PVar<Expr> x, y;
if ((x + y).Match(expr)) {
  if((x * y).Match(another_expr)) {
    ...
  }
  // x was refilled but it's not entirely clear from the code
}

It is possible to forbid this by raising an error in InitMatch_ if the variable has been filled already. However I'm not sure if it's worth the trouble for an internal tool, so I won't insist.

* // The following code tries to match the declared pattern.
* // Match will fill the result of match into PVar if successful.
* // Note that z occurs twice in the pattern,
* // an equality check to ensure each occurance of z is equivalent
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess there is a word missing, "equality checked is performed" or something like this

namespace tvm {
namespace arith {
/*!
* \brief Base cass of all the patterns.
Copy link
Contributor

Choose a reason for hiding this comment

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

typo cass -> class

* \brief Pattern not expression.
* \tparam TCond The pattern type of the condition.
* \tparam TA The pattern type of the true operand.
* \tparam TB The pattern type of the false operand.
Copy link
Contributor

Choose a reason for hiding this comment

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

Parameter descriptions are from the next class.

};

template<typename TA>
inline PNotExpr<TA> operator!(const TA& value) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't it be Pattern<TA> in the type of the argument?

}
private:
const T value_;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

May be add some helper function to construct const patterns?

Copy link
Member Author

Choose a reason for hiding this comment

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

Since PConst() is usually as short, I will skip this for now.

* \return The result pattern.
*
* \tparam DType The pattern type of type.
* \tparam TA The pattern type of the true operand.
Copy link
Contributor

Choose a reason for hiding this comment

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

The "type of the true operand" seems to be from some other place.

}

// internal namespace
namespace detail {
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't know what the code style guide says but I would indent the contents of detail for readability.

Copy link
Member Author

Choose a reason for hiding this comment

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

The google code style do not indent namespace

@tqchen
Copy link
Member Author

tqchen commented Feb 13, 2019

Thanks, @sgrechanik-h @derisavi for helpful reviews, please check again.

* arith::PVar<Expr> x, y, z;
*
* // The following code tries to match the declared pattern.
* // Match will fill the result of match into PVar if successful.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think my main concern is the possibility of writing something like this:

PVar<Expr> x, y;
if ((x + y).Match(expr)) {
  if((x * y).Match(another_expr)) {
    ...
  }
  // x was refilled but it's not entirely clear from the code
}

It is possible to forbid this by raising an error in InitMatch_ if the variable has been filled already. However I'm not sure if it's worth the trouble for an internal tool, so I won't insist.

* \endcode
*
* \note The pattern matcher is not threadsafe,
* do not use the same PVar in multiple threads.
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess saving a pattern into a variable may also be dangerous:

PVar<Expr> x, y, z;
auto pat = max(x + y, z);
pat.Match(expr); // x + y may be already destroyed here?

If it's indeed the case, it's worth mentioning here as well.

Copy link
Member Author

Choose a reason for hiding this comment

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

We should confirm, my understanding is that it will be fine, because the temporary patterns will be destructed at the end of the current scope, so they are still valid here

Copy link
Member Author

Choose a reason for hiding this comment

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

I particular, the above code will translate to

{
  PVar<Expr> x, y, z; 
  auto t0 = x + y;
  auto pat = max(t0, z);
  pat.Match(expr);
} // t0 is alive until here

Copy link
Member Author

Choose a reason for hiding this comment

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

Also good point on the anti-pattern on bringing up the anti-pattern. I have updated comments to make people be aware of the potential misuse.

Copy link
Contributor

Choose a reason for hiding this comment

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

I tried this code (if I'm not mistaken it's close to how the pattern classes work):

#include <iostream>

struct A {
    A() { std::cout << "A()\n"; }
    ~A() { std::cout << "~A()\n"; }
};

struct C {
    C(const A& a) : a_(a) { std::cout << "C()\n"; }
    ~C() { std::cout << "~C()\n"; }
    void match() const { std::cout << "match\n"; }
    const A& a_;
};

int main() {
    auto pat = C(A());
    pat.match();
}

The output is (both gcc and clang):

A()
C()
~A()
match
~C()

So A is destroyed before the call to match.

Copy link
Member Author

Choose a reason for hiding this comment

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

Interesting, I didn't realize that was the case. Perhaps we need to do a deep construction by copy instead of by reference...

@tqchen
Copy link
Member Author

tqchen commented Feb 17, 2019

I have updated the code so that intermediate expression nest by value, @sgrechanik-h please check again

@@ -593,7 +632,8 @@ class PCallExpr :
}

private:
const std::tuple<const TArgs&...> args_;
typename detail::nested_tuple_type_helper<
std::tuple<TArgs...> >::Nested args_;
Copy link
Contributor

Choose a reason for hiding this comment

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

Won't something like std::tuple<typename TArgs::Nested...> work here? Otherwise lgtm.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks, this is something I don't know before :)

@tqchen tqchen merged commit 255c187 into apache:master Feb 18, 2019
@tqchen
Copy link
Member Author

tqchen commented Feb 18, 2019

Thanks, @ZihengJiang @sgrechanik-h @derisavi , this is now merged

@tqchen tqchen deleted the pattern branch February 18, 2019 23:33
wweic pushed a commit to neo-ai/tvm that referenced this pull request Feb 20, 2019
wweic pushed a commit to neo-ai/tvm that referenced this pull request Feb 20, 2019
@yzhliu yzhliu mentioned this pull request Mar 2, 2019
28 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants