[Mlir-commits] [mlir] [mlir][CAPI] Add C API for creating dynamic dialects, ops, types, and attrs (PR #187239)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Thu Mar 19 12:17:04 PDT 2026


https://github.com/edg-l updated https://github.com/llvm/llvm-project/pull/187239

>From 5b7171969c59747c3e5c8df9e6abf8240c3bf7cb Mon Sep 17 00:00:00 2001
From: Edgar Luque <git at edgl.dev>
Date: Wed, 18 Mar 2026 11:37:56 +0100
Subject: [PATCH 1/2] [mlir][CAPI] Add C API for creating dynamic dialects,
 ops, types, and attrs

The existing ExtensibleDialect C API exposes functions for looking up
and instantiating dynamic types/attrs, and for attaching traits to
dynamic ops. However, there is no way to *create* a DynamicDialect,
DynamicOpDefinition, DynamicTypeDefinition, or DynamicAttrDefinition
from C.

This patch adds the missing creation APIs:
- mlirDynamicDialectCreate: wraps MLIRContext::getOrLoadDynamicDialect
- mlirDynamicOpDefinitionCreate + mlirDynamicDialectRegisterOp
- mlirDynamicTypeDefinitionCreate + mlirDynamicDialectRegisterType
- mlirDynamicAttrDefinitionCreate + mlirDynamicDialectRegisterAttr

Op definitions accept C callback functions for verify and verifyRegion.
Type and attribute definitions accept C callback functions for parameter
verification.

This enables languages with C FFI (Rust, Zig, etc.) to define complete
MLIR dialects at runtime without C++ code.
---
 mlir/include/mlir-c/ExtensibleDialect.h | 116 ++++++++++++++++
 mlir/lib/CAPI/IR/ExtensibleDialect.cpp  | 129 ++++++++++++++++++
 mlir/test/CAPI/CMakeLists.txt           |   6 +
 mlir/test/CAPI/extensible_dialect.c     | 172 ++++++++++++++++++++++++
 mlir/test/CMakeLists.txt                |   1 +
 5 files changed, 424 insertions(+)
 create mode 100644 mlir/test/CAPI/extensible_dialect.c

diff --git a/mlir/include/mlir-c/ExtensibleDialect.h b/mlir/include/mlir-c/ExtensibleDialect.h
index fee26772e1560..c0770a8920981 100644
--- a/mlir/include/mlir-c/ExtensibleDialect.h
+++ b/mlir/include/mlir-c/ExtensibleDialect.h
@@ -31,12 +31,128 @@ extern "C" {
   };                                                                           \
   typedef struct name name
 
+DEFINE_C_API_STRUCT(MlirDynamicDialect, void);
+DEFINE_C_API_STRUCT(MlirDynamicOpDefinition, void);
 DEFINE_C_API_STRUCT(MlirDynamicOpTrait, void);
 DEFINE_C_API_STRUCT(MlirDynamicTypeDefinition, void);
 DEFINE_C_API_STRUCT(MlirDynamicAttrDefinition, void);
 
 #undef DEFINE_C_API_STRUCT
 
+//===----------------------------------------------------------------------===//
+/// Dynamic dialect creation
+//===----------------------------------------------------------------------===//
+
+/// Create a new dynamic dialect with the given name and register it with the
+/// context. If a dialect with the same name already exists, returns the
+/// existing one. The context takes ownership of the dialect. The returned
+/// handle is valid as long as the context is alive and must not be freed.
+MLIR_CAPI_EXPORTED MlirDynamicDialect
+mlirDynamicDialectCreate(MlirContext ctx, MlirStringRef name);
+
+/// Get the underlying MlirDialect from a MlirDynamicDialect.
+MLIR_CAPI_EXPORTED MlirDialect
+mlirDynamicDialectGetDialect(MlirDynamicDialect dialect);
+
+//===----------------------------------------------------------------------===//
+/// Dynamic op definition creation
+//===----------------------------------------------------------------------===//
+
+/// Callback to verify a dynamic op.
+typedef MlirLogicalResult (*MlirDynamicOpVerifyFn)(MlirOperation op,
+                                                   void *userData);
+
+/// Create a dynamic op definition with the given name and verifier callbacks.
+/// The name should be the bare op name (e.g. "constant"), not the
+/// dialect-qualified name. The definition is NOT yet registered with the
+/// dialect; call mlirDynamicDialectRegisterOp to register it.
+/// Both \p verifyFn and \p verifyRegionFn may be NULL, in which case
+/// verification always succeeds. \p userData is shared between both callbacks.
+/// If the definition is not registered, it must be destroyed with
+/// mlirDynamicOpDefinitionDestroy to avoid a memory leak.
+MLIR_CAPI_EXPORTED MlirDynamicOpDefinition mlirDynamicOpDefinitionCreate(
+    MlirDynamicDialect dialect, MlirStringRef name,
+    MlirDynamicOpVerifyFn verifyFn, MlirDynamicOpVerifyFn verifyRegionFn,
+    void *userData);
+
+/// Destroy a dynamic op definition that was not registered with a dialect.
+MLIR_CAPI_EXPORTED void
+mlirDynamicOpDefinitionDestroy(MlirDynamicOpDefinition opDef);
+
+/// Register a dynamic op definition with its parent dialect.
+/// This transfers ownership of the definition to the dialect.
+/// After this call, ops with this name can be created using
+/// mlirOperationCreate with the dialect-qualified name.
+MLIR_CAPI_EXPORTED void
+mlirDynamicDialectRegisterOp(MlirDynamicDialect dialect,
+                             MlirDynamicOpDefinition opDef);
+
+//===----------------------------------------------------------------------===//
+/// Dynamic type definition creation
+//===----------------------------------------------------------------------===//
+
+/// Callback to verify a dynamic type's parameters.
+typedef MlirLogicalResult (*MlirDynamicTypeVerifyFn)(
+    intptr_t nParams, MlirAttribute const *params, void *userData);
+
+/// Create a dynamic type definition with the given name and verifier.
+/// The name should be the bare type name (e.g. "mystruct"), not the
+/// dialect-qualified name. The definition is NOT yet registered with the
+/// dialect; call mlirDynamicDialectRegisterType to register it.
+/// \p verifyFn may be NULL, in which case verification always succeeds.
+/// Note: the verifier callback does not receive diagnostic context; failures
+/// are reported without a custom error message.
+/// If the definition is not registered, it must be destroyed with
+/// mlirDynamicTypeDefinitionDestroy to avoid a memory leak.
+MLIR_CAPI_EXPORTED MlirDynamicTypeDefinition mlirDynamicTypeDefinitionCreate(
+    MlirDynamicDialect dialect, MlirStringRef name,
+    MlirDynamicTypeVerifyFn verifyFn, void *userData);
+
+/// Destroy a dynamic type definition that was not registered with a dialect.
+MLIR_CAPI_EXPORTED void
+mlirDynamicTypeDefinitionDestroy(MlirDynamicTypeDefinition typeDef);
+
+/// Register a dynamic type definition with its parent dialect.
+/// This transfers ownership of the definition to the dialect.
+MLIR_CAPI_EXPORTED void
+mlirDynamicDialectRegisterType(MlirDynamicDialect dialect,
+                               MlirDynamicTypeDefinition typeDef);
+
+//===----------------------------------------------------------------------===//
+/// Dynamic attribute definition creation
+//===----------------------------------------------------------------------===//
+
+/// Callback to verify a dynamic attribute's parameters.
+typedef MlirLogicalResult (*MlirDynamicAttrVerifyFn)(
+    intptr_t nParams, MlirAttribute const *params, void *userData);
+
+/// Create a dynamic attribute definition with the given name and verifier.
+/// The name should be the bare attr name, not the dialect-qualified name.
+/// The definition is NOT yet registered with the dialect; call
+/// mlirDynamicDialectRegisterAttr to register it.
+/// \p verifyFn may be NULL, in which case verification always succeeds.
+/// Note: the verifier callback does not receive diagnostic context; failures
+/// are reported without a custom error message.
+/// If the definition is not registered, it must be destroyed with
+/// mlirDynamicAttrDefinitionDestroy to avoid a memory leak.
+MLIR_CAPI_EXPORTED MlirDynamicAttrDefinition mlirDynamicAttrDefinitionCreate(
+    MlirDynamicDialect dialect, MlirStringRef name,
+    MlirDynamicAttrVerifyFn verifyFn, void *userData);
+
+/// Destroy a dynamic attr definition that was not registered with a dialect.
+MLIR_CAPI_EXPORTED void
+mlirDynamicAttrDefinitionDestroy(MlirDynamicAttrDefinition attrDef);
+
+/// Register a dynamic attribute definition with its parent dialect.
+/// This transfers ownership of the definition to the dialect.
+MLIR_CAPI_EXPORTED void
+mlirDynamicDialectRegisterAttr(MlirDynamicDialect dialect,
+                               MlirDynamicAttrDefinition attrDef);
+
+//===----------------------------------------------------------------------===//
+/// Dynamic op trait APIs
+//===----------------------------------------------------------------------===//
+
 /// Attach a dynamic op trait to the given operation name.
 /// Note that the operation name must be modeled by dynamic dialect and must be
 /// registered.
diff --git a/mlir/lib/CAPI/IR/ExtensibleDialect.cpp b/mlir/lib/CAPI/IR/ExtensibleDialect.cpp
index 60a7f7d9064eb..2178196dbb232 100644
--- a/mlir/lib/CAPI/IR/ExtensibleDialect.cpp
+++ b/mlir/lib/CAPI/IR/ExtensibleDialect.cpp
@@ -9,15 +9,144 @@
 #include "mlir-c/ExtensibleDialect.h"
 #include "mlir/CAPI/IR.h"
 #include "mlir/CAPI/Support.h"
+#include "mlir/CAPI/Wrap.h"
 #include "mlir/IR/ExtensibleDialect.h"
+#include "mlir/IR/MLIRContext.h"
 #include "mlir/IR/OperationSupport.h"
 
 using namespace mlir;
 
+DEFINE_C_API_PTR_METHODS(MlirDynamicDialect, DynamicDialect)
+DEFINE_C_API_PTR_METHODS(MlirDynamicOpDefinition, DynamicOpDefinition)
 DEFINE_C_API_PTR_METHODS(MlirDynamicOpTrait, DynamicOpTrait)
 DEFINE_C_API_PTR_METHODS(MlirDynamicTypeDefinition, DynamicTypeDefinition)
 DEFINE_C_API_PTR_METHODS(MlirDynamicAttrDefinition, DynamicAttrDefinition)
 
+//===----------------------------------------------------------------------===//
+// Dynamic dialect creation
+//===----------------------------------------------------------------------===//
+
+MlirDynamicDialect mlirDynamicDialectCreate(MlirContext ctx,
+                                            MlirStringRef name) {
+  DynamicDialect *dialect = unwrap(ctx)->getOrLoadDynamicDialect(
+      unwrap(name), [](DynamicDialect *) {});
+  return wrap(dialect);
+}
+
+MlirDialect mlirDynamicDialectGetDialect(MlirDynamicDialect dialect) {
+  return wrap(static_cast<Dialect *>(unwrap(dialect)));
+}
+
+//===----------------------------------------------------------------------===//
+// Dynamic op definition creation
+//===----------------------------------------------------------------------===//
+
+MlirDynamicOpDefinition
+mlirDynamicOpDefinitionCreate(MlirDynamicDialect dialect, MlirStringRef name,
+                              MlirDynamicOpVerifyFn verifyFn,
+                              MlirDynamicOpVerifyFn verifyRegionFn,
+                              void *userData) {
+  auto cppVerifyFn = [verifyFn, userData](Operation *op) -> LogicalResult {
+    if (!verifyFn)
+      return success();
+    return unwrap(verifyFn(wrap(op), userData));
+  };
+  auto cppVerifyRegionFn = [verifyRegionFn,
+                            userData](Operation *op) -> LogicalResult {
+    if (!verifyRegionFn)
+      return success();
+    return unwrap(verifyRegionFn(wrap(op), userData));
+  };
+  std::unique_ptr<DynamicOpDefinition> opDef = DynamicOpDefinition::get(
+      unwrap(name), unwrap(dialect), std::move(cppVerifyFn),
+      std::move(cppVerifyRegionFn));
+  return wrap(opDef.release());
+}
+
+void mlirDynamicOpDefinitionDestroy(MlirDynamicOpDefinition opDef) {
+  delete unwrap(opDef);
+}
+
+void mlirDynamicDialectRegisterOp(MlirDynamicDialect dialect,
+                                  MlirDynamicOpDefinition opDef) {
+  unwrap(dialect)->registerDynamicOp(
+      std::unique_ptr<DynamicOpDefinition>(unwrap(opDef)));
+}
+
+//===----------------------------------------------------------------------===//
+// Dynamic type definition creation
+//===----------------------------------------------------------------------===//
+
+MlirDynamicTypeDefinition
+mlirDynamicTypeDefinitionCreate(MlirDynamicDialect dialect, MlirStringRef name,
+                                MlirDynamicTypeVerifyFn verifyFn,
+                                void *userData) {
+  auto cppVerifyFn = [verifyFn,
+                      userData](function_ref<InFlightDiagnostic()> emitError,
+                                ArrayRef<Attribute> params) -> LogicalResult {
+    if (!verifyFn)
+      return success();
+    SmallVector<MlirAttribute> cParams;
+    cParams.reserve(params.size());
+    for (Attribute p : params)
+      cParams.push_back(wrap(p));
+    return unwrap(verifyFn(static_cast<intptr_t>(cParams.size()),
+                           cParams.data(), userData));
+  };
+  std::unique_ptr<DynamicTypeDefinition> typeDef = DynamicTypeDefinition::get(
+      unwrap(name), unwrap(dialect), std::move(cppVerifyFn));
+  return wrap(typeDef.release());
+}
+
+void mlirDynamicTypeDefinitionDestroy(MlirDynamicTypeDefinition typeDef) {
+  delete unwrap(typeDef);
+}
+
+void mlirDynamicDialectRegisterType(MlirDynamicDialect dialect,
+                                    MlirDynamicTypeDefinition typeDef) {
+  unwrap(dialect)->registerDynamicType(
+      std::unique_ptr<DynamicTypeDefinition>(unwrap(typeDef)));
+}
+
+//===----------------------------------------------------------------------===//
+// Dynamic attribute definition creation
+//===----------------------------------------------------------------------===//
+
+MlirDynamicAttrDefinition
+mlirDynamicAttrDefinitionCreate(MlirDynamicDialect dialect, MlirStringRef name,
+                                MlirDynamicAttrVerifyFn verifyFn,
+                                void *userData) {
+  auto cppVerifyFn = [verifyFn,
+                      userData](function_ref<InFlightDiagnostic()> emitError,
+                                ArrayRef<Attribute> params) -> LogicalResult {
+    if (!verifyFn)
+      return success();
+    SmallVector<MlirAttribute> cParams;
+    cParams.reserve(params.size());
+    for (Attribute p : params)
+      cParams.push_back(wrap(p));
+    return unwrap(verifyFn(static_cast<intptr_t>(cParams.size()),
+                           cParams.data(), userData));
+  };
+  std::unique_ptr<DynamicAttrDefinition> attrDef = DynamicAttrDefinition::get(
+      unwrap(name), unwrap(dialect), std::move(cppVerifyFn));
+  return wrap(attrDef.release());
+}
+
+void mlirDynamicAttrDefinitionDestroy(MlirDynamicAttrDefinition attrDef) {
+  delete unwrap(attrDef);
+}
+
+void mlirDynamicDialectRegisterAttr(MlirDynamicDialect dialect,
+                                    MlirDynamicAttrDefinition attrDef) {
+  unwrap(dialect)->registerDynamicAttr(
+      std::unique_ptr<DynamicAttrDefinition>(unwrap(attrDef)));
+}
+
+//===----------------------------------------------------------------------===//
+// Dynamic op trait APIs
+//===----------------------------------------------------------------------===//
+
 bool mlirDynamicOpTraitAttach(MlirDynamicOpTrait dynamicOpTrait,
                               MlirStringRef opName, MlirContext context) {
   std::optional<RegisteredOperationName> opNameFound =
diff --git a/mlir/test/CAPI/CMakeLists.txt b/mlir/test/CAPI/CMakeLists.txt
index d45142510a496..b8509674c5b87 100644
--- a/mlir/test/CAPI/CMakeLists.txt
+++ b/mlir/test/CAPI/CMakeLists.txt
@@ -48,6 +48,12 @@ _add_capi_test_executable(mlir-capi-ir-test
     MLIRCAPIRegisterEverything
 )
 
+_add_capi_test_executable(mlir-capi-extensible-dialect-test
+  extensible_dialect.c
+  LINK_LIBS PRIVATE
+    MLIRCAPIIR
+)
+
 _add_capi_test_executable(mlir-capi-irdl-test
   irdl.c
   LINK_LIBS PRIVATE
diff --git a/mlir/test/CAPI/extensible_dialect.c b/mlir/test/CAPI/extensible_dialect.c
new file mode 100644
index 0000000000000..cf1706f10ba84
--- /dev/null
+++ b/mlir/test/CAPI/extensible_dialect.c
@@ -0,0 +1,172 @@
+//===- extensible_dialect.c - Test C API for extensible dialect creation
+//---===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+/* RUN: mlir-capi-extensible-dialect-test 2>&1 | FileCheck %s
+ */
+
+#include "mlir-c/BuiltinAttributes.h"
+#include "mlir-c/BuiltinTypes.h"
+#include "mlir-c/ExtensibleDialect.h"
+#include "mlir-c/IR.h"
+#include "mlir-c/Support.h"
+
+#include <stdio.h>
+
+static MlirStringRef strref(const char *s) {
+  return mlirStringRefCreateFromCString(s);
+}
+
+static MlirLogicalResult successTypeVerify(intptr_t nParams,
+                                           MlirAttribute const *params,
+                                           void *userData) {
+  (void)nParams;
+  (void)params;
+  (void)userData;
+  return mlirLogicalResultSuccess();
+}
+
+static MlirLogicalResult successAttrVerify(intptr_t nParams,
+                                           MlirAttribute const *params,
+                                           void *userData) {
+  (void)nParams;
+  (void)params;
+  (void)userData;
+  return mlirLogicalResultSuccess();
+}
+
+void testDynamicDialectCreation(MlirContext ctx) {
+  // Create a dynamic dialect.
+  MlirDynamicDialect testDialect =
+      mlirDynamicDialectCreate(ctx, strref("test_dyn"));
+  MlirDialect dialect = mlirDynamicDialectGetDialect(testDialect);
+
+  // CHECK: test_dyn is extensible: 1
+  fprintf(stderr, "test_dyn is extensible: %d\n",
+          mlirDialectIsAExtensibleDialect(dialect));
+}
+
+void testDynamicOpCreation(MlirContext ctx) {
+  MlirDynamicDialect testDialect =
+      mlirDynamicDialectCreate(ctx, strref("testop"));
+
+  // Define and register a simple op with NULL verifiers.
+  MlirDynamicOpDefinition opDef = mlirDynamicOpDefinitionCreate(
+      testDialect, strref("my_op"), NULL, NULL, NULL);
+  mlirDynamicDialectRegisterOp(testDialect, opDef);
+
+  // Parse and dump IR that uses this op.
+  const char *irStr = "module { \"testop.my_op\"() : () -> () }";
+  MlirModule module = mlirModuleCreateParse(ctx, strref(irStr));
+  if (mlirModuleIsNull(module)) {
+    fprintf(stderr, "failed to parse module\n");
+    return;
+  }
+
+  // CHECK:      module {
+  // CHECK-NEXT:   "testop.my_op"() : () -> ()
+  // CHECK-NEXT: }
+  mlirOperationDump(mlirModuleGetOperation(module));
+  mlirModuleDestroy(module);
+}
+
+void testDynamicTypeCreation(MlirContext ctx) {
+  MlirDynamicDialect testDialect =
+      mlirDynamicDialectCreate(ctx, strref("testtype"));
+
+  // Define and register a dynamic type.
+  MlirDynamicTypeDefinition typeDef = mlirDynamicTypeDefinitionCreate(
+      testDialect, strref("my_type"), successTypeVerify, NULL);
+  mlirDynamicDialectRegisterType(testDialect, typeDef);
+
+  // Look it up to verify registration worked.
+  MlirDialect dialect = mlirDynamicDialectGetDialect(testDialect);
+  MlirDynamicTypeDefinition lookedUp =
+      mlirExtensibleDialectLookupTypeDefinition(dialect, strref("my_type"));
+
+  // CHECK: type lookup succeeded: 1
+  fprintf(stderr, "type lookup succeeded: %d\n", lookedUp.ptr != NULL);
+
+  // Create an instance with no parameters.
+  MlirType dynType = mlirDynamicTypeGet(lookedUp, NULL, 0);
+
+  // CHECK: is dynamic type: 1
+  fprintf(stderr, "is dynamic type: %d\n", mlirTypeIsADynamicType(dynType));
+
+  // CHECK: dynamic type num params: 0
+  fprintf(stderr, "dynamic type num params: %ld\n",
+          (long)mlirDynamicTypeGetNumParams(dynType));
+}
+
+void testDynamicAttrCreation(MlirContext ctx) {
+  MlirDynamicDialect testDialect =
+      mlirDynamicDialectCreate(ctx, strref("testattr"));
+
+  // Define and register a dynamic attribute.
+  MlirDynamicAttrDefinition attrDef = mlirDynamicAttrDefinitionCreate(
+      testDialect, strref("my_attr"), successAttrVerify, NULL);
+  mlirDynamicDialectRegisterAttr(testDialect, attrDef);
+
+  // Look it up to verify registration worked.
+  MlirDialect dialect = mlirDynamicDialectGetDialect(testDialect);
+  MlirDynamicAttrDefinition lookedUp =
+      mlirExtensibleDialectLookupAttrDefinition(dialect, strref("my_attr"));
+
+  // CHECK: attr lookup succeeded: 1
+  fprintf(stderr, "attr lookup succeeded: %d\n", lookedUp.ptr != NULL);
+
+  // Create an instance with no parameters.
+  MlirAttribute dynAttr = mlirDynamicAttrGet(lookedUp, NULL, 0);
+
+  // CHECK: is dynamic attr: 1
+  fprintf(stderr, "is dynamic attr: %d\n",
+          mlirAttributeIsADynamicAttr(dynAttr));
+
+  // CHECK: dynamic attr num params: 0
+  fprintf(stderr, "dynamic attr num params: %ld\n",
+          (long)mlirDynamicAttrGetNumParams(dynAttr));
+}
+
+void testDynamicTypeWithParams(MlirContext ctx) {
+  MlirDynamicDialect testDialect =
+      mlirDynamicDialectCreate(ctx, strref("testparams"));
+
+  // Define and register a parameterized type.
+  MlirDynamicTypeDefinition typeDef = mlirDynamicTypeDefinitionCreate(
+      testDialect, strref("pair"), successTypeVerify, NULL);
+  mlirDynamicDialectRegisterType(testDialect, typeDef);
+
+  MlirDialect dialect = mlirDynamicDialectGetDialect(testDialect);
+  MlirDynamicTypeDefinition lookedUp =
+      mlirExtensibleDialectLookupTypeDefinition(dialect, strref("pair"));
+
+  // Create an instance with two integer attribute parameters.
+  MlirAttribute params[2];
+  params[0] = mlirIntegerAttrGet(mlirIntegerTypeGet(ctx, 32), 42);
+  params[1] = mlirIntegerAttrGet(mlirIntegerTypeGet(ctx, 32), 7);
+  MlirType dynType = mlirDynamicTypeGet(lookedUp, params, 2);
+
+  // CHECK: parameterized type num params: 2
+  fprintf(stderr, "parameterized type num params: %ld\n",
+          (long)mlirDynamicTypeGetNumParams(dynType));
+}
+
+int main(void) {
+  MlirContext ctx = mlirContextCreate();
+  mlirContextSetAllowUnregisteredDialects(ctx, true);
+
+  testDynamicDialectCreation(ctx);
+  testDynamicOpCreation(ctx);
+  testDynamicTypeCreation(ctx);
+  testDynamicAttrCreation(ctx);
+  testDynamicTypeWithParams(ctx);
+
+  mlirContextDestroy(ctx);
+  return 0;
+}
diff --git a/mlir/test/CMakeLists.txt b/mlir/test/CMakeLists.txt
index e0c32cd4bd9a0..d24fb79753783 100644
--- a/mlir/test/CMakeLists.txt
+++ b/mlir/test/CMakeLists.txt
@@ -102,6 +102,7 @@ configure_lit_site_cfg(
   )
 
 set(MLIR_TEST_DEPENDS
+  mlir-capi-extensible-dialect-test
   mlir-capi-ir-test
   mlir-capi-irdl-test
   mlir-capi-llvm-test

>From 18007b7036242e141c661a87ce1ca99450f26ac8 Mon Sep 17 00:00:00 2001
From: Edgar Luque <git at edgl.dev>
Date: Thu, 19 Mar 2026 20:15:24 +0100
Subject: [PATCH 2/2] [mlir][CAPI] Use callback structs and rename
 mlirDynamicDialectAsDialect

---
 mlir/include/mlir-c/ExtensibleDialect.h | 63 ++++++++++++++++---------
 mlir/lib/CAPI/IR/ExtensibleDialect.cpp  | 37 +++++++--------
 mlir/test/CAPI/extensible_dialect.c     | 25 ++++++----
 3 files changed, 76 insertions(+), 49 deletions(-)

diff --git a/mlir/include/mlir-c/ExtensibleDialect.h b/mlir/include/mlir-c/ExtensibleDialect.h
index c0770a8920981..4a8733407faba 100644
--- a/mlir/include/mlir-c/ExtensibleDialect.h
+++ b/mlir/include/mlir-c/ExtensibleDialect.h
@@ -52,28 +52,35 @@ mlirDynamicDialectCreate(MlirContext ctx, MlirStringRef name);
 
 /// Get the underlying MlirDialect from a MlirDynamicDialect.
 MLIR_CAPI_EXPORTED MlirDialect
-mlirDynamicDialectGetDialect(MlirDynamicDialect dialect);
+mlirDynamicDialectAsDialect(MlirDynamicDialect dialect);
 
 //===----------------------------------------------------------------------===//
 /// Dynamic op definition creation
 //===----------------------------------------------------------------------===//
 
-/// Callback to verify a dynamic op.
-typedef MlirLogicalResult (*MlirDynamicOpVerifyFn)(MlirOperation op,
-                                                   void *userData);
+/// Callbacks for a dynamic op definition. All fields may be NULL, in which
+/// case the corresponding action is a no-op / always succeeds.
+typedef struct {
+  /// Optional constructor for the user data.
+  void (*construct)(void *userData);
+  /// Optional destructor for the user data.
+  void (*destruct)(void *userData);
+  /// The callback function to verify the operation.
+  MlirLogicalResult (*verify)(MlirOperation op, void *userData);
+  /// The callback function to verify the operation with access to regions.
+  MlirLogicalResult (*verifyRegion)(MlirOperation op, void *userData);
+} MlirDynamicOpDefinitionCallbacks;
 
-/// Create a dynamic op definition with the given name and verifier callbacks.
+/// Create a dynamic op definition with the given name and callbacks.
 /// The name should be the bare op name (e.g. "constant"), not the
 /// dialect-qualified name. The definition is NOT yet registered with the
 /// dialect; call mlirDynamicDialectRegisterOp to register it.
-/// Both \p verifyFn and \p verifyRegionFn may be NULL, in which case
-/// verification always succeeds. \p userData is shared between both callbacks.
+/// \p userData is shared between all callbacks.
 /// If the definition is not registered, it must be destroyed with
 /// mlirDynamicOpDefinitionDestroy to avoid a memory leak.
 MLIR_CAPI_EXPORTED MlirDynamicOpDefinition mlirDynamicOpDefinitionCreate(
     MlirDynamicDialect dialect, MlirStringRef name,
-    MlirDynamicOpVerifyFn verifyFn, MlirDynamicOpVerifyFn verifyRegionFn,
-    void *userData);
+    MlirDynamicOpDefinitionCallbacks callbacks, void *userData);
 
 /// Destroy a dynamic op definition that was not registered with a dialect.
 MLIR_CAPI_EXPORTED void
@@ -91,22 +98,29 @@ mlirDynamicDialectRegisterOp(MlirDynamicDialect dialect,
 /// Dynamic type definition creation
 //===----------------------------------------------------------------------===//
 
-/// Callback to verify a dynamic type's parameters.
-typedef MlirLogicalResult (*MlirDynamicTypeVerifyFn)(
-    intptr_t nParams, MlirAttribute const *params, void *userData);
+/// Callbacks for a dynamic type definition. All fields may be NULL, in which
+/// case the corresponding action is a no-op / always succeeds.
+typedef struct {
+  /// Optional constructor for the user data.
+  void (*construct)(void *userData);
+  /// Optional destructor for the user data.
+  void (*destruct)(void *userData);
+  /// The callback function to verify the type's parameters.
+  MlirLogicalResult (*verify)(intptr_t nParams, MlirAttribute const *params,
+                              void *userData);
+} MlirDynamicTypeDefinitionCallbacks;
 
-/// Create a dynamic type definition with the given name and verifier.
+/// Create a dynamic type definition with the given name and callbacks.
 /// The name should be the bare type name (e.g. "mystruct"), not the
 /// dialect-qualified name. The definition is NOT yet registered with the
 /// dialect; call mlirDynamicDialectRegisterType to register it.
-/// \p verifyFn may be NULL, in which case verification always succeeds.
 /// Note: the verifier callback does not receive diagnostic context; failures
 /// are reported without a custom error message.
 /// If the definition is not registered, it must be destroyed with
 /// mlirDynamicTypeDefinitionDestroy to avoid a memory leak.
 MLIR_CAPI_EXPORTED MlirDynamicTypeDefinition mlirDynamicTypeDefinitionCreate(
     MlirDynamicDialect dialect, MlirStringRef name,
-    MlirDynamicTypeVerifyFn verifyFn, void *userData);
+    MlirDynamicTypeDefinitionCallbacks callbacks, void *userData);
 
 /// Destroy a dynamic type definition that was not registered with a dialect.
 MLIR_CAPI_EXPORTED void
@@ -122,22 +136,29 @@ mlirDynamicDialectRegisterType(MlirDynamicDialect dialect,
 /// Dynamic attribute definition creation
 //===----------------------------------------------------------------------===//
 
-/// Callback to verify a dynamic attribute's parameters.
-typedef MlirLogicalResult (*MlirDynamicAttrVerifyFn)(
-    intptr_t nParams, MlirAttribute const *params, void *userData);
+/// Callbacks for a dynamic attribute definition. All fields may be NULL, in
+/// which case the corresponding action is a no-op / always succeeds.
+typedef struct {
+  /// Optional constructor for the user data.
+  void (*construct)(void *userData);
+  /// Optional destructor for the user data.
+  void (*destruct)(void *userData);
+  /// The callback function to verify the attribute's parameters.
+  MlirLogicalResult (*verify)(intptr_t nParams, MlirAttribute const *params,
+                              void *userData);
+} MlirDynamicAttrDefinitionCallbacks;
 
-/// Create a dynamic attribute definition with the given name and verifier.
+/// Create a dynamic attribute definition with the given name and callbacks.
 /// The name should be the bare attr name, not the dialect-qualified name.
 /// The definition is NOT yet registered with the dialect; call
 /// mlirDynamicDialectRegisterAttr to register it.
-/// \p verifyFn may be NULL, in which case verification always succeeds.
 /// Note: the verifier callback does not receive diagnostic context; failures
 /// are reported without a custom error message.
 /// If the definition is not registered, it must be destroyed with
 /// mlirDynamicAttrDefinitionDestroy to avoid a memory leak.
 MLIR_CAPI_EXPORTED MlirDynamicAttrDefinition mlirDynamicAttrDefinitionCreate(
     MlirDynamicDialect dialect, MlirStringRef name,
-    MlirDynamicAttrVerifyFn verifyFn, void *userData);
+    MlirDynamicAttrDefinitionCallbacks callbacks, void *userData);
 
 /// Destroy a dynamic attr definition that was not registered with a dialect.
 MLIR_CAPI_EXPORTED void
diff --git a/mlir/lib/CAPI/IR/ExtensibleDialect.cpp b/mlir/lib/CAPI/IR/ExtensibleDialect.cpp
index 2178196dbb232..960ef7c5dea19 100644
--- a/mlir/lib/CAPI/IR/ExtensibleDialect.cpp
+++ b/mlir/lib/CAPI/IR/ExtensibleDialect.cpp
@@ -33,7 +33,7 @@ MlirDynamicDialect mlirDynamicDialectCreate(MlirContext ctx,
   return wrap(dialect);
 }
 
-MlirDialect mlirDynamicDialectGetDialect(MlirDynamicDialect dialect) {
+MlirDialect mlirDynamicDialectAsDialect(MlirDynamicDialect dialect) {
   return wrap(static_cast<Dialect *>(unwrap(dialect)));
 }
 
@@ -43,19 +43,18 @@ MlirDialect mlirDynamicDialectGetDialect(MlirDynamicDialect dialect) {
 
 MlirDynamicOpDefinition
 mlirDynamicOpDefinitionCreate(MlirDynamicDialect dialect, MlirStringRef name,
-                              MlirDynamicOpVerifyFn verifyFn,
-                              MlirDynamicOpVerifyFn verifyRegionFn,
+                              MlirDynamicOpDefinitionCallbacks callbacks,
                               void *userData) {
-  auto cppVerifyFn = [verifyFn, userData](Operation *op) -> LogicalResult {
-    if (!verifyFn)
+  auto cppVerifyFn = [callbacks, userData](Operation *op) -> LogicalResult {
+    if (!callbacks.verify)
       return success();
-    return unwrap(verifyFn(wrap(op), userData));
+    return unwrap(callbacks.verify(wrap(op), userData));
   };
-  auto cppVerifyRegionFn = [verifyRegionFn,
+  auto cppVerifyRegionFn = [callbacks,
                             userData](Operation *op) -> LogicalResult {
-    if (!verifyRegionFn)
+    if (!callbacks.verifyRegion)
       return success();
-    return unwrap(verifyRegionFn(wrap(op), userData));
+    return unwrap(callbacks.verifyRegion(wrap(op), userData));
   };
   std::unique_ptr<DynamicOpDefinition> opDef = DynamicOpDefinition::get(
       unwrap(name), unwrap(dialect), std::move(cppVerifyFn),
@@ -79,19 +78,19 @@ void mlirDynamicDialectRegisterOp(MlirDynamicDialect dialect,
 
 MlirDynamicTypeDefinition
 mlirDynamicTypeDefinitionCreate(MlirDynamicDialect dialect, MlirStringRef name,
-                                MlirDynamicTypeVerifyFn verifyFn,
+                                MlirDynamicTypeDefinitionCallbacks callbacks,
                                 void *userData) {
-  auto cppVerifyFn = [verifyFn,
+  auto cppVerifyFn = [callbacks,
                       userData](function_ref<InFlightDiagnostic()> emitError,
                                 ArrayRef<Attribute> params) -> LogicalResult {
-    if (!verifyFn)
+    if (!callbacks.verify)
       return success();
     SmallVector<MlirAttribute> cParams;
     cParams.reserve(params.size());
     for (Attribute p : params)
       cParams.push_back(wrap(p));
-    return unwrap(verifyFn(static_cast<intptr_t>(cParams.size()),
-                           cParams.data(), userData));
+    return unwrap(callbacks.verify(static_cast<intptr_t>(cParams.size()),
+                                   cParams.data(), userData));
   };
   std::unique_ptr<DynamicTypeDefinition> typeDef = DynamicTypeDefinition::get(
       unwrap(name), unwrap(dialect), std::move(cppVerifyFn));
@@ -114,19 +113,19 @@ void mlirDynamicDialectRegisterType(MlirDynamicDialect dialect,
 
 MlirDynamicAttrDefinition
 mlirDynamicAttrDefinitionCreate(MlirDynamicDialect dialect, MlirStringRef name,
-                                MlirDynamicAttrVerifyFn verifyFn,
+                                MlirDynamicAttrDefinitionCallbacks callbacks,
                                 void *userData) {
-  auto cppVerifyFn = [verifyFn,
+  auto cppVerifyFn = [callbacks,
                       userData](function_ref<InFlightDiagnostic()> emitError,
                                 ArrayRef<Attribute> params) -> LogicalResult {
-    if (!verifyFn)
+    if (!callbacks.verify)
       return success();
     SmallVector<MlirAttribute> cParams;
     cParams.reserve(params.size());
     for (Attribute p : params)
       cParams.push_back(wrap(p));
-    return unwrap(verifyFn(static_cast<intptr_t>(cParams.size()),
-                           cParams.data(), userData));
+    return unwrap(callbacks.verify(static_cast<intptr_t>(cParams.size()),
+                                   cParams.data(), userData));
   };
   std::unique_ptr<DynamicAttrDefinition> attrDef = DynamicAttrDefinition::get(
       unwrap(name), unwrap(dialect), std::move(cppVerifyFn));
diff --git a/mlir/test/CAPI/extensible_dialect.c b/mlir/test/CAPI/extensible_dialect.c
index cf1706f10ba84..5734045f6366a 100644
--- a/mlir/test/CAPI/extensible_dialect.c
+++ b/mlir/test/CAPI/extensible_dialect.c
@@ -45,7 +45,7 @@ void testDynamicDialectCreation(MlirContext ctx) {
   // Create a dynamic dialect.
   MlirDynamicDialect testDialect =
       mlirDynamicDialectCreate(ctx, strref("test_dyn"));
-  MlirDialect dialect = mlirDynamicDialectGetDialect(testDialect);
+  MlirDialect dialect = mlirDynamicDialectAsDialect(testDialect);
 
   // CHECK: test_dyn is extensible: 1
   fprintf(stderr, "test_dyn is extensible: %d\n",
@@ -56,9 +56,10 @@ void testDynamicOpCreation(MlirContext ctx) {
   MlirDynamicDialect testDialect =
       mlirDynamicDialectCreate(ctx, strref("testop"));
 
-  // Define and register a simple op with NULL verifiers.
+  // Define and register a simple op with no callbacks.
+  MlirDynamicOpDefinitionCallbacks opCallbacks = {NULL, NULL, NULL, NULL};
   MlirDynamicOpDefinition opDef = mlirDynamicOpDefinitionCreate(
-      testDialect, strref("my_op"), NULL, NULL, NULL);
+      testDialect, strref("my_op"), opCallbacks, NULL);
   mlirDynamicDialectRegisterOp(testDialect, opDef);
 
   // Parse and dump IR that uses this op.
@@ -81,12 +82,14 @@ void testDynamicTypeCreation(MlirContext ctx) {
       mlirDynamicDialectCreate(ctx, strref("testtype"));
 
   // Define and register a dynamic type.
+  MlirDynamicTypeDefinitionCallbacks typeCallbacks = {NULL, NULL,
+                                                      successTypeVerify};
   MlirDynamicTypeDefinition typeDef = mlirDynamicTypeDefinitionCreate(
-      testDialect, strref("my_type"), successTypeVerify, NULL);
+      testDialect, strref("my_type"), typeCallbacks, NULL);
   mlirDynamicDialectRegisterType(testDialect, typeDef);
 
   // Look it up to verify registration worked.
-  MlirDialect dialect = mlirDynamicDialectGetDialect(testDialect);
+  MlirDialect dialect = mlirDynamicDialectAsDialect(testDialect);
   MlirDynamicTypeDefinition lookedUp =
       mlirExtensibleDialectLookupTypeDefinition(dialect, strref("my_type"));
 
@@ -109,12 +112,14 @@ void testDynamicAttrCreation(MlirContext ctx) {
       mlirDynamicDialectCreate(ctx, strref("testattr"));
 
   // Define and register a dynamic attribute.
+  MlirDynamicAttrDefinitionCallbacks attrCallbacks = {NULL, NULL,
+                                                      successAttrVerify};
   MlirDynamicAttrDefinition attrDef = mlirDynamicAttrDefinitionCreate(
-      testDialect, strref("my_attr"), successAttrVerify, NULL);
+      testDialect, strref("my_attr"), attrCallbacks, NULL);
   mlirDynamicDialectRegisterAttr(testDialect, attrDef);
 
   // Look it up to verify registration worked.
-  MlirDialect dialect = mlirDynamicDialectGetDialect(testDialect);
+  MlirDialect dialect = mlirDynamicDialectAsDialect(testDialect);
   MlirDynamicAttrDefinition lookedUp =
       mlirExtensibleDialectLookupAttrDefinition(dialect, strref("my_attr"));
 
@@ -138,11 +143,13 @@ void testDynamicTypeWithParams(MlirContext ctx) {
       mlirDynamicDialectCreate(ctx, strref("testparams"));
 
   // Define and register a parameterized type.
+  MlirDynamicTypeDefinitionCallbacks typeCallbacks = {NULL, NULL,
+                                                      successTypeVerify};
   MlirDynamicTypeDefinition typeDef = mlirDynamicTypeDefinitionCreate(
-      testDialect, strref("pair"), successTypeVerify, NULL);
+      testDialect, strref("pair"), typeCallbacks, NULL);
   mlirDynamicDialectRegisterType(testDialect, typeDef);
 
-  MlirDialect dialect = mlirDynamicDialectGetDialect(testDialect);
+  MlirDialect dialect = mlirDynamicDialectAsDialect(testDialect);
   MlirDynamicTypeDefinition lookedUp =
       mlirExtensibleDialectLookupTypeDefinition(dialect, strref("pair"));
 



More information about the Mlir-commits mailing list