This adds support for native PDL (and PDLL) C++ constraints to return results.
This is useful for situations where a pattern checks for certain constraints of multiple interdependent attributes and computes a new attribute value based on them. Currently, for such an example it is required to escape to C++ during matching to perform the check and after a successful match again escape to native C++ to perform the computation during the rewriting part of the pattern.
With this work we can do the computation in C++ during matching and use the result in the rewriting part of the pattern. Effectively this enables a choice in the trade-off of memory consumption during matching vs recomputation of values.
This is an example of a situation where this is useful: We have two operations with certain attributes that have interdependent constraints. For instance attr_foo: one_of [0, 2, 4, 8], attr_bar: one_of [0, 2, 4, 8] and attr_foo == attr_bar. The pattern should only match if all conditions are true. The new operation should be created with a new attribute which is computed from the two matched attributes e.g. attr_baz = attr_foo * attr_bar. For the check we already escape to native C++ and have all values at hand so it makes sense to directly compute the new attribute value as well:
PDLL Constraint checkAndCompute(attr0: Attr, attr1: Attr) -> Attr; Pattern example with benefit(1) { let foo = op<test.foo>() {attr = attr_foo : Attr}; let bar = op<test.bar>(foo) {attr = attr_bar : Attr}; let attr_baz = checkAndCompute(attr_foo, attr_bar); rewrite bar with { let baz = op<test.baz> {attr=attr_baz}; replace bar with baz; }; }
To achieve this the following notable changes were necessary:
PDLL
- Remove check in PDLL parser that prevented native constraints from returning results
PDL
- Change PDL definition of pdl.apply_native_constraint to allow variadic results
PDL_interp
- Change PDL_interp definition of pdl_interp.apply_constraint to allow variadic results
PDLToPDLInterp Pass:
The input to the pass is an arbitrary number of PDL patterns. The pass collects the predicates
that are required to match all of the pdl patterns and establishes an ordering that allows
creation of a single efficient matcher function to match all of them. Values that are matched and
possibly used in the rewriting part of a pattern are represented as positions. This allows fusion
and thus reusing a single position for multiple matching patterns.
Accordingly, we introduce ConstraintPosition, which records the type and index of the result of the constraint.
The problem is for the corresponding value to be used in the rewriting part of a pattern it has
to be an input to the pdl_interp.record_match operation, which is generated early during the pass
such that its surrounding block can be referred to by branching operations. In consequence the value has
to be materialized after the original pdl.apply_native_constraint has been deleted but before
we get the chance to generate the corresponding pdl_interp.apply_constraint operation.
We solve this by emitting a placeholder value when a ConstraintPosition is evaluated.
These placeholder values (due to fusion there may be multiple for one constraint result) are
replaced later when the actual pdl_interp.apply_constraint operation is created.
Bytecode generator and interpreter:
Constraint functions which return results have a different type compared to existing constraint functions.
They have the same type as native rewrite functions and hence are registered as rewrite functions.
Other options:
- Have a specific registration bucket for constraints with results
- Change the type for all native constraints to include (possibly empty) results and store all natvie constraints in the same registration bucket. (It is open how this interacts with the convenience PDL Constraint Builders, possibly something for a follow-up PR)
Please add an example of apply_native_constraint that returns results