[Mlir-commits] [mlir] 836f91b - [mlir][tblgen]: forward overloaded interface methods to external models (#203507)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Thu Jun 18 00:23:12 PDT 2026


Author: Ege Beysel
Date: 2026-06-18T09:23:07+02:00
New Revision: 836f91b3a196b20cef6c1090fea6533ba872e37d

URL: https://github.com/llvm/llvm-project/commit/836f91b3a196b20cef6c1090fea6533ba872e37d
DIFF: https://github.com/llvm/llvm-project/commit/836f91b3a196b20cef6c1090fea6533ba872e37d.diff

LOG: [mlir][tblgen]: forward overloaded interface methods to external models (#203507)

The FallbackModel/ExternalModel forwarders generated for an OpInterface
dispatched to the concrete model by the method's unique name (e.g.
`getOverloadedValue_1`), whereas the Model path already forwards by the
method's source name. For an interface that overloads a method this
prevents an external model from overriding an overload by its real name.
Forward by the source name in both paths so external models can
implement overloaded interface methods. No change for non-overloaded
methods, where the unique and source names coincide.

Previously, the external model interfaces exposed the internal name
mangling that we do for overloaded methods to the implementers,
prohibiting them from overriding these. This PR fixes that and hides the
internal name mangling from the implementers/users, and exposes source
names for overloaded methods, symmetrically to the `Model` case. E.g.
this PR allows:

```cpp
// An external model can override an OVERLOADED
// interface method by its normal (source) name.

struct BaseInterface {
  // Two overloads, both with default implementations.
  int overloadedMethod(int a)        { return a * 2; }
  int overloadedMethod(int a, int b) { return a * b; }
};

struct SpecializedInterface
    : public BaseInterface::ExternalModel<SpecializedInterface, some::Op> {

  // First overload: always worked, nothing about it is mangled.
  int overloadedMethod(int a) { ... }

  // Second overload: this is the case the PR fixes. Internally the overload is
  // given a mangled name, and the generated forwarder used to dispatch to that
  // mangled name, so this source-name override was bypassed: the call fell
  // through to the default (`a * b`), or failed to compile if no default
  // existed. The PR forwards by the source name instead, so this override is
  // the one that runs.
  int overloadedMethod(int a, int b) { return a * b + 3; }
};
```

You can also find a more concrete use-case of this in this (yet) [draft
PR](https://github.com/llvm/llvm-project/pull/204007) that depends on
this PR.

Assisted-by: Claude Code

---------

Signed-off-by: Ege Beysel <beyselege at gmail.com>

Added: 
    

Modified: 
    mlir/test/lib/Dialect/Test/TestInterfaces.td
    mlir/tools/mlir-tblgen/OpInterfacesGen.cpp
    mlir/unittests/IR/InterfaceAttachmentTest.cpp

Removed: 
    


################################################################################
diff  --git a/mlir/test/lib/Dialect/Test/TestInterfaces.td b/mlir/test/lib/Dialect/Test/TestInterfaces.td
index 3697e38ac4c7d..c5787d00e853e 100644
--- a/mlir/test/lib/Dialect/Test/TestInterfaces.td
+++ b/mlir/test/lib/Dialect/Test/TestInterfaces.td
@@ -91,6 +91,18 @@ def TestExternalTypeInterface : TypeInterface<"TestExternalTypeInterface"> {
     StaticInterfaceMethod<"Returns the argument.",
       "unsigned", "staticGetArgument", (ins "unsigned":$arg), "",
       "return arg;">,
+    // Overloaded method (same name, 
diff erent arity) used to check that an
+    // external model can override an overloaded interface method by its source
+    // name. Both overloads have a default so other models stay unaffected.
+    InterfaceMethod<"Overloaded method, single argument.",
+      "unsigned", "getOverloadedValue", (ins "unsigned":$arg), "",
+      "return 1000 + arg;">,
+    InterfaceMethod<"Overloaded method, two arguments.",
+      "unsigned", "getOverloadedValue", (ins "unsigned":$arg, "unsigned":$arg2),
+      "", "return 2000 + arg + arg2;">,
+    InterfaceMethod<"Overloaded method, three arguments.",
+      "unsigned", "getOverloadedValue", (ins "unsigned":$arg, "unsigned":$arg2, "unsigned":$arg3),
+      "", "return 3000 + arg + arg2 + arg3;">,
   ];
 }
 

diff  --git a/mlir/tools/mlir-tblgen/OpInterfacesGen.cpp b/mlir/tools/mlir-tblgen/OpInterfacesGen.cpp
index ab8d534a99f19..8f4a2f3907a53 100644
--- a/mlir/tools/mlir-tblgen/OpInterfacesGen.cpp
+++ b/mlir/tools/mlir-tblgen/OpInterfacesGen.cpp
@@ -289,6 +289,10 @@ void InterfaceGenerator::emitModelDecl(const Interface &interface) {
     os << "  class " << modelClass << " : public Concept {\n  public:\n";
     os << "    using Interface = " << interface.getFullyQualifiedName()
        << ";\n";
+    // The Concept's function-pointer members and these wrapper signatures are
+    // named by the unique name so that overloaded interface methods stay
+    // distinct here; only the forward target (the concrete model call) uses the
+    // shared source name. Do not collapse these to getName().
     os << "    " << modelClass << "() : Concept{";
     llvm::interleaveComma(
         interface.getMethods(), os,
@@ -322,7 +326,9 @@ void InterfaceGenerator::emitModelDecl(const Interface &interface) {
     if (method.isStatic())
       os << "static ";
     emitCPPType(method.getReturnType(), os);
-    os << method.getUniqueName() << "(";
+    // External models declare methods by their non-unique source names so that
+    // overloaded methods can be overridden by implementers.
+    os << method.getName() << "(";
     if (!method.isStatic()) {
       emitCPPType(valueType, os);
       os << "tablegen_opaque_val";
@@ -394,8 +400,10 @@ void InterfaceGenerator::emitModelMethodsDef(const Interface &interface) {
     else
       os << "return static_cast<const " << valueTemplate << " *>(impl)->";
 
-    // Add the arguments to the call.
-    os << method.getUniqueName() << '(';
+    // Add the arguments to the call. Forward by the (possibly non-unique)
+    // method name so that overloaded interface methods resolve to the right
+    // concrete-model overload.
+    os << method.getName() << '(';
     if (!method.isStatic())
       os << "tablegen_opaque_val" << (method.arg_empty() ? "" : ", ");
     llvm::interleaveComma(
@@ -414,8 +422,9 @@ void InterfaceGenerator::emitModelMethodsDef(const Interface &interface) {
     os << "detail::" << interface.getName()
        << "InterfaceTraits::ExternalModel<ConcreteModel, " << valueTemplate
        << ">::";
-
-    os << method.getUniqueName() << "(";
+    // External models expose (possibly overloaded) methods by their original
+    // source names, hiding the internal name mangling from implementers.
+    os << method.getName() << "(";
     if (!method.isStatic()) {
       emitCPPType(valueType, os);
       os << "tablegen_opaque_val";

diff  --git a/mlir/unittests/IR/InterfaceAttachmentTest.cpp b/mlir/unittests/IR/InterfaceAttachmentTest.cpp
index e1e65dad27980..b8dc851cdc501 100644
--- a/mlir/unittests/IR/InterfaceAttachmentTest.cpp
+++ b/mlir/unittests/IR/InterfaceAttachmentTest.cpp
@@ -90,6 +90,51 @@ TEST(InterfaceAttachment, Type) {
   EXPECT_FALSE(isa<TestExternalTypeInterface>(i8other));
 }
 
+/// External interface model that overrides the OVERLOADED `getOverloadedValue`
+/// methods by their source name (both arities).
+struct OverloadModel
+    : public TestExternalTypeInterface::ExternalModel<OverloadModel,
+                                                      IntegerType> {
+  using Base =
+      TestExternalTypeInterface::ExternalModel<OverloadModel, IntegerType>;
+  using Base::getOverloadedValue;
+  // Required (non-default) methods of the interface.
+  unsigned getBitwidthPlusArg(Type type, unsigned arg) const {
+    return type.getIntOrFloatBitWidth() + arg;
+  }
+  static unsigned staticGetSomeValuePlusArg(unsigned arg) { return 42 + arg; }
+
+  // Overrides of the two overloads, by source name.
+  unsigned getOverloadedValue(Type type, unsigned arg) const { return arg; }
+  unsigned getOverloadedValue(Type type, unsigned arg, unsigned arg2) const {
+    return arg * arg2;
+  }
+};
+
+// Verify that an external model is able to override an overloaded interface
+// method by its source name and the generated forwarders dispatch there rather
+// than fall back to the inherited default.
+TEST(InterfaceAttachment, OverloadedExternalModel) {
+  MLIRContext context;
+  IntegerType i8 = IntegerType::get(&context, 8);
+  IntegerType::attachInterface<OverloadModel>(context);
+  TestExternalTypeInterface iface = dyn_cast<TestExternalTypeInterface>(i8);
+  ASSERT_TRUE(iface != nullptr);
+
+  // First overload (unique name == source name): dispatched to the override.
+  // The default implementation would have returned 1000 + 7 = 1007 instead.
+  EXPECT_EQ(iface.getOverloadedValue(7u), 7u);
+
+  // Second overload: overridden by source name. If the forwarder dispatches by
+  // the mangled unique name instead, the override is missed and the inherited
+  // default (2000 + 6 + 7 == 2013) runs.
+  EXPECT_EQ(iface.getOverloadedValue(6u, 7u), 42u);
+
+  // Third overload: not overridden, should be dispatched to the base
+  // implementation. The default implementation returns 3000 + 6 + 7 + 8 = 3021.
+  EXPECT_EQ(iface.getOverloadedValue(6u, 7u, 8u), 3021u);
+}
+
 /// External interface model for the test type from the test dialect.
 struct TestTypeModel
     : public TestExternalTypeInterface::ExternalModel<TestTypeModel,


        


More information about the Mlir-commits mailing list