Yep, agreed. I think we need to define the expectations. The base expectations of an MLIR pass is that it doesn’t touch things it doesn’t know - it can be non-functional but should be inert in the face of weird things. I agree with you that this is mostly the case for the current tf executor world (but see below).
Ok, but that isn’t safe with the current MLIR infra in general: your dialect is enforcing structural constraints on the SSA graph that are not value flow dependencies. Splitting into blocks with bb arguments breaks this. I can hear you about to say that this is a single block region - but single block regions are also a structural constraint on the IR.
What this means is that there are classes of passes that will break in invariants expected of the dialect - for example, an outlining pass cannot assume that operations can be arbitrarily hoisted out of a function (tf executor is not alone here - dynamic allocas and builtinframeaddress in C have the same property etc). Similarly, a de-predication pass that turns a select
into CFG has to know whether the select is in a single-block region (like an affine.if body) and make sure not to touch it, but know that it can change std.func body, etc.
While it is theoretically possible to describe every possible invariant using op interfaces, this isn’t really practical, and would just lead to bugs where someone isn’t checking the dozens or hundreds of invariant they have. I think it is far more reasonable to say that certain passes should not be used on certain kind of code, or give those passes granular interfaces that allow them to be disabled on code that is scary.
Yes, I agree, this is the crux of the issue. The question is how do we model this in a way that has low impact on the compiler complexity (no, we should not duplicate Operation
! :-). But that makes it easy for passes to be disabled/updated when and if they are found to be buggy on a cyclic dialect. I personally don’t think that code review or “perfect foresight” is possible here. I think it is fine for people to just write lots of code at scale, and people who are assembling compilers will have to deal with the integration problems when they try to do something new and unexpected (like running an LICM pass on a netlist, boggle)
One thing that I think we may be misunderstanding though - you seem to think that many passes will crash when given cyclic use-def edges. Why do you think this? I can’t think of an example in LLVM that walks up or down use-def edges in unbounded ways. Passes end up getting limited for compile time reasons (such walks end up being exponential) including to amazingly silly constants like “6”.
-Chris