Ported in Polly the library for pattern matching on schedule tree available at https://github.com/PollyLabs/islutils.
Details
Diff Detail
Event Timeline
Thanks for the work,
Matching the MemoryAcesses would be interesting as well.
Consider adding some unittests.
I like the idea! The API needs to be polished a bit I think.
Some time back I prototyped an approach to this as well, but never got around to using/submitting it. I'll paste my code later, since I liked the API at that time.
This was how my API worked:
#include "isl/isl-noexceptions.h" #include <vector> enum class schedule_node_type { error = -1, band, context, domain, expansion, extension, filter, leaf, guard, mark, sequence, set, any }; struct Matcher { /* implicit */ Matcher(schedule_node_type T) : MatchedType(T) {} Matcher(schedule_node_type T, Matcher &&Inner) : MatchedType(T), InnerMatchers({Inner}) {} template <typename... MatcherTs> Matcher(schedule_node_type T, MatcherTs &&... Matchers) : Matcher(T, {std::forward<MatcherTs>(Matchers)...}) {} Matcher(schedule_node_type T, std::initializer_list<Matcher> Inner) : MatchedType(T), InnerMatchers(Inner) {} isl::schedule_node match(); template <typename Callable> isl::schedule_node match(Callable &&); schedule_node_type MatchedType; std::vector<Matcher> InnerMatchers; }; int main() { { Matcher M{schedule_node_type::domain, {schedule_node_type::sequence, {schedule_node_type::filter, {schedule_node_type::filter}}, {schedule_node_type::filter, {schedule_node_type::filter}}}}; } }
I prefer not using macros. With this implementation you get only one callback per full match, not per matched subtree, but I felt like this is all I needed.
include/polly/ScheduleOptimizer.h | ||
---|---|---|
18 | I would really appreciate if you mention the author(s) of the original code. It is distributed under an MIT license which, although open source, requires the license and copyright information to be preserved if you distribute copies or substantial portions of the code... I'm fine contributing it if you ask me explicitly, but please don't claim authorship on the code you did not write. |
@ftynse, the source is referenced in the patch summary. If we commit such a patch to Polly, we must make we have your permission to contribute it under the LLVM license and obviously need to acknowledge the source. For now, I asked Lorenzo to push the code as RFC to allow others to see how it would be used. This already sparked a comment from Philip and will likely spark further discussions on how to extend the interface. Thanks for commenting.
I am not sure where you would prefer to have discussions on such a matcher. Probably an (organized?) mix of phabricator here and the github repo where you pushed the API could work.
Matching the MemoryAcesses would be interesting as well.
Could you describe which properties you want to match?
This was how my API worked:
I don't know when and how mainline isl will provide maintained C++ bindings, but the idea there is that enum isl_schedule_node_type will not be visible from C++. This is essentially a trick to have "inheritance" on node types in C. Practically, isl is going towards individual node types being specific classes that inherit from the generic schedule_node one. They are likely to feature isa<>-style interface. We can declare a local enum and use it to construct matchers though. The trade-off is that the matchers will not scale to non-structural properties. If we something closer to AST matchers, we will eventually have structure, traversal and property matchers, which are better off with functions or object constructors.
I prefer not using macros.
Between macros and almost identical copy-pasted code, I chose macros. Arguably, it can be rewritten with more templates where the almost-identical part is a one-liner. If the inheritance-based interface to trees is provided, we can think of something like
match<isl::schedule_node_domain>( match<isl::schedule_node_context>( match<isl::schedule_node_band>()))
although it does look more verbose.
With this implementation you get only one callback per full match, not per matched subtree, but I felt like this is all I needed.
The callbacks are intended to control the matching itself, not for actions on subtrees. Although one can use them for actions, I would discourage that. In practice, I often needed to match more than just a node type: filters involving specific statements, mapping to gpu blocks, marks with specific ids, etc. While it is possible to first collect all subtrees with the matching structures, and then filter them for the desired properties, it looks easier to not match such subtrees in the first place. When the desired properties are well-defined, one can build a higher-level API around these callbacks, like
auto isKernel = [](isl::schedule_node mark) { return mark.node_mark_get_id() == isl::id(mark.get_ctx(), "kernel"); }; auto matcher = mark(isKernel, context( band()));
or even
template <typename... Args> Matcher kernel(Args... args) { return mark(isKernel, args...); } auto matcher = kernel( conext( band()));
include/polly/ScheduleOptimizer.h | ||
---|---|---|
50 | It depends on what you need exactly. Just one node without specifying a type? Does that mean that the node _does_ exist or _may_ exist? Do you want to skip over multiple levels of the tree? Do you want to skip over multiple siblings? One can go shell-like wildcard way with ? and *. Or some flavor of regular expressions, but this quickly goes out of hand. I'd like to see examples of what needs to be expressed before taking a decision. My feeling after using different flavors of schedule trees is that you probably need some traversal matchers like hasSibling or hasDescendant | |
53 | set is actually an tricky one, because it explicitly says the order of children does not matter. This implementation does not account for that, not sure if it should | |
include/polly/matchers-inl.h | ||
13 | about what? | |
lib/Transform/ScheduleOptimizer.cpp | ||
1713 | The idea is to use a scoped using namespace to make it less verbose. Optionally, add trailing comments to tell clang-format to leave the formatting alone and reflect the tree structure. c++ { using namespace matchers; auto matcher = domain( // d sequence( // s filter( // f band()), // b filter( // f band()))); // b } |
I don't know when and how mainline isl will provide maintained C++ bindings, but the idea there is that enum isl_schedule_node_type will not be visible from C++. This is essentially a trick to have "inheritance" on node types in C. Practically, isl is going towards individual node types being specific classes that inherit from the generic schedule_node one. They are likely to feature isa<>-style interface. We can declare a local enum and use it to construct matchers though.
Is there a roadmap or RFC for that? I think that's a great idea.
The trade-off is that the matchers will not scale to non-structural properties. If we something closer to AST matchers, we will eventually have structure, traversal and property matchers, which are better off with functions or object constructors.
I'm not sure if we're going to need something as powerful as AST matchers. I usually prefer using a low-tech solution first and upgrade once I know with certainty what I actually need out of my system.
Between macros and almost identical copy-pasted code, I chose macros.
This wasn't criticism about your use of macros, but that the code requires macros.
The callbacks are intended to control the matching itself, not for actions on subtrees. Although one can use them for actions, I would discourage that.
This absolutely makes sense, but I didn't get that from the original summary. This then also satisfies Micheal's wish for a mechanism to match memory patterns. You can just do that in the callbacks.
My interface above can be easily extended to also incorporate Callbacks in the constructor. I don't have a categorical aversion against the interface proposed in the RFC, but I find the enum-based API much more readable.
For now, I asked Lorenzo to push the code as RFC to allow others to see how it would be used. This already sparked a comment from Philip and will likely spark further discussions on how to extend the interface.
It's better if we don't play the relay game and have people send comments on my code to me.
I am not sure where you would prefer to have discussions on such a matcher. Probably an (organized?) mix of phabricator here and the github repo where you pushed the API could work.
Absolutely not a mix of two things. In this case, we would spread out information and keep repeating the same things over and over again. Whatever going into my inbox (that is, CCing me directly) is fine.
Is there a roadmap or RFC for that? I think that's a great idea.
No, I don't think there is anything like that in isl.
I'm not sure if we're going to need something as powerful as AST matchers. I usually prefer using a low-tech solution first and upgrade once I know with certainty what I actually need out of my system.
We agree on that. If somebody has concrete usecases now, that would be helpful though.
include/polly/ScheduleOptimizer.h | ||
---|---|---|
50 | Hi, Indeed hasSibling or hasDescendant would be really useful. In the case of matrix-multiplication suggested by Michael you may, for example, have two different schedule trees: -Domain For the simple case without initialization or -Domain For the case with initialization. A possible matchers would look like as Domain(hasDescendant(Band())). Of course, band should have specific properties related to the matmul operation. Do you already have in mind a possible implementation for these traversal matchers? |
include/polly/ScheduleOptimizer.h | ||
---|---|---|
50 | I do have a possible implementation in mind. What I don't see is how to make it usable in this case. With a pure structural matcher, domain( band(isMatMul)) you have a guaranteed structure in case of a match. That is, you can write auto matcher = domain(band([](isl::schedule_node n) { return true; /*check if matmul*/ })); if (ScheduleNodeMatcher::isMatching(matcher, node)) { auto n = node.get_child(0); // guaranteed to produce a non-null node // n is guaranteed to be a band node here n.band_tile(...); // no need for extra cheks } With traversals, you don't get such guarantee: the descendant you are interested in may be arbitrarily deep and you need to somehow reach it to do a transformation. One way to go would be to return some collection of nodes that matched with a predefined structure. Another possibility would be to do that transformation in the callback passed to the matcher, but I'm not convinced it is the right approach, I think matchers are for finding stuff. In this particular example, I don't see why do you want to match the domain... It's always at the root anyway. But you seem to need to anchor the matcher at leafs. |
I would really appreciate if you mention the author(s) of the original code. It is distributed under an MIT license which, although open source, requires the license and copyright information to be preserved if you distribute copies or substantial portions of the code... I'm fine contributing it if you ask me explicitly, but please don't claim authorship on the code you did not write.