[RFC] Rename LoopOps dialect to SCF (structured control flow)

I argue against this. I think that “loops”, “conditionals”, and “branches” are fundamentally different concepts and deserve to be in separate dialects.

  1. ‘branches’ represent unstructured control flow between basic blocks in a region. They are uniformly terminators of their containing basic block.
  2. “conditionals” represent structured control flow and typically contain multiple regions which are conditionally executed at most once.
  3. “loops” represent structured control flow and typically contain multiple regions which are executed repeatedly.

I think a “loop” dialect is highly intuitive for most everyone, representing #3. Earlier I proposed “branch” for #2, but this could be “conditional”.

I find it hard to argue that something is really fundamental to the MLIR ecosystem, hence my historical aversion to the standard dialect. Is br fundamental? Is execute_region?

“for” loops, with sequential, parallel and reduction flavors. “if” conditionals with and without return values. These sound pretty ubiquitous under imperative paradigm. (Which hints that if someone were to implement a LISP machine with MLIR, they wouldn’t use any of those, or br). These are used in all lowering passes available in core, i.e. both Linalg and Affine lower to these, which can further go to standard CFG or GPU kernels, or whatever.

This is discutable. Depending on the differences you care about… I would say for is “more different” from divf and from linalg.range than it is from if or br. The criterion I have been using for putting operations into this dialect since its inception is, as I already mentioned, the operation representing structured control flow. Both “if” and “for” are structured control flow. Now, if the criterion is the number of times the region executes, or another notion of the contrl flow kind, you do get a different split.

If we proceed with this separation, which looks like having three dialects for control flow (loops, conditionals and branches), should we do the same for other kinds of ops in “standard”? Should we have “arithmetic ops” and “memory ops”, “integer arithmetic ops” + “float arithmetic ops”, “low-precedence arithmetic ops” (add, sub) + “high-precedence arithmetic ops” (mul, div, rem)? Pushing this to the absurd, we can have one dialect per operation because ultimately there exists a difference between any pair of operations. Why loops and conditionals are so special that they deserve a dialect each?

If the dialect contained only loops. I think the current dialect is misnamed because, when introduced originally, it only contained the “for” operation because that was the only thing we needed. And I basically got tired of repeating that it should be called “control flow” because it will have other control flow structure.

Why are we moving std.br, std.cond_br into this? If you are moving them because they are related to control flow, why are we leaving std.return back? std.br/cond_br/return are all “flat” (no regions) and fundamental enough, and I strongly feel they should stay in the std dialect. If we don’t name this dialect region, I’d vote for moving all of LoopOps back into the std dialect.

I’m in favor of splitting std, I’d actually like it to go away entirely. I don’t at which granularity to look at the problem but separating concepts (memory vs arithmetic for instance is already a possible good discriminant). Ultimately I see dialect similarly to libraries: when we refactor and organize libraries we try to keep together things that form a single unit. We don’t have a single function per library because it increases the mental cost of what we manipulate (a library is a single component, hopefully coherent) as well as the actual boiler plate (build file, includes, etc.).

1 Like

If we aren’t sure of what the separation is going to be, that’s good enough reason for it to stay in the same dialect. While more libraries is better, just over-separating into dialects based on concepts would be nothing but random directory switching pain while developing and having to think of the right name prefixes for the ops while writing test cases and having to bikeshed on and come up with awkward/confusing names for the dialects themselves. By keeping it in std for now, all this discussion goes away.

+1 for this. It does make the standard dialect more load bearing, which is not desirable, but in this context, this seems better to me.FWIW, region dialect would be super confusing. Statements like “region of a region.op” would be unparsable for someone new to MLIR.

My question was intended to be simple: lots of people are currently using the branch instructions in the standard dialect for lots of things, and they haven’t changed since MLIR was introduced.

I’m asking if LoopOps is widely used or not. My understanding is that some researchy things are starting to use it, but these are all under active evolution.

-Chris

This is a strange argument to me: not being sure if it should be in 3 pieces or 5 pieces for example, or not knowing right now the exact split point does not seem like a reason to not look into it…

Because it (used to be) a different kind of control flow connected to functions and calls, rather than to intra-procedural control flow (this is also why I didn’t like the generalization of std.return).

+1 but it’s a different discussion, apparently even more complex than this one.

If we talk about directly working on IR containing loop.*, we have at least Linalg and its users (e.g., IREE), XLA LHLO that lowers to these, GPU mapping that starts from these, Stencil optimizer (presented at ODM a couple of month ago) lowers to these, probably more external users I’m not aware of.

If we talk about indirect users, both affine and linalg lower to loopops, which in turn lower to std. So anybody who doesn’t target std directly and has some kind of structured loops is likely relying on these as much as they do on std.

This doesn’t sound meaningful at all – in what way are br and cond_br closer to for and if than they are to return? There is barely any structure to br/cond_br, and there are very few things that are more unstructured than those (none in MLIR).

If you read the thread again, you will likely notice that we discussed having a “control flow” dialect instead of having a “structured control flow” dialect.

This is why my original proposal was to just rename the misnamed dialect so that we don’t get into endless discussions about essentially personal preferences.

IIUC, the loop dialect tries to ensure creating a reducible loop by avoiding br and multiple blocks. I think that this is because to make optimizations easy for a loop. “structured control flow” seems to say like this.

How about “rloop” (reducible loop) or others instead of just “loop” dialect?

The original goal for renaming was “that the dialect contains “if/else” operation, which is obviously not a loop, and loop.if just looks confusing”, it isn’t clear to me that “rloop” would be helping to address this aspect.

I would like to make some progress here. Any op redistribution across dialects seems contentious (nothing unexpected for me), so I reiterate my original proposal: rename the Loop dialect without changing anything else.

Naming options:

  • scf (stands for structured control flow) - this was the original intention of the dialect
  • control_region - derived from @bondhugula’s proposition but highlights that the dialect contains (mainly) region-carrying ops related to control flow.

+1 on the rename (with some extra text to make discourse happy)

Renaming to scf works for me.

Sounds good to rename it without moving anything. scf is definitely better than loop. control_region sounds a bit awkward in many ways.

scf is fine with me, thanks!

I am OK with it.

Tatiana

+1 to scf. Thanks!