[Mlir-commits] [mlir] [MLIR] feat(mlir-tblgen): Add support for dialect interfaces (PR #170046)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Wed Dec 3 14:06:11 PST 2025


https://github.com/aidint updated https://github.com/llvm/llvm-project/pull/170046

>From 504bb5c0dd7ce2907938d5bb1d0649e56537d04a Mon Sep 17 00:00:00 2001
From: aidint <at.aidin at gmail.com>
Date: Sun, 30 Nov 2025 19:40:51 +0100
Subject: [PATCH 1/4] feat(mlir-tblgen): Add support for dialect interfaces

---
 mlir/include/mlir/IR/Interfaces.td            |   5 +
 mlir/include/mlir/TableGen/Interfaces.h       |   7 +
 mlir/lib/TableGen/Interfaces.cpp              |   8 +
 mlir/test/mlir-tblgen/dialect-interface.td    |  66 +++++++
 mlir/tools/mlir-tblgen/CMakeLists.txt         |   1 +
 .../mlir-tblgen/DialectInterfacesGen.cpp      | 176 ++++++++++++++++++
 6 files changed, 263 insertions(+)
 create mode 100644 mlir/test/mlir-tblgen/dialect-interface.td
 create mode 100644 mlir/tools/mlir-tblgen/DialectInterfacesGen.cpp

diff --git a/mlir/include/mlir/IR/Interfaces.td b/mlir/include/mlir/IR/Interfaces.td
index 0cbe3fa25c9e7..746ad3408f424 100644
--- a/mlir/include/mlir/IR/Interfaces.td
+++ b/mlir/include/mlir/IR/Interfaces.td
@@ -147,6 +147,11 @@ class TypeInterface<string name, list<Interface> baseInterfaces = []>
 			!if(!empty(cppNamespace),"", cppNamespace # "::") # name
     >;
 
+// DialectInterface represents an interface registered to an operation.
+class DialectInterface<string name, list<Interface> baseInterfaces = []>
+  : Interface<name, baseInterfaces>, OpInterfaceTrait<name>;
+
+
 // Whether to declare the interface methods in the user entity's header. This
 // class simply wraps an Interface but is used to indicate that the method
 // declarations should be generated. This class takes an optional set of methods
diff --git a/mlir/include/mlir/TableGen/Interfaces.h b/mlir/include/mlir/TableGen/Interfaces.h
index 7c36cbc1192ac..f62d21da467a1 100644
--- a/mlir/include/mlir/TableGen/Interfaces.h
+++ b/mlir/include/mlir/TableGen/Interfaces.h
@@ -157,6 +157,13 @@ struct TypeInterface : public Interface {
 
   static bool classof(const Interface *interface);
 };
+// An interface that is registered to a Dialect.
+struct DialectInterface : public Interface {
+  using Interface::Interface;
+
+  static bool classof(const Interface *interface);
+};
+
 } // namespace tblgen
 } // namespace mlir
 
diff --git a/mlir/lib/TableGen/Interfaces.cpp b/mlir/lib/TableGen/Interfaces.cpp
index b0ad3ee59a089..77a6cecebbeaf 100644
--- a/mlir/lib/TableGen/Interfaces.cpp
+++ b/mlir/lib/TableGen/Interfaces.cpp
@@ -208,3 +208,11 @@ bool OpInterface::classof(const Interface *interface) {
 bool TypeInterface::classof(const Interface *interface) {
   return interface->getDef().isSubClassOf("TypeInterface");
 }
+
+//===----------------------------------------------------------------------===//
+// DialectInterface
+//===----------------------------------------------------------------------===//
+
+bool DialectInterface::classof(const Interface *interface) {
+  return interface->getDef().isSubClassOf("DialectInterface");
+}
diff --git a/mlir/test/mlir-tblgen/dialect-interface.td b/mlir/test/mlir-tblgen/dialect-interface.td
new file mode 100644
index 0000000000000..9b424bf501be3
--- /dev/null
+++ b/mlir/test/mlir-tblgen/dialect-interface.td
@@ -0,0 +1,66 @@
+// RUN: mlir-tblgen -gen-dialect-interface-decls -I %S/../../include %s | FileCheck %s --check-prefix=DECL
+
+include "mlir/IR/Interfaces.td"
+
+def NoDefaultMethod : DialectInterface<"NoDefaultMethod"> {
+  let description = [{
+    This is an example dialect interface without default method body.
+  }];
+
+  let cppNamespace = "::mlir::example";
+
+  let methods = [
+    InterfaceMethod<
+      /*desc=*/        "Check if it's an example dialect",
+      /*returnType=*/  "bool",
+      /*methodName=*/  "isExampleDialect",
+      /*args=*/        (ins)
+      >,
+      InterfaceMethod<
+      /*desc=*/        "second method to check if multiple methods supported",
+      /*returnType=*/  "unsigned",
+      /*methodName=*/  "supportSecondMethod",
+      /*args=*/        (ins "::mlir::Type":$type)
+      >
+
+  ];
+}
+
+// DECL:   class NoDefaultMethod : public {{.*}}DialectInterface::Base<NoDefaultMethod>
+// DECL:   virtual bool isExampleDialect() const = 0;
+// DECL:   virtual unsigned supportSecondMethod(::mlir::Type type) const = 0;
+// DECL:   protected:
+// DECL-NEXT:   NoDefaultMethod(::mlir::Dialect *dialect) : Base(dialect) {}
+
+def WithDefaultMethodInterface : DialectInterface<"WithDefaultMethodInterface"> {
+  let description = [{
+    This is an example dialect interface with default method bodies.
+  }];
+
+  let cppNamespace = "::mlir::example";
+
+  let methods = [
+    InterfaceMethod<
+      /*desc=*/        "Check if it's an example dialect",
+      /*returnType=*/  "bool",
+      /*methodName=*/  "isExampleDialect",
+      /*args=*/        (ins),
+      /*methodBody=*/  [{
+          return true;
+         }]
+      >,
+      InterfaceMethod<
+      /*desc=*/        "second method to check if multiple methods supported",
+      /*returnType=*/  "unsigned",
+      /*methodName=*/  "supportSecondMethod",
+      /*args=*/        (ins "::mlir::Type":$type)
+      >
+
+  ];
+}
+
+// DECL:  virtual bool isExampleDialect() const;
+// DECL:  bool ::mlir::example::WithDefaultMethodInterface::isExampleDialect() const {
+// DECL-NEXT:  return true;
+// DECL-NEXT: }
+
diff --git a/mlir/tools/mlir-tblgen/CMakeLists.txt b/mlir/tools/mlir-tblgen/CMakeLists.txt
index 2a7ef7e0576c8..d7087cba3c874 100644
--- a/mlir/tools/mlir-tblgen/CMakeLists.txt
+++ b/mlir/tools/mlir-tblgen/CMakeLists.txt
@@ -12,6 +12,7 @@ add_tablegen(mlir-tblgen MLIR
   AttrOrTypeFormatGen.cpp
   BytecodeDialectGen.cpp
   DialectGen.cpp
+  DialectInterfacesGen.cpp
   DirectiveCommonGen.cpp
   EnumsGen.cpp
   EnumPythonBindingGen.cpp
diff --git a/mlir/tools/mlir-tblgen/DialectInterfacesGen.cpp b/mlir/tools/mlir-tblgen/DialectInterfacesGen.cpp
new file mode 100644
index 0000000000000..2fc500343501c
--- /dev/null
+++ b/mlir/tools/mlir-tblgen/DialectInterfacesGen.cpp
@@ -0,0 +1,176 @@
+//===- DialectInterfacesGen.cpp - MLIR dialect interface utility generator ===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// DialectInterfaceGen generates definitions for Dialect interfaces.
+//
+//===----------------------------------------------------------------------===//
+
+#include "CppGenUtilities.h"
+#include "DocGenUtilities.h"
+#include "mlir/TableGen/GenInfo.h"
+#include "mlir/TableGen/Interfaces.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/raw_ostream.h"
+#include "llvm/TableGen/CodeGenHelpers.h"
+#include "llvm/TableGen/Error.h"
+#include "llvm/TableGen/Record.h"
+#include "llvm/TableGen/TableGenBackend.h"
+
+using namespace mlir;
+using llvm::Record;
+using llvm::RecordKeeper;
+using mlir::tblgen::Interface;
+using mlir::tblgen::InterfaceMethod;
+
+/// Emit a string corresponding to a C++ type, followed by a space if necessary.
+static raw_ostream &emitCPPType(StringRef type, raw_ostream &os) {
+  type = type.trim();
+  os << type;
+  if (type.back() != '&' && type.back() != '*')
+    os << " ";
+  return os;
+}
+
+/// Emit the method name and argument list for the given method.
+static void emitMethodNameAndArgs(const InterfaceMethod &method, StringRef name,
+                                  raw_ostream &os) {
+  os << name << '(';
+  llvm::interleaveComma(method.getArguments(), os,
+                        [&](const InterfaceMethod::Argument &arg) {
+                          os << arg.type << " " << arg.name;
+                        });
+  os << ") const";
+}
+
+/// Get an array of all Dialect Interface definitions
+static std::vector<const Record *>
+getAllInterfaceDefinitions(const RecordKeeper &records) {
+  std::vector<const Record *> defs =
+      records.getAllDerivedDefinitions("DialectInterface");
+
+  llvm::erase_if(defs, [&](const Record *def) {
+    // Ignore interfaces defined outside of the top-level file.
+    return llvm::SrcMgr.FindBufferContainingLoc(def->getLoc()[0]) !=
+           llvm::SrcMgr.getMainFileID();
+  });
+  return defs;
+}
+
+namespace {
+/// This struct is the generator used when processing tablegen dialect
+/// interfaces.
+class DialectInterfaceGenerator {
+public:
+  DialectInterfaceGenerator(const RecordKeeper &records, raw_ostream &os)
+      : defs(getAllInterfaceDefinitions(records)), os(os) {}
+
+  bool emitInterfaceDecls();
+
+protected:
+  void emitInterfaceDecl(const Interface &interface);
+  void emitInterfaceMethodsDef(const Interface &interface);
+
+  /// The set of interface records to emit.
+  std::vector<const Record *> defs;
+  // The stream to emit to.
+  raw_ostream &os;
+};
+} // namespace
+
+//===----------------------------------------------------------------------===//
+// GEN: Interface declarations
+//===----------------------------------------------------------------------===//
+
+static void emitInterfaceMethodDoc(const InterfaceMethod &method,
+                                   raw_ostream &os, StringRef prefix = "") {
+  if (std::optional<StringRef> description = method.getDescription())
+    tblgen::emitDescriptionComment(*description, os, prefix);
+}
+
+static void emitInterfaceDeclMethods(const Interface &interface,
+                                     raw_ostream &os) {
+  for (auto &method : interface.getMethods()) {
+    emitInterfaceMethodDoc(method, os, "  ");
+    os << "  virtual ";
+    emitCPPType(method.getReturnType(), os);
+    emitMethodNameAndArgs(method, method.getName(), os);
+    if (!method.getBody())
+      // no default method body
+      os << " = 0";
+    os << ";\n";
+  }
+}
+
+void DialectInterfaceGenerator::emitInterfaceDecl(const Interface &interface) {
+  llvm::NamespaceEmitter ns(os, interface.getCppNamespace());
+
+  StringRef interfaceName = interface.getName();
+
+  tblgen::emitSummaryAndDescComments(os, "",
+                                     interface.getDescription().value_or(""));
+
+  // Emit the main interface class declaration.
+  os << llvm::formatv(
+      "class {0} : public ::mlir::DialectInterface::Base<{0}> {{\n"
+      "public:\n",
+      interfaceName);
+
+  emitInterfaceDeclMethods(interface, os);
+  os << llvm::formatv("\nprotected:\n"
+                      "  {0}(::mlir::Dialect *dialect) : Base(dialect) {{}\n",
+                      interfaceName);
+
+  os << "};\n";
+}
+
+void DialectInterfaceGenerator::emitInterfaceMethodsDef(
+    const Interface &interface) {
+
+  for (auto &method : interface.getMethods()) {
+    if (auto body = method.getBody()) {
+      emitCPPType(method.getReturnType(), os);
+      os << interface.getCppNamespace() << "::";
+      os << interface.getName() << "::";
+      emitMethodNameAndArgs(method, method.getName(), os);
+      os << " {\n  " << body.value() << "\n}\n";
+    }
+  }
+}
+
+bool DialectInterfaceGenerator::emitInterfaceDecls() {
+
+  llvm::emitSourceFileHeader("Dialect Interface Declarations", os);
+
+  // Sort according to ID, so defs are emitted in the order in which they appear
+  // in the Tablegen file.
+  std::vector<const Record *> sortedDefs(defs);
+  llvm::sort(sortedDefs, [](const Record *lhs, const Record *rhs) {
+    return lhs->getID() < rhs->getID();
+  });
+
+  for (const Record *def : sortedDefs)
+    emitInterfaceDecl(Interface(def));
+
+  os << "\n";
+  for (const Record *def : sortedDefs)
+    emitInterfaceMethodsDef(Interface(def));
+
+  return false;
+}
+
+//===----------------------------------------------------------------------===//
+// GEN: Interface registration hooks
+//===----------------------------------------------------------------------===//
+
+static mlir::GenRegistration genDecls(
+    "gen-dialect-interface-decls",
+    "Generate dialect interface declarations.",
+    [](const RecordKeeper &records, raw_ostream &os) {
+      return DialectInterfaceGenerator(records, os).emitInterfaceDecls();
+    });

>From 4ca9d25f184b808021289ea921a3494a01c1ff93 Mon Sep 17 00:00:00 2001
From: aidint <at.aidin at gmail.com>
Date: Sun, 30 Nov 2025 20:00:59 +0100
Subject: [PATCH 2/4] resolve clang-format problem

---
 mlir/tools/mlir-tblgen/DialectInterfacesGen.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/mlir/tools/mlir-tblgen/DialectInterfacesGen.cpp b/mlir/tools/mlir-tblgen/DialectInterfacesGen.cpp
index 2fc500343501c..0ce4c3ef603b3 100644
--- a/mlir/tools/mlir-tblgen/DialectInterfacesGen.cpp
+++ b/mlir/tools/mlir-tblgen/DialectInterfacesGen.cpp
@@ -169,8 +169,7 @@ bool DialectInterfaceGenerator::emitInterfaceDecls() {
 //===----------------------------------------------------------------------===//
 
 static mlir::GenRegistration genDecls(
-    "gen-dialect-interface-decls",
-    "Generate dialect interface declarations.",
+    "gen-dialect-interface-decls", "Generate dialect interface declarations.",
     [](const RecordKeeper &records, raw_ostream &os) {
       return DialectInterfaceGenerator(records, os).emitInterfaceDecls();
     });

>From e3d4a439f237b0d7349c785b488ebe8531ab0c55 Mon Sep 17 00:00:00 2001
From: aidint <at.aidin at gmail.com>
Date: Tue, 2 Dec 2025 23:24:32 +0100
Subject: [PATCH 3/4] convert DialectInlinerInterface and use ODS

---
 mlir/include/mlir/IR/Interfaces.td            |   2 +-
 mlir/include/mlir/Transforms/CMakeLists.txt   |   4 +
 .../Transforms/DialectInlinerInterface.td     | 217 ++++++++++++++++++
 mlir/include/mlir/Transforms/InliningUtils.h  | 155 +------------
 mlir/lib/Transforms/CMakeLists.txt            |   1 +
 mlir/test/mlir-tblgen/dialect-interface.td    |   9 +-
 .../mlir-tblgen/DialectInterfacesGen.cpp      |  40 +---
 7 files changed, 240 insertions(+), 188 deletions(-)
 create mode 100644 mlir/include/mlir/Transforms/DialectInlinerInterface.td

diff --git a/mlir/include/mlir/IR/Interfaces.td b/mlir/include/mlir/IR/Interfaces.td
index 746ad3408f424..e51bbd5620280 100644
--- a/mlir/include/mlir/IR/Interfaces.td
+++ b/mlir/include/mlir/IR/Interfaces.td
@@ -147,7 +147,7 @@ class TypeInterface<string name, list<Interface> baseInterfaces = []>
 			!if(!empty(cppNamespace),"", cppNamespace # "::") # name
     >;
 
-// DialectInterface represents an interface registered to an operation.
+// DialectInterface represents a Dialect Interface.
 class DialectInterface<string name, list<Interface> baseInterfaces = []>
   : Interface<name, baseInterfaces>, OpInterfaceTrait<name>;
 
diff --git a/mlir/include/mlir/Transforms/CMakeLists.txt b/mlir/include/mlir/Transforms/CMakeLists.txt
index 5fa52b28b6f1d..1b57a3482c1bb 100644
--- a/mlir/include/mlir/Transforms/CMakeLists.txt
+++ b/mlir/include/mlir/Transforms/CMakeLists.txt
@@ -5,4 +5,8 @@ mlir_tablegen(Transforms.capi.h.inc -gen-pass-capi-header --prefix Transforms)
 mlir_tablegen(Transforms.capi.cpp.inc -gen-pass-capi-impl --prefix Transforms)
 add_mlir_dialect_tablegen_target(MLIRTransformsPassIncGen)
 
+set(LLVM_TARGET_DEFINITIONS DialectInlinerInterface.td)
+mlir_tablegen(DialectInlinerInterface.h.inc -gen-dialect-interface-decls)
+add_mlir_dialect_tablegen_target(MLIRTransformsDialectInterfaceIncGen)
+
 add_mlir_doc(Passes GeneralPasses ./ -gen-pass-doc)
diff --git a/mlir/include/mlir/Transforms/DialectInlinerInterface.td b/mlir/include/mlir/Transforms/DialectInlinerInterface.td
new file mode 100644
index 0000000000000..f8fd341cf8ea2
--- /dev/null
+++ b/mlir/include/mlir/Transforms/DialectInlinerInterface.td
@@ -0,0 +1,217 @@
+#ifndef DIALECTINLINERINTERFACE
+#define DIALECTINLINERINTERFACE
+
+include "mlir/IR/Interfaces.td"
+
+def DialectInlinerInterface : DialectInterface<"DialectInlinerInterface"> {
+  let description = [{
+    This is the interface that must be implemented by the dialects of operations
+    to be inlined. This interface should only handle the operations of the
+    given dialect.
+    }];
+
+
+  let cppNamespace = "::mlir";
+
+  let methods = [
+    InterfaceMethod<
+      /*desc=*/        [{
+        Returns true if the given operation 'callable', that implements the
+        'CallableOpInterface', can be inlined into the position given call
+        operation 'call', that is registered to the current dialect and implements
+        the `CallOpInterface`. 'wouldBeCloned' is set to true if the region of the
+        given 'callable' is set to be cloned during the inlining process, or false
+        if the region is set to be moved in-place(i.e. no duplicates would be
+        created).
+      }],
+      /*returnType=*/  "bool",
+      /*methodName=*/  "isLegalToInline",
+      /*args=*/        (ins "::mlir::Operation *":$call, "::mlir::Operation *":$callable, "bool":$wouldBeCloned),
+      /*methodBody=*/  [{
+        return false;
+      }]
+    >,
+    InterfaceMethod<
+      /*desc=*/        [{
+        Returns true if the given region 'src' can be inlined into the region
+        'dest' that is attached to an operation registered to the current dialect.
+        'wouldBeCloned' is set to true if the given 'src' region is set to be
+        cloned during the inlining process, or false if the region is set to be
+        moved in-place(i.e. no duplicates would be created). 'valueMapping'
+        contains any remapped values from within the 'src' region. This can be
+        used to examine what values will replace entry arguments into the 'src'
+        region for example.
+      }],
+      /*returnType=*/  "bool",
+      /*methodName=*/  "isLegalToInline",
+      /*args=*/        (ins "::mlir::Region *":$dest, "::mlir::Region *":$src, "bool":$wouldBeCloned,
+                        "::mlir::IRMapping &":$valueMapping),
+      /*methodBody=*/  [{
+        return false;
+      }]
+    >,
+    InterfaceMethod<
+      /*desc=*/        [{
+        Returns true if the given region 'src' can be inlined into the region
+        'dest' that is attached to an operation registered to the current dialect.
+        'wouldBeCloned' is set to true if the given 'src' region is set to be
+        cloned during the inlining process, or false if the region is set to be
+        moved in-place(i.e. no duplicates would be created). 'valueMapping'
+        contains any remapped values from within the 'src' region. This can be
+        used to examine what values will replace entry arguments into the 'src'
+        region for example.
+      }],
+      /*returnType=*/  "bool",
+      /*methodName=*/  "isLegalToInline",
+      /*args=*/        (ins "::mlir::Operation *":$op, "::mlir::Region *":$dest, "bool":$wouldBeCloned,
+                        "::mlir::IRMapping &":$valueMapping),
+      /*methodBody=*/  [{
+        return false;
+      }]
+    >,
+    InterfaceMethod<
+      /*desc=*/        [{
+        This hook is invoked on an operation that contains regions. It should
+        return true if the analyzer should recurse within the regions of this
+        operation when computing legality and cost, false otherwise. The default
+        implementation returns true.
+      }],
+      /*returnType=*/  "bool",
+      /*methodName=*/  "shouldAnalyzeRecursively",
+      /*args=*/        (ins "::mlir::Operation *":$op),
+      /*methodBody=*/  [{
+        return true;
+      }]
+    >,
+    InterfaceMethod<
+      /*desc=*/        [{
+        Handle the given inlined terminator by replacing it with a new operation
+        as necessary. This overload is called when the inlined region has more
+        than one block. The 'newDest' block represents the new final branching
+        destination of blocks within this region, i.e. operations that release
+        control to the parent operation will likely now branch to this block.
+        Its block arguments correspond to any values that need to be replaced by
+        terminators within the inlined region.
+      }],
+      /*returnType=*/  "void",
+      /*methodName=*/  "handleTerminator",
+  /*args=*/        (ins "::mlir::Operation *":$op, "::mlir::Block *":$newDest),
+      /*methodBody=*/  [{
+        llvm_unreachable("must implement handleTerminator in the case of multiple "
+                         "inlined blocks");
+      }]
+    >,
+    InterfaceMethod<
+      /*desc=*/        [{
+        Handle the given inlined terminator by replacing it with a new operation
+        as necessary. This overload is called when the inlined region only
+        contains one block. 'valuesToReplace' contains the previously returned
+        values of the call site before inlining. These values must be replaced by
+        this callback if they had any users (for example for traditional function
+        calls, these are directly replaced with the operands of the `return`
+        operation). The given 'op' will be removed by the caller, after this
+        function has been called.
+      }],
+      /*returnType=*/  "void",
+      /*methodName=*/  "handleTerminator",
+      /*args=*/        (ins "::mlir::Operation *":$op, "::mlir::ValueRange":$valuesToReplace),
+      /*methodBody=*/  [{
+        llvm_unreachable(
+            "must implement handleTerminator in the case of one inlined block");
+      }]
+    >,
+    InterfaceMethod<
+      /*desc=*/        [{
+        Attempt to materialize a conversion for a type mismatch between a call
+        from this dialect, and a callable region. This method should generate an
+        operation that takes 'input' as the only operand, and produces a single
+        result of 'resultType'. If a conversion can not be generated, nullptr
+        should be returned. For example, this hook may be invoked in the following
+        scenarios:
+          func @foo(i32) -> i32 { ... }
+        
+          // Mismatched input operand
+          ... = foo.call @foo(%input : i16) -> i32
+        
+          // Mismatched result type.
+          ... = foo.call @foo(%input : i32) -> i16
+        
+        NOTE: This hook may be invoked before the 'isLegal' checks above.
+      }],
+      /*returnType=*/  "::mlir::Operation *",
+      /*methodName=*/  "materializeCallConversion",
+      /*args=*/        (ins "::mlir::OpBuilder &":$builder, "::mlir::Value":$input, 
+                        "::mlir::Type":$resultType, "::mlir::Location":$conversionLoc),
+      /*methodBody=*/  [{
+        return nullptr;
+      }]
+    >,
+    InterfaceMethod<
+      /*desc=*/        [{
+        Hook to transform the call arguments before using them to replace the
+        callee arguments. Returns a value of the same type or the `argument`
+        itself if nothing changed. The `argumentAttrs` dictionary is non-null even
+        if no attribute is present. The hook is called after converting the
+        callsite argument types using the materializeCallConversion callback, and
+        right before inlining the callee region. Any operations created using the
+        provided `builder` are inserted right before the inlined callee region. An
+        example use case is the insertion of copies for by value arguments.
+      }],
+      /*returnType=*/  "::mlir::Value",
+      /*methodName=*/  "handleArgument",
+      /*args=*/        (ins "::mlir::OpBuilder &":$builder, "::mlir::Operation *":$call, 
+                        "::mlir::Operation *":$callable, "::mlir::Value":$argument,
+                        "::mlir::DictionaryAttr":$argumentAttrs),
+      /*methodBody=*/  [{
+        return argument;
+      }]
+    >,
+    InterfaceMethod<
+      /*desc=*/        [{
+        Hook to transform the callee results before using them to replace the call
+        results. Returns a value of the same type or the `result` itself if
+        nothing changed. The `resultAttrs` dictionary is non-null even if no
+        attribute is present. The hook is called right before handling
+        terminators, and obtains the callee result before converting its type
+        using the `materializeCallConversion` callback. Any operations created
+        using the provided `builder` are inserted right after the inlined callee
+        region. An example use case is the insertion of copies for by value
+        results. NOTE: This hook is invoked after inlining the `callable` region.
+      }],
+      /*returnType=*/  "::mlir::Value",
+      /*methodName=*/  "handleResult",
+      /*args=*/        (ins "::mlir::OpBuilder &":$builder, "::mlir::Operation *":$call, 
+                        "::mlir::Operation *":$callable, "::mlir::Value":$result,
+                        "::mlir::DictionaryAttr":$resultAttrs),
+      /*methodBody=*/  [{
+        return result;
+      }]
+    >,
+    InterfaceMethod<
+      /*desc=*/        [{
+        Process a set of blocks that have been inlined for a call. This callback
+        is invoked before inlined terminator operations have been processed.
+      }],
+      /*returnType=*/  "void",
+      /*methodName=*/  "processInlinedCallBlocks",
+      /*args=*/        (ins "::mlir::Operation *":$call, 
+                        "::mlir::iterator_range<::mlir::Region::iterator>":$inlinedBlocks),
+      /*methodBody=*/  [{}]
+    >,
+    InterfaceMethod<
+      /*desc=*/        [{
+        Returns true if the inliner can assume a fast path of not creating a new
+        block, if there is only one block.
+      }],
+      /*returnType=*/  "bool",
+      /*methodName=*/  "allowSingleBlockOptimization",
+      /*args=*/        (ins "::mlir::iterator_range<::mlir::Region::iterator>":$inlinedBlocks),
+      /*methodBody=*/  [{
+        return true;
+      }]
+    >
+  ];
+}
+
+
+#endif
diff --git a/mlir/include/mlir/Transforms/InliningUtils.h b/mlir/include/mlir/Transforms/InliningUtils.h
index ed6413d8cd44c..b6c6da3ddcc9b 100644
--- a/mlir/include/mlir/Transforms/InliningUtils.h
+++ b/mlir/include/mlir/Transforms/InliningUtils.h
@@ -32,158 +32,7 @@ class Region;
 class TypeRange;
 class Value;
 class ValueRange;
-
-//===----------------------------------------------------------------------===//
-// InlinerInterface
-//===----------------------------------------------------------------------===//
-
-/// This is the interface that must be implemented by the dialects of operations
-/// to be inlined. This interface should only handle the operations of the
-/// given dialect.
-class DialectInlinerInterface
-    : public DialectInterface::Base<DialectInlinerInterface> {
-public:
-  DialectInlinerInterface(Dialect *dialect) : Base(dialect) {}
-
-  //===--------------------------------------------------------------------===//
-  // Analysis Hooks
-  //===--------------------------------------------------------------------===//
-
-  /// Returns true if the given operation 'callable', that implements the
-  /// 'CallableOpInterface', can be inlined into the position given call
-  /// operation 'call', that is registered to the current dialect and implements
-  /// the `CallOpInterface`. 'wouldBeCloned' is set to true if the region of the
-  /// given 'callable' is set to be cloned during the inlining process, or false
-  /// if the region is set to be moved in-place(i.e. no duplicates would be
-  /// created).
-  virtual bool isLegalToInline(Operation *call, Operation *callable,
-                               bool wouldBeCloned) const {
-    return false;
-  }
-
-  /// Returns true if the given region 'src' can be inlined into the region
-  /// 'dest' that is attached to an operation registered to the current dialect.
-  /// 'wouldBeCloned' is set to true if the given 'src' region is set to be
-  /// cloned during the inlining process, or false if the region is set to be
-  /// moved in-place(i.e. no duplicates would be created). 'valueMapping'
-  /// contains any remapped values from within the 'src' region. This can be
-  /// used to examine what values will replace entry arguments into the 'src'
-  /// region for example.
-  virtual bool isLegalToInline(Region *dest, Region *src, bool wouldBeCloned,
-                               IRMapping &valueMapping) const {
-    return false;
-  }
-
-  /// Returns true if the given operation 'op', that is registered to this
-  /// dialect, can be inlined into the given region, false otherwise.
-  /// 'wouldBeCloned' is set to true if the given 'op' is set to be cloned
-  /// during the inlining process, or false if the operation is set to be moved
-  /// in-place(i.e. no duplicates would be created). 'valueMapping' contains any
-  /// remapped values from within the 'src' region. This can be used to examine
-  /// what values may potentially replace the operands to 'op'.
-  virtual bool isLegalToInline(Operation *op, Region *dest, bool wouldBeCloned,
-                               IRMapping &valueMapping) const {
-    return false;
-  }
-
-  /// This hook is invoked on an operation that contains regions. It should
-  /// return true if the analyzer should recurse within the regions of this
-  /// operation when computing legality and cost, false otherwise. The default
-  /// implementation returns true.
-  virtual bool shouldAnalyzeRecursively(Operation *op) const { return true; }
-
-  //===--------------------------------------------------------------------===//
-  // Transformation Hooks
-  //===--------------------------------------------------------------------===//
-
-  /// Handle the given inlined terminator by replacing it with a new operation
-  /// as necessary. This overload is called when the inlined region has more
-  /// than one block. The 'newDest' block represents the new final branching
-  /// destination of blocks within this region, i.e. operations that release
-  /// control to the parent operation will likely now branch to this block.
-  /// Its block arguments correspond to any values that need to be replaced by
-  /// terminators within the inlined region.
-  virtual void handleTerminator(Operation *op, Block *newDest) const {
-    llvm_unreachable("must implement handleTerminator in the case of multiple "
-                     "inlined blocks");
-  }
-
-  /// Handle the given inlined terminator by replacing it with a new operation
-  /// as necessary. This overload is called when the inlined region only
-  /// contains one block. 'valuesToReplace' contains the previously returned
-  /// values of the call site before inlining. These values must be replaced by
-  /// this callback if they had any users (for example for traditional function
-  /// calls, these are directly replaced with the operands of the `return`
-  /// operation). The given 'op' will be removed by the caller, after this
-  /// function has been called.
-  virtual void handleTerminator(Operation *op,
-                                ValueRange valuesToReplace) const {
-    llvm_unreachable(
-        "must implement handleTerminator in the case of one inlined block");
-  }
-
-  /// Attempt to materialize a conversion for a type mismatch between a call
-  /// from this dialect, and a callable region. This method should generate an
-  /// operation that takes 'input' as the only operand, and produces a single
-  /// result of 'resultType'. If a conversion can not be generated, nullptr
-  /// should be returned. For example, this hook may be invoked in the following
-  /// scenarios:
-  ///   func @foo(i32) -> i32 { ... }
-  ///
-  ///   // Mismatched input operand
-  ///   ... = foo.call @foo(%input : i16) -> i32
-  ///
-  ///   // Mismatched result type.
-  ///   ... = foo.call @foo(%input : i32) -> i16
-  ///
-  /// NOTE: This hook may be invoked before the 'isLegal' checks above.
-  virtual Operation *materializeCallConversion(OpBuilder &builder, Value input,
-                                               Type resultType,
-                                               Location conversionLoc) const {
-    return nullptr;
-  }
-
-  /// Hook to transform the call arguments before using them to replace the
-  /// callee arguments. Returns a value of the same type or the `argument`
-  /// itself if nothing changed. The `argumentAttrs` dictionary is non-null even
-  /// if no attribute is present. The hook is called after converting the
-  /// callsite argument types using the materializeCallConversion callback, and
-  /// right before inlining the callee region. Any operations created using the
-  /// provided `builder` are inserted right before the inlined callee region. An
-  /// example use case is the insertion of copies for by value arguments.
-  virtual Value handleArgument(OpBuilder &builder, Operation *call,
-                               Operation *callable, Value argument,
-                               DictionaryAttr argumentAttrs) const {
-    return argument;
-  }
-
-  /// Hook to transform the callee results before using them to replace the call
-  /// results. Returns a value of the same type or the `result` itself if
-  /// nothing changed. The `resultAttrs` dictionary is non-null even if no
-  /// attribute is present. The hook is called right before handling
-  /// terminators, and obtains the callee result before converting its type
-  /// using the `materializeCallConversion` callback. Any operations created
-  /// using the provided `builder` are inserted right after the inlined callee
-  /// region. An example use case is the insertion of copies for by value
-  /// results. NOTE: This hook is invoked after inlining the `callable` region.
-  virtual Value handleResult(OpBuilder &builder, Operation *call,
-                             Operation *callable, Value result,
-                             DictionaryAttr resultAttrs) const {
-    return result;
-  }
-
-  /// Process a set of blocks that have been inlined for a call. This callback
-  /// is invoked before inlined terminator operations have been processed.
-  virtual void processInlinedCallBlocks(
-      Operation *call, iterator_range<Region::iterator> inlinedBlocks) const {}
-
-  /// Returns true if the inliner can assume a fast path of not creating a new
-  /// block, if there is only one block.
-  virtual bool allowSingleBlockOptimization(
-      iterator_range<Region::iterator> inlinedBlocks) const {
-    return true;
-  }
-};
+class DialectInlinerInterface;
 
 /// This interface provides the hooks into the inlining interface.
 /// Note: this class automatically collects 'DialectInlinerInterface' objects
@@ -307,4 +156,6 @@ inlineCall(InlinerInterface &interface,
 
 } // namespace mlir
 
+#include "mlir/Transforms/DialectInlinerInterface.h.inc"
+
 #endif // MLIR_TRANSFORMS_INLININGUTILS_H
diff --git a/mlir/lib/Transforms/CMakeLists.txt b/mlir/lib/Transforms/CMakeLists.txt
index 54b67f5c7a91e..6c5303b4dd8a4 100644
--- a/mlir/lib/Transforms/CMakeLists.txt
+++ b/mlir/lib/Transforms/CMakeLists.txt
@@ -27,6 +27,7 @@ add_mlir_library(MLIRTransforms
 
   DEPENDS
   MLIRTransformsPassIncGen
+  MLIRTransformsDialectInterfaceIncGen
 
   LINK_LIBS PUBLIC
   MLIRAnalysis
diff --git a/mlir/test/mlir-tblgen/dialect-interface.td b/mlir/test/mlir-tblgen/dialect-interface.td
index 9b424bf501be3..ff39fd941f300 100644
--- a/mlir/test/mlir-tblgen/dialect-interface.td
+++ b/mlir/test/mlir-tblgen/dialect-interface.td
@@ -27,10 +27,10 @@ def NoDefaultMethod : DialectInterface<"NoDefaultMethod"> {
 }
 
 // DECL:   class NoDefaultMethod : public {{.*}}DialectInterface::Base<NoDefaultMethod>
-// DECL:   virtual bool isExampleDialect() const = 0;
-// DECL:   virtual unsigned supportSecondMethod(::mlir::Type type) const = 0;
-// DECL:   protected:
+// DECL:   public:
 // DECL-NEXT:   NoDefaultMethod(::mlir::Dialect *dialect) : Base(dialect) {}
+// DECL:   virtual bool isExampleDialect() const {}
+// DECL:   virtual unsigned supportSecondMethod(::mlir::Type type) const {}
 
 def WithDefaultMethodInterface : DialectInterface<"WithDefaultMethodInterface"> {
   let description = [{
@@ -59,8 +59,7 @@ def WithDefaultMethodInterface : DialectInterface<"WithDefaultMethodInterface">
   ];
 }
 
-// DECL:  virtual bool isExampleDialect() const;
-// DECL:  bool ::mlir::example::WithDefaultMethodInterface::isExampleDialect() const {
+// DECL:  virtual bool isExampleDialect() const {
 // DECL-NEXT:  return true;
 // DECL-NEXT: }
 
diff --git a/mlir/tools/mlir-tblgen/DialectInterfacesGen.cpp b/mlir/tools/mlir-tblgen/DialectInterfacesGen.cpp
index 0ce4c3ef603b3..4400c381fe7b2 100644
--- a/mlir/tools/mlir-tblgen/DialectInterfacesGen.cpp
+++ b/mlir/tools/mlir-tblgen/DialectInterfacesGen.cpp
@@ -74,7 +74,6 @@ class DialectInterfaceGenerator {
 
 protected:
   void emitInterfaceDecl(const Interface &interface);
-  void emitInterfaceMethodsDef(const Interface &interface);
 
   /// The set of interface records to emit.
   std::vector<const Record *> defs;
@@ -93,17 +92,18 @@ static void emitInterfaceMethodDoc(const InterfaceMethod &method,
     tblgen::emitDescriptionComment(*description, os, prefix);
 }
 
-static void emitInterfaceDeclMethods(const Interface &interface,
-                                     raw_ostream &os) {
+static void emitInterfaceMethodsDef(const Interface &interface,
+                                    raw_ostream &os) {
   for (auto &method : interface.getMethods()) {
     emitInterfaceMethodDoc(method, os, "  ");
     os << "  virtual ";
     emitCPPType(method.getReturnType(), os);
     emitMethodNameAndArgs(method, method.getName(), os);
-    if (!method.getBody())
+    if (auto body = method.getBody())
+      os << " {\n    " << body << "\n  }\n";
+    else
       // no default method body
-      os << " = 0";
-    os << ";\n";
+      os << " {}\n";
   }
 }
 
@@ -117,32 +117,16 @@ void DialectInterfaceGenerator::emitInterfaceDecl(const Interface &interface) {
 
   // Emit the main interface class declaration.
   os << llvm::formatv(
-      "class {0} : public ::mlir::DialectInterface::Base<{0}> {{\n"
-      "public:\n",
+      "class {0} : public ::mlir::DialectInterface::Base<{0}> {\n"
+      "public:\n"
+      "  {0}(::mlir::Dialect *dialect) : Base(dialect) {{}\n",
       interfaceName);
 
-  emitInterfaceDeclMethods(interface, os);
-  os << llvm::formatv("\nprotected:\n"
-                      "  {0}(::mlir::Dialect *dialect) : Base(dialect) {{}\n",
-                      interfaceName);
+  emitInterfaceMethodsDef(interface, os);
 
   os << "};\n";
 }
 
-void DialectInterfaceGenerator::emitInterfaceMethodsDef(
-    const Interface &interface) {
-
-  for (auto &method : interface.getMethods()) {
-    if (auto body = method.getBody()) {
-      emitCPPType(method.getReturnType(), os);
-      os << interface.getCppNamespace() << "::";
-      os << interface.getName() << "::";
-      emitMethodNameAndArgs(method, method.getName(), os);
-      os << " {\n  " << body.value() << "\n}\n";
-    }
-  }
-}
-
 bool DialectInterfaceGenerator::emitInterfaceDecls() {
 
   llvm::emitSourceFileHeader("Dialect Interface Declarations", os);
@@ -157,10 +141,6 @@ bool DialectInterfaceGenerator::emitInterfaceDecls() {
   for (const Record *def : sortedDefs)
     emitInterfaceDecl(Interface(def));
 
-  os << "\n";
-  for (const Record *def : sortedDefs)
-    emitInterfaceMethodsDef(Interface(def));
-
   return false;
 }
 

>From 3f0fc8be2e2d0ffc1a7d86559b420545e27bbd92 Mon Sep 17 00:00:00 2001
From: aidint <at.aidin at gmail.com>
Date: Wed, 3 Dec 2025 23:05:48 +0100
Subject: [PATCH 4/4] add documentation for dialect interface ods support

---
 mlir/docs/Interfaces.md | 62 ++++++++++++++++++++++++++++++++++++++---
 1 file changed, 58 insertions(+), 4 deletions(-)

diff --git a/mlir/docs/Interfaces.md b/mlir/docs/Interfaces.md
index 7e1c5fe075675..71ed0966a2f9b 100644
--- a/mlir/docs/Interfaces.md
+++ b/mlir/docs/Interfaces.md
@@ -85,6 +85,64 @@ if (DialectInlinerInterface *interface = dyn_cast<DialectInlinerInterface>(diale
 }
 ```
 
+#### Utilizing the ODS framework
+
+Note: Before reading this section, the reader should have some familiarity with
+the concepts described in the
+[`Operation Definition Specification`](DefiningDialects/Operations.md) documentation.
+
+MLIR also supports defining dialect interfaces directly in **TableGen**.
+This reduces boilerplate and allows authors to specify high-level interface 
+structure declaratively.
+
+For example, the above interface can be defined using ODS as follows:
+```tablegen
+def DialectInlinerInterface : DialectInterface<"DialectInlinerInterface"> {
+  let description = [{
+     Define a base inlining interface class to allow for dialects to opt-in to 
+     the inliner.
+  }];
+
+  let methods = [
+    InterfaceMethod<
+      /*desc=*/        [{
+        Returns true if the given region 'src' can be inlined into the region
+        'dest' that is attached to an operation registered to the current dialect.
+        'valueMapping' contains any remapped values from within the 'src' region.
+        This can be used to examine what values will replace entry arguments into
+        the 'src' region, for example.
+      }],
+      /*returnType=*/  "bool",
+      /*methodName=*/  "isLegalToInline",
+      /*args=*/        (ins "Region *":$dest, "Region *":$src, 
+                        "IRMapping &":$valueMapping),
+      /*methodBody=*/  [{
+        return false;
+      }]
+      >
+  ];
+}
+```
+
+`DialectInterfaces` class make use of the following components:
+
+*   C++ Class Name (Provided via template parameter)
+    -   The name of the C++ interface class.
+*   Description (`description`)
+    -   A string description of the interface, its invariants, example usages,
+    etc.
+*   C++ Namespace (`cppNamespace`)
+    -   The C++ namespace that the interface class should be generated in.
+*   Methods (`methods`)
+    -   The list of interface hook methods that are defined by the IR object.
+    -   The structure of these methods is defined [here](#interface-methods).
+
+The header file can be generated via the following command:
+```bash
+mlir-tblgen gen-dialect-interface-decls DialectInterface.td
+
+```
+
 #### DialectInterfaceCollection
 
 An additional utility is provided via `DialectInterfaceCollection`. This class
@@ -364,10 +422,6 @@ void *TestDialect::getRegisteredInterfaceForOp(TypeID typeID,
 
 #### Utilizing the ODS Framework
 
-Note: Before reading this section, the reader should have some familiarity with
-the concepts described in the
-[`Operation Definition Specification`](DefiningDialects/Operations.md) documentation.
-
 As detailed above, [Interfaces](#attributeoperationtype-interfaces) allow for
 attributes, operations, and types to expose method calls without requiring that
 the caller know the specific derived type. The downside to this infrastructure,



More information about the Mlir-commits mailing list