Conversation
|
I'm updating this to "ready for review", even though I'm still not entirely happy with it, because after some reflection, I think it's still a net improvement. |
|
Monday |
dsharletg
left a comment
There was a problem hiding this comment.
I had hesitated to make ops immutable, because this design will require more memory allocations, which might be an issue on DSPs with weak scalar performance. But I also think we should deal with that later if it's an issue, rather than make the IR harder to work with now.
| } | ||
| OpPtr op_ptr(int i) { | ||
| OpPtr result = nullptr; | ||
| std::swap(ops_[i], result); |
There was a problem hiding this comment.
What's going on here? This looks suspicious... it looks like a getter that takes the op out of the group?
There was a problem hiding this comment.
Yep -- this is used by OpMutator::visit to mutate an OpGroup. It needs to take ownership of the unique_ptr so it can grant ownership to the new OpGroup. (The implementation with swap is probably overkill; I wanted to be sure I wasn't leaving the old OpGroup in an inconsistent state, but that's probably not essential.)
There was a problem hiding this comment.
Should this be renamed then? take_op or something?
|
|
||
| void accept(OpVisitor *v) override; | ||
| void accept(OpVisitor *v) const override; | ||
| OpPtr mutate(OpMutator *m, OpPtr op) override; |
There was a problem hiding this comment.
Comment why this takes an op as a parameter
There was a problem hiding this comment.
I'll comment here for starters (and move into code if that's what you want)...
The problem here is that we are trying to implement double-dispatch via double-virtual-method calls (in the same fashion that Halide's similar classes do); the gotcha is that we also want to transfer ownership of 'this' into the receiving method call (since it will ultimately either keep-or-replace it).
In Halide's IR, the IntrusivePtr approach allows us to transparently 'recover' a refcounted Expr from a specific pointer (e.g. const Call * implicitly converts to Expr and maintains the refcount correctly); with unique_ptr, there isn't any such way to do this, so we have to pass a redundant argument. It's not pretty, but the generated code is efficient.
For the record, I considered moving Op to use shared_ptr instead, which can sorta-kinda do the same sort of recovery via std::enable_shared_from_this, but it felt like it required too much obscure special knowledge to be friendly (e.g. explicit calls to the shared_from_this() method). I also considered moving Op to just use a cut-down variant of IntrusivePtr, but that would have been a pretty major chunk of code churn.
There was a problem hiding this comment.
I guess more specifically, I just think there should be a brief comment here on how to actually use this function: what the arguments mean.
There was a problem hiding this comment.
Added some comments, but also overhauled the mutate()/accept()/visit() methods some to be cleaner and to make improper calls hard-to-impossible, PTAL
| public: | ||
| // Go in reverse order so removing a dead op enables earlier ops to be seen as dead. | ||
| explicit RemoveDeadOps(const Op *root) | ||
| : OpMutator(OpMutator::Reverse) { |
There was a problem hiding this comment.
Is the reverse order a common use case? I am wondering if RemoveDeadOps should just override the default behavior rather than building it into the base class.
There was a problem hiding this comment.
No, it has exactly one use case currently. I initially did what you suggest here, but the implementation of OpMutator::visit ended up feeling ticklish enough to get right that I thought it more prudent to encapsulate it all in one spot, rather than encourage possibly-wrong code replication. (I don't feel strongly about this, though.)
Yeah, it's a fair concern. It may be that the Halide IRMutator pattern isn't the ideal one for what we want to do. But it does seem like a more tractable way to get IR that is reliably clean -- my hope here is that if we find the extra allocations to be problematic, we can reintroduce mutability on a selective basis. |
(I'm opening this as a draft because I'm not sure if the current design is acceptable; in particular, the implementation of
OpMutatoris ugly in ways that bother me a lot. I'm nevertheless tagging some people for 'review' because I'd like opinions on whether the ugliness is a dealbreaker.)This PR attempts to refactor the Hannk IR (and transforms) to be a bit more principled and less error-prone. In particular:
mutableannotations.OpMutator, in which you construct a new, replacement Op when changes are needed.Using this, I rewrote all the transforms using OpMutator (and added a new
flatten_groupspass); I also added a variant of the model-validator code from #6317. Everything in my test suite passes, and I haven't seen any regression in optimization (I'll post performance numbers as a followup comment).The reason I'm hesitant to move forward with this is that the implementation of
OpMutatoris... ugly, due to the necessity of transferringunique_ptrownership. I'm convinced that it's actually legal, safe C++, but I don't like it. (Nevertheless, I'd appreciate comments.)