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

[Unity][Transform] Improved canonicalization of non-dataflow Var #15941

Conversation

Lunderberg
Copy link
Contributor

Prior to this commit, relax.transform.CanonicalizeBindings removed trivial bindings var_y = var_x where a var_y: relax.DataflowVar and var_x: relax.Var, but did not remove trivial bindings when var_y: relax.Var and var_x: relax.DataflowVar. This was to avoid invalid use of a relax.DataflowVar outside of a dataflow block.

This commit updates CanonicalizeBindings to handle this type of binding as well. To ensure that no relax.DataflowVar instances are used outside of a dataflow block, this is done by replacing var_y: relax.DataflowVar at its point of definition, instead of replacing var_x: relax.Var at its point of use.

This commit also canonicalizes relax.Var definitions to relax.DataflowVar, if the binding occurs within a dataflow block, and the variable is never used outside of a dataflow block.

@Lunderberg Lunderberg force-pushed the unity_canonicalize_var_to_dataflowvar_inside_dataflowblock branch 2 times, most recently from 86c3f0e to ddf30fb Compare October 17, 2023 19:28
@Lunderberg Lunderberg force-pushed the unity_canonicalize_var_to_dataflowvar_inside_dataflowblock branch from c673f16 to b3f0a94 Compare October 20, 2023 17:12
Prior to this commit, `relax.transform.CanonicalizeBindings` removed
trivial bindings `var_y = var_x` where a `var_y: relax.DataflowVar`
and `var_x: relax.Var`, but did not remove trivial bindings when
`var_y: relax.Var` and `var_x: relax.DataflowVar`.  This was to avoid
invalid use of a `relax.DataflowVar` outside of a dataflow block.

This commit updates `CanonicalizeBindings` to handle this type of
binding as well.  To ensure that no `relax.DataflowVar` instances are
used outside of a dataflow block, this is done by replacing `var_y:
relax.DataflowVar` at its point of definition, instead of replacing
`var_x: relax.Var` at its point of use.

This commit also canonicalizes `relax.Var` definitions to
`relax.DataflowVar`, if the binding occurs within a dataflow block,
and the variable is never used outside of a dataflow block.
@Lunderberg Lunderberg force-pushed the unity_canonicalize_var_to_dataflowvar_inside_dataflowblock branch from 5ada41f to a488ac4 Compare October 24, 2023 19:29
@Lunderberg
Copy link
Contributor Author

Rebased onto unity head and resolved conflicts with the #15791.

Copy link
Contributor

@slyubomirsky slyubomirsky left a comment

Choose a reason for hiding this comment

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

This is an excellent refactor and is very well documented. I made a couple of comments that I think would be worth having responses to before merging, but I think this leaves the transformation in great shape.

value = ptr->value;
} else if (auto ptr = binding.as<MatchCastNode>()) {
has_same_struct_info =
StructuralEqual()(GetStructInfo(binding->var), GetStructInfo(ptr->value));
Copy link
Contributor

Choose a reason for hiding this comment

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

Might we want to consider using the arithmetic solver to check if shapes are statically provable to be equal? The struct info can be equivalent without being structurally equal. (We should factor out that checking into a utility function if it isn't already, as this comes up a lot.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'd like to do so, yes, though I think that would be best isolated in an independent PR. Here, the check for having identical struct info is the same check as was done previously in the CanCanonicalizeVar method, and was kept the same in order to minimize the extent of changes.

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay. We can revisit this issue separately (I bet there are other passes where we're checking structural equality of struct info instead of static equivalence).

Comment on lines 153 to 154
while (true) {
if (auto tuple_var = tuple.as<Var>()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Might it be cleaner to have while (auto* tuple_var = tuple.as<VarNode>()) { as the condition? I believe this should work and it would be correct.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed, and updated.

Comment on lines 66 to 76
while (true) {
if (auto it = visitor.trivial_bindings_.find(bound_to);
it != visitor.trivial_bindings_.end()) {
// This may be a trivial binding into a trivial binding. In
// that case, unwrap the bindings until we find the earliest
// non-trivial binding.
bound_to = it->second;
} else {
break;
}
}
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 this might work instead and might be clearer:

for (auto it = visitor.trivial_bindings_.find(bound_to);
    it != visitor.trivial_bindings_.end();
    it = visitor.trivial_bindings_.find(bound_to)) {
  bound_to = it->second;
} 

(Or the first clause can be moved to before the loop.)

Matter of preference, perhaps, if you consider it simpler than the above.

Copy link
Contributor

Choose a reason for hiding this comment

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

The same applies to the loop below as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've gone back and forth on this one a bit. On the one hand, the current version has an extra level of nesting, but on the other hand, using for would require repeating the .find clause. In a perfect world, we would have while(init_statement; condition), but unfortunately (according to this stackoverflow post), that variation was left out for sake of simplicity.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I made another update to use Map<Id, Expr> instead of std::unordered_map<Id, Expr>, so that the loops could be rewritten as while(auto opt = lookup.Get(key)), avoiding the while(true) altogether.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I checked to see if while (stmt1; condition) would work and it doesn't. I'm not against having any while(true), but avoiding a level of nesting is usually a good change.

Copy link
Contributor

@slyubomirsky slyubomirsky left a comment

Choose a reason for hiding this comment

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

Thank you for addressing the comments.

@Lunderberg
Copy link
Contributor Author

No problem!

I'm going to merge this in, as it resolves a few test failures in unity head that resulted from a conflict between PR#15791 and PR#15904. Each PR passed CI on its own, but the combination of the two changes caused a few minor test failures. Since the test failures were resolved in this PR, that avoids needing to make a hotfix PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants