[Mlir-commits] [mlir] [mlir-c] Add RegionBranchOpInterface C API bindings (PR #206563)

Maksim Levental llvmlistbot at llvm.org
Mon Jun 29 11:51:56 PDT 2026


https://github.com/makslevental updated https://github.com/llvm/llvm-project/pull/206563

>From 9404cc4460e586bdde60c7ad4c19a5889080d244 Mon Sep 17 00:00:00 2001
From: makslevental <maksim.levental at gmail.com>
Date: Mon, 29 Jun 2026 11:38:45 -0700
Subject: [PATCH] [mlir-c] Add RegionBranchOpInterface C API bindings

---
 mlir/include/mlir-c/Interfaces.h        | 34 +++++++++++++
 mlir/lib/CAPI/Interfaces/CMakeLists.txt |  1 +
 mlir/lib/CAPI/Interfaces/Interfaces.cpp | 42 +++++++++++++++
 mlir/test/CAPI/ir.c                     | 68 +++++++++++++++++++++++++
 4 files changed, 145 insertions(+)

diff --git a/mlir/include/mlir-c/Interfaces.h b/mlir/include/mlir-c/Interfaces.h
index a5251dc78471f..4566fd419218c 100644
--- a/mlir/include/mlir-c/Interfaces.h
+++ b/mlir/include/mlir-c/Interfaces.h
@@ -165,6 +165,40 @@ MLIR_CAPI_EXPORTED void mlirMemoryEffectsOpInterfaceAttachFallbackModel(
     MlirContext ctx, MlirStringRef opName,
     MlirMemoryEffectsOpInterfaceCallbacks callbacks);
 
+//===---------------------------------------------------------------------===//
+// RegionBranchOpInterface
+//===---------------------------------------------------------------------===//
+
+/// Returns the interface TypeID of the RegionBranchOpInterface.
+MLIR_CAPI_EXPORTED MlirTypeID mlirRegionBranchOpInterfaceTypeID(void);
+
+/// Returns the regions that control can branch into when entering the given
+/// region-branch operation from its parent. The successor regions are written
+/// into the caller-allocated `regions` buffer, up to `n` entries, and the total
+/// number of entry successors is returned. A null MlirRegion entry denotes a
+/// successor that leaves the operation (i.e. the parent op itself). The
+/// operation must implement RegionBranchOpInterface.
+MLIR_CAPI_EXPORTED intptr_t mlirRegionBranchOpInterfaceGetEntrySuccessorRegions(
+    MlirOperation op, intptr_t n, MlirRegion *regions);
+
+/// Returns the regions that control can branch into from the given region
+/// terminator. `terminator` must be a terminator operation (implementing
+/// RegionBranchTerminatorOpInterface) nested in one of `op`'s regions. The
+/// successor regions are written into the caller-allocated `regions` buffer, up
+/// to `n` entries, and the total number of successors is returned. A null
+/// MlirRegion entry denotes a successor that leaves the operation (i.e. the
+/// parent op itself). The operation must implement RegionBranchOpInterface.
+MLIR_CAPI_EXPORTED intptr_t mlirRegionBranchOpInterfaceGetSuccessorRegions(
+    MlirOperation op, MlirOperation terminator, intptr_t n,
+    MlirRegion *regions);
+
+/// Returns true if the types are compatible for region branching between the
+/// successor regions of the given operation. The operation must implement
+/// RegionBranchOpInterface.
+MLIR_CAPI_EXPORTED bool
+mlirRegionBranchOpInterfaceAreTypesCompatible(MlirOperation op, MlirType lhs,
+                                              MlirType rhs);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/mlir/lib/CAPI/Interfaces/CMakeLists.txt b/mlir/lib/CAPI/Interfaces/CMakeLists.txt
index dbe3f33975980..d5a711404e808 100644
--- a/mlir/lib/CAPI/Interfaces/CMakeLists.txt
+++ b/mlir/lib/CAPI/Interfaces/CMakeLists.txt
@@ -2,5 +2,6 @@ add_mlir_upstream_c_api_library(MLIRCAPIInterfaces
   Interfaces.cpp
 
   LINK_LIBS PUBLIC
+  MLIRControlFlowInterfaces
   MLIRInferTypeOpInterface
   MLIRSideEffectInterfaces)
diff --git a/mlir/lib/CAPI/Interfaces/Interfaces.cpp b/mlir/lib/CAPI/Interfaces/Interfaces.cpp
index 35a2bd562a8a1..463d0cef684d3 100644
--- a/mlir/lib/CAPI/Interfaces/Interfaces.cpp
+++ b/mlir/lib/CAPI/Interfaces/Interfaces.cpp
@@ -15,6 +15,7 @@
 #include "mlir/CAPI/Support.h"
 #include "mlir/CAPI/Wrap.h"
 #include "mlir/IR/ValueRange.h"
+#include "mlir/Interfaces/ControlFlowInterfaces.h"
 #include "mlir/Interfaces/InferTypeOpInterface.h"
 #include "llvm/ADT/ScopeExit.h"
 #include <optional>
@@ -345,3 +346,44 @@ void mlirMemoryEffectsOpInterfaceAttachFallbackModel(
   assert(model && "Failed to get MemoryEffectOpInterfaceFallbackModel");
   model->setCallbacks(callbacks);
 }
+
+//===---------------------------------------------------------------------===//
+// RegionBranchOpInterface
+//===---------------------------------------------------------------------===//
+
+MlirTypeID mlirRegionBranchOpInterfaceTypeID(void) {
+  return wrap(RegionBranchOpInterface::getInterfaceID());
+}
+
+intptr_t mlirRegionBranchOpInterfaceGetEntrySuccessorRegions(
+    MlirOperation op, intptr_t n, MlirRegion *regions) {
+  auto branchOp = cast<RegionBranchOpInterface>(unwrap(op));
+  SmallVector<RegionSuccessor> successors;
+  branchOp.getSuccessorRegions(RegionBranchPoint::parent(), successors);
+  intptr_t count = static_cast<intptr_t>(successors.size());
+  for (intptr_t i = 0; i < count && i < n; ++i)
+    // A null successor region denotes the parent op (control leaves the op).
+    regions[i] = wrap(successors[i].getSuccessor());
+  return count;
+}
+
+intptr_t mlirRegionBranchOpInterfaceGetSuccessorRegions(
+    MlirOperation op, MlirOperation terminator, intptr_t n,
+    MlirRegion *regions) {
+  auto branchOp = cast<RegionBranchOpInterface>(unwrap(op));
+  RegionBranchPoint point(
+      cast<RegionBranchTerminatorOpInterface>(unwrap(terminator)));
+  SmallVector<RegionSuccessor> successors;
+  branchOp.getSuccessorRegions(point, successors);
+  intptr_t count = static_cast<intptr_t>(successors.size());
+  for (intptr_t i = 0; i < count && i < n; ++i)
+    // A null successor region denotes the parent op (control leaves the op).
+    regions[i] = wrap(successors[i].getSuccessor());
+  return count;
+}
+
+bool mlirRegionBranchOpInterfaceAreTypesCompatible(MlirOperation op,
+                                                   MlirType lhs, MlirType rhs) {
+  return cast<RegionBranchOpInterface>(unwrap(op))
+      .areTypesCompatible(unwrap(lhs), unwrap(rhs));
+}
diff --git a/mlir/test/CAPI/ir.c b/mlir/test/CAPI/ir.c
index 57ae8b9a2819b..f321f32dca063 100644
--- a/mlir/test/CAPI/ir.c
+++ b/mlir/test/CAPI/ir.c
@@ -2900,6 +2900,72 @@ int testDominanceInfo(MlirContext ctx) {
   return 0;
 }
 
+int testRegionBranchOpInterface(MlirContext ctx) {
+  fprintf(stderr, "@testRegionBranchOpInterface\n");
+  // CHECK-LABEL: @testRegionBranchOpInterface
+
+  mlirContextGetOrLoadDialect(ctx, mlirStringRefCreateFromCString("scf"));
+
+  const char *moduleStr = "func.func @f(%cond: i1) {\n"
+                          "  scf.if %cond {\n"
+                          "    scf.yield\n"
+                          "  } else {\n"
+                          "    scf.yield\n"
+                          "  }\n"
+                          "  return\n"
+                          "}\n";
+  MlirModule module =
+      mlirModuleCreateParse(ctx, mlirStringRefCreateFromCString(moduleStr));
+
+  MlirBlock moduleBody = mlirModuleGetBody(module);
+  MlirOperation funcOp = mlirBlockGetFirstOperation(moduleBody);
+  MlirRegion funcRegion = mlirOperationGetRegion(funcOp, 0);
+  MlirBlock funcBody = mlirRegionGetFirstBlock(funcRegion);
+  MlirOperation ifOp = mlirBlockGetFirstOperation(funcBody);
+  MlirRegion thenRegion = mlirOperationGetRegion(ifOp, 0);
+  MlirRegion elseRegion = mlirOperationGetRegion(ifOp, 1);
+
+  // The scf.if op implements the interface.
+  MlirTypeID id = mlirRegionBranchOpInterfaceTypeID();
+  assert(!mlirTypeIDIsNull(id));
+  assert(mlirOperationImplementsInterface(ifOp, id));
+
+  // Entering the scf.if from its parent can branch into the then and the else
+  // regions.
+  MlirRegion successors[2];
+  intptr_t count =
+      mlirRegionBranchOpInterfaceGetEntrySuccessorRegions(ifOp, 2, successors);
+  assert(count == 2);
+  bool matchesInOrder = mlirRegionEqual(successors[0], thenRegion) &&
+                        mlirRegionEqual(successors[1], elseRegion);
+  bool matchesSwapped = mlirRegionEqual(successors[0], elseRegion) &&
+                        mlirRegionEqual(successors[1], thenRegion);
+  assert(matchesInOrder || matchesSwapped);
+
+  // Branching from a region's terminator (the scf.yield in the then region)
+  // leaves the op: the single successor is the parent, denoted by a null
+  // region.
+  MlirBlock thenBlock = mlirRegionGetFirstBlock(thenRegion);
+  MlirOperation thenYield = mlirBlockGetTerminator(thenBlock);
+  MlirRegion fromTerminator[1];
+  intptr_t fromCount = mlirRegionBranchOpInterfaceGetSuccessorRegions(
+      ifOp, thenYield, 1, fromTerminator);
+  assert(fromCount == 1);
+  assert(mlirRegionIsNull(fromTerminator[0]));
+
+  // Type compatibility along control flow uses equality by default.
+  MlirType i32 = mlirIntegerTypeGet(ctx, 32);
+  MlirType i64 = mlirIntegerTypeGet(ctx, 64);
+  assert(mlirRegionBranchOpInterfaceAreTypesCompatible(ifOp, i32, i32));
+  assert(!mlirRegionBranchOpInterfaceAreTypesCompatible(ifOp, i32, i64));
+
+  mlirModuleDestroy(module);
+
+  // CHECK: testRegionBranchOpInterface: PASSED
+  fprintf(stderr, "testRegionBranchOpInterface: PASSED\n");
+  return 0;
+}
+
 int main(void) {
   MlirContext ctx = mlirContextCreate();
   registerAllUpstreamDialects(ctx);
@@ -2955,6 +3021,8 @@ int main(void) {
     return 19;
   if (testDominanceInfo(ctx))
     return 20;
+  if (testRegionBranchOpInterface(ctx))
+    return 21;
 
   // CHECK: DESTROY MAIN CONTEXT
   // CHECK: reportResourceDelete: resource_i64_blob



More information about the Mlir-commits mailing list