Defining new top level operations

Are there any directions for this?

I need to define a new operation for a state machine language, the state machines have states and transitions. The idea was to model this with a top level operation at the same level as an std.func. The operation would model states and transitions (where states can can call std.func at state entry, exit and transitions.

What I am having a problem is to understand how I define a top level function like operation, the tutorial is a bit sparse on this topic. Is this even possible using ODS, or do I need to write custom operation classes for this?

Hi Mattias! glad to hear someone else is interested in such a dialect. I’d love to kibitz on this.

FuncOp is largely implemented as a c++ class (lib/IR/Function.cpp), and it’s relatively easy to copy+paste this into a new class and start hacking away. However, I don’t think there’s alot of guidance on doing this as most people are working on dialects at the next level down and although some aspects of the FuncOp are abstracted in the FunctionLike Interface, it’s still somewhat ‘special’ as it lives outside of even the standard dialect.

My thinking on this was to represent each state as a basic block and first focus on a new terminator which it like CondBr, but would accept a list of guards, actions, and next state symbols.

Hi Mattias,

MLIR doesn’t really have the concept of a “top-level” op. Any operation you define can be placed within a ModuleOp, it is up to your operations semantics if that is correct or not. For example, look at the definition of llvm.global(https://github.com/llvm/llvm-project/blob/a7325298e1f311b383b8ce5ba8e2d3698fef472a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td#L587). The definition is no different from that of any other operation. This is why the tutorial is a bit lax on this(although we could likely mention it).

A prior art here from IREE is vm.func and vm.import.

To make it very explicit on what River mentioned above, this alone is a valid MLIR input:

"my.top_level_op"() : () -> ()

If you feed it to mlir-opt you will get out:

module {
  "my.top_level_op"() : () -> ()
}

And this work side-by-side with functions, the following is also valid:

"my.top_level_op"() : () -> ()
func @foo() {
  return
}

Sorry, took some time to get back on this.

In any case, the idea with a special branching instruction for state transitions makes a lot of sense. I am currently leaning on the transition ops having a custom formatter, something like (not proper MLIR syntax), but anyway:

foo.transition %object, {/*state exit actions*/},
    trigger-attributes, {/*guard block*/}, {/* transit actions*/}, ^targetstate(i1 0, %object),
    ...

The boolean parameter to the targetsstate block would be used to pass a flag whether we are doing a transition that do not change state. Such operations should not execute the actions for state entry. So each state essentially will be a block with a conditional instruction (checking the self-transition flag) and executing the entry block if it is a transition from another state only.