Standard Dialect: The Final Chapter

Looking at some of the non-contentious (at least from observation) things from above, uploaded D118648 to move SelectOp to Arithmetic. Also sent out D118654 for removing the unit support from ConstantOp.

– River

1 Like

Yes, so far I’ve been playing a bit of devil’s advocate to see what people’s perceptions were. It’s interesting to consider if we added FuncOp today, instead of it being a builtin thing from the beginning. On one hand, it’s a special construct, on the other it’s just fancy control flow. I think there are two main paths (which can be tweaked) that we can discuss and consider going down:

FuncOp(and some friends) in SCF:

Basically mentioned above, we have some function-related operations that could be slotted into the CF dialect with the rest going into SCF.

Control Flow (new dialect):

  • CallOp, CallIndirectOp

SCF:

  • ConstantOp (function reference), FuncOp, ReturnOp

New dialect

If we truly feel that Func is such a special construct to keep separately, we need to decide what kind of dialect could/should hold it. There are different ways we could do this:

Everything func related in a new dialect

  • ConstantOp (function reference), FuncOp, ReturnOp, CallOp, CallIndirectOp

We could just shove everything currently related to FuncOp in a new dialect.

Some func related stuff in a new dialect, some elsewhere

Keying off of previous points, we could move some CallOp/CallIndirectOp (with some slight generalization) to a ControlFlow dialect; with the rest going to a new Func dialect.

Control Flow (new dialect):

  • CallOp, CallIndirectOp

New Func specific dialect:

  • ConstantOp (function reference), FuncOp, ReturnOp

General thoughts

I can see pros+cons to all of these as potential paths to take. There are definite benefits to a new dialect though. SCF in its current state is definitely not as general as the dialect name/description aspires to be (for better or worse), which kind of furthers the divide that FuncOp doesn’t really fit well. Also from a migration point-of-view, having a new dialect would definitely be easier in many respects. Realistically, this is a good opportunity to discuss where FuncOp really fits within the ecosystem (cough not builtin cough) and how it is positioned.

– River

Sorry for missing this thread, apparently gmail decided that llvm forums are spam, whodathought. I super endorse narrowing std.constant and moving select to arith. Both are great moves.

While I can see this, it doesn’t really seem right to me. “func” and related constructs are more of a default container than they are related to specific forms of control flow. The later should compose with other sorts of containers, e.g. a Fortran fir.procedure or whatever. Putting func into the scf dialect wouldn’t force the two to be used together, but I don’t see how they are conceptually aligned.

I’m not sure what the practical concerns are here, but from a theoretical perspective, this seems cleaner to me. I’d also trow in the builtin ModuleOp as well - it should eventually devolve into an artifact of convenience, not something that everyone uses.

The problem with these operators is that I’m not sure what they really are - they’re almost “convenience” operations that MLIR provides that are useful for bootstrapping things when you don’t care about nailing the semantics for containers down yet. That isn’t a bad thing btw, I do think there is utility in MLIR providing such a thing, but we’ve moved a long way in the MLIR design from when these were introduced - I don’t think they are nearly as primal as they used to be.

All “real” (whatever that means :slight_smile: compilers should define their own containers, because you want your own verification logic etc and to control your world.

Yes, I think this is also a very practical realization. I think there is a good argument for merging func and scf together into something (and rename it to not be called “scf”) in that scf is also mostly a convenience dialect at this point. It is useful for bootstrapping, research and experimentation, but has turned into more "kitchen sink"y than it might be.

-Chris

I’m fine with having a new dialect for FuncOp (and associated functions like Return, Call, …).

We should likely look at ModuleOp separately in another step than all this: it is a bit “special” right now and it’s worth more consideration.

I really don’t understand what warrants this comment right now: scf looks far from a “kitchen sink” to me. It has for, if, and while as the most basic block of structured control flow as far as I can understand it. The only things that might be seen as borderline there could be the parallel multi-loop construct (and the associated support for reductions) as well as the execute_region. It would be a bit of a stretch to create another dialect for these when they aren’t clearly not belonging to “structured control-flow” either.
I’m not really sure how different would be a scf dialect if we were designing it from scratch today.
Anyway, this is a bit off-topic for this thread :slight_smile:

1 Like

Yeah, that one can wait until after we resolve the current situation. There are various special considerations there that I’d rather not pull into this discussion for now (killing Standard and moving FuncOp would already be monumental achievements).

– River

1 Like

Kitchen sink is the wrong phrase, sorry I just meant it has been stretched, and /looks/ general but isn’t really a general solution. I don’t want to dig deep into this, but I just mean things like the scf.while loop follows the pattern of the tf+xla loop constructs which don’t allow the “condition” clause to have additional live outs that are visible on the body, forcing recomputation for many loop structures. The way scf.reduce and scf.parallel are associated is very particular to specific use cases, even that scf.if forces the use of i1 specifically for the condition, but allows arbitrary types for live ins and live outs, making it more annoying for use by dialects that have more specific condition types (like a bool type).

In any case, I agree they are useful! It is just hard to make a general thing that serves many audiences well, and the more general a construct is, the more difficult it is to analyze and transform.

Actually no: it does not follow the XLA while loop and allows for arbitrary flow of values from the condition to the body, exactly for the reason you mention :slight_smile:
By the way: I recently implemented some canonicalization on the XLA while loop (around removing loop invariant value carried from iteration to iteration) and we had the same implemented in SCF, and the generality of the scf.while made the implementation much more complex! So there is definitely an interesting tradeoff here.

I think the same applies to cond_br for example right? It is hard to abstract entirely the type system, maybe with TypeInterface we could get there…

Another way to look at it as SCF constructs are almost always lowered to branches, functions are not. They persist until assembly levels. IMO, functions+calls can go to a separate dialect, or can live together with the rest of “assembly-level” control flow operations such as branches. The latter solution boils down to renaming whatever remains of “standard” to “control-flow”.

One other question that was left unanswered in the original splitting proposal is what happens with all the StandardToX and XToStandard conversions. I’m totally fine to ignore it here to reach convergence faster.

Just an additional reminder that SCF, as well as cond_br, largely perdate type interfaces and even custom types. We couldn’t have proposed a general solution to the problem we did not know existed. To me, the fact that SCF remained largely the same over the years means the exact opposite of it being “experimental”. I am happy to discuss this in a separate topic to avoid derailing the discussion here.

We discussed this a bit during the ODM today. I think the general semi-consensus is to have a new dialect containing the function constructs. From there, I think we need to fully decide what this dialect is supposed to represent (not just now, but into the future). There are a few options:

  • Functional like constructs, (potential proc dialect)?
  • Function + some other high level constructs?

Aside from that, there is still some desire to have CallOp/CallIndirectOp not be hardcoded on FuncOp, and be moved to (potentially) the Control Flow dialect.

I’d like to move the discussion towards the above (even though the concepts surrounding SCF are interesting), so that we can finish out this effort.

Thanks
– River

I’m sorry I missed the discussion today, River just poked me to suggest some names.

The design of ‘func’ has a bunch of assumptions in it: it allows a CFG, it has an imperative control flow nature to it (op execution is ordered by a program counter), etc. I’m still +1 on moving func out to its own dialect. If we do that, we should consider dialect names like “sw” (dual to “hw” in CIRCT) given that all these things are pretty generic to many different software systems, or maybe “imperative” given the model isn’t a dataflow graph.

I’m not as excited about the term “procedural” - I have old pascal hangups with that where procedural programming was against functional and higher order programming.

-Chris

If we’re getting to the name bike shed, we’re almost there :slight_smile:
I’m not convinced by “sw”: first because it is overly generic as a name (like std was) but also because “dataflow” vs “imperative” isn’t about “hw” vs “sw”: TensorFlow, XLA, and many other software project have a dataflow model.

We also touched on the “procedural” term because some folks were asking about higher order programming and pure functional construct indeed: but my take was that the current FuncOp abstraction is indeed not really intended to target this kind of abstraction and I would think that someone addressing these would design a dedicated dialect.

Keeping the ball rolling, sent out a review(D118966) for an initial ControlFlow dialect while we discuss final steps for FuncOp and friends.

– River

1 Like

The initial control flow dialect has landed, and we are quite literally nearing the finish line. The Standard dialect now only has 4 operations(!): CallOp, CallIndirectOp, ConstantOp, ReturnOp. We are now at the stage where we just need to rename the current standard dialect to whatever fits the FuncOp+friends dialect. Let’s discuss a destination for these and close off this chapter of MLIR!

There have been two main potential paths: a dialect focused around functional concepts; a dialect focused around more general high-level components (e.g. could conceptually contain Module).

If we intend to go the “function concepts” route, we could just go for functional. Such a dialect could conceptually grow modeling for things like “invoke”/exception handling, closures, etc.

(this is mostly a call for people who are better at naming than I am, which is a fairly low bar)

– River

1 Like

Yay! Almost there!

From up-thread:

  • -1 on “proc” or “procedural” for the same hangups from the Pascal era as mentioned.
  • builtin.func is actually a fairly specific thing (as mentioned, CFG-based, aligned directly with what can be represented by LLVM’s concept of such things). When I think of “functional”, I don’t think of builtin.func – it is something lower level and more specific than that. So -1 on that.
  • I am also partial to some moniker like sw as a shorthand for “the last 40 years of CFG-based, low-level, C-influenced representations that LLVM also embraced but no one bothered to name”. But by opening up such a name, we take what is likely a simple naming discussion and make it in to an open ended taxonomy discussion.
  • If we didn’t also have the llvm interop and a lot of ambiguity that goes with that name, I probably would have just called it an “llvm function” and left other concepts of function to more specific cases.

I’m trying to come up with something better…

In a functional dialect, I would expect to find lambdas and other combinators.

How about calling the dialect func? To have func.call, func.indirect_call, and func.constant. The function definition is a bit awkward as func.func (unless we keep the prefix-less format) though.

3 Likes

This looks the most reasonable to me. A tiny tweak:
func.funcfunc.function.

2 Likes

What about f? So we can have a UncOp what would read as f.unc :rofl:

I was going to to suggestion sticking with func but just calling it func.tion

This is cracking me up. If we don’t converge, I’m going to propose: ƑƲƝƇ :slight_smile:

What is this constant for?

Today’s std.func could just be func.constant (it’s constant of function type afterall if we consider functions values, so you can have ops producing functions and this is just a constant function value producing op :slight_smile: )

fn also a fun abbreviation (“fn.func not working” is a phrase that may have been uttered a few times before …). If seeming duplicate names is a concern, func.callabale to mirror call is an option (:paintbrush: :hut:)

1 Like