-
Notifications
You must be signed in to change notification settings - Fork 3.5k
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
[Unity][Transform] Improved canonicalization of non-dataflow Var #15941
Conversation
86c3f0e
to
ddf30fb
Compare
c673f16
to
b3f0a94
Compare
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.
5ada41f
to
a488ac4
Compare
Rebased onto unity head and resolved conflicts with the #15791. |
There was a problem hiding this 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)); |
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
while (true) { | ||
if (auto tuple_var = tuple.as<Var>()) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, and updated.
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; | ||
} | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this 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.
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. |
Prior to this commit,
relax.transform.CanonicalizeBindings
removed trivial bindingsvar_y = var_x
where avar_y: relax.DataflowVar
andvar_x: relax.Var
, but did not remove trivial bindings whenvar_y: relax.Var
andvar_x: relax.DataflowVar
. This was to avoid invalid use of arelax.DataflowVar
outside of a dataflow block.This commit updates
CanonicalizeBindings
to handle this type of binding as well. To ensure that norelax.DataflowVar
instances are used outside of a dataflow block, this is done by replacingvar_y: relax.DataflowVar
at its point of definition, instead of replacingvar_x: relax.Var
at its point of use.This commit also canonicalizes
relax.Var
definitions torelax.DataflowVar
, if the binding occurs within a dataflow block, and the variable is never used outside of a dataflow block.