[Mlir-commits] [mlir] [mlir-c] Add LoopLikeOpInterface C API bindings (PR #206560)

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


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

>From aa97fe4dc6d7248c975f13abd7ad7d5b77410533 Mon Sep 17 00:00:00 2001
From: makslevental <maksim.levental at gmail.com>
Date: Mon, 29 Jun 2026 11:30:14 -0700
Subject: [PATCH] [mlir-c] Add LoopLikeOpInterface C API bindings

---
 mlir/include/mlir-c/Interfaces.h        | 45 ++++++++++++++
 mlir/lib/CAPI/Interfaces/CMakeLists.txt |  1 +
 mlir/lib/CAPI/Interfaces/Interfaces.cpp | 61 +++++++++++++++++++
 mlir/test/CAPI/ir.c                     | 81 +++++++++++++++++++++++++
 4 files changed, 188 insertions(+)

diff --git a/mlir/include/mlir-c/Interfaces.h b/mlir/include/mlir-c/Interfaces.h
index a5251dc78471f..f369f948b0cdf 100644
--- a/mlir/include/mlir-c/Interfaces.h
+++ b/mlir/include/mlir-c/Interfaces.h
@@ -165,6 +165,51 @@ MLIR_CAPI_EXPORTED void mlirMemoryEffectsOpInterfaceAttachFallbackModel(
     MlirContext ctx, MlirStringRef opName,
     MlirMemoryEffectsOpInterfaceCallbacks callbacks);
 
+//===---------------------------------------------------------------------===//
+// LoopLikeOpInterface
+//===---------------------------------------------------------------------===//
+
+/// Returns the interface TypeID of the LoopLikeOpInterface.
+MLIR_CAPI_EXPORTED MlirTypeID mlirLoopLikeOpInterfaceTypeID(void);
+
+/// Returns the loop regions of the given loop-like operation. The regions are
+/// written into the caller-allocated `regions` buffer, up to `n` entries, and
+/// the total number of loop regions is returned. The operation must implement
+/// LoopLikeOpInterface.
+MLIR_CAPI_EXPORTED intptr_t mlirLoopLikeOpInterfaceGetLoopRegions(
+    MlirOperation op, intptr_t n, MlirRegion *regions);
+
+/// Returns true if the given value is defined outside of the loop. The
+/// operation must implement LoopLikeOpInterface.
+MLIR_CAPI_EXPORTED bool
+mlirLoopLikeOpInterfaceIsDefinedOutsideOfLoop(MlirOperation op,
+                                              MlirValue value);
+
+/// Returns the induction variables of the loop. The values are written into the
+/// caller-allocated `vars` buffer, up to `n` entries, and the total number of
+/// induction variables is returned. A negative return value indicates that the
+/// loop has no notion of induction variables. The operation must implement
+/// LoopLikeOpInterface.
+MLIR_CAPI_EXPORTED intptr_t mlirLoopLikeOpInterfaceGetLoopInductionVars(
+    MlirOperation op, intptr_t n, MlirValue *vars);
+
+/// Returns the region iter_args of the loop (the block arguments corresponding
+/// to the init operands). The values are written into the caller-allocated
+/// `args` buffer, up to `n` entries, and the total number is returned. The
+/// operation must implement LoopLikeOpInterface.
+MLIR_CAPI_EXPORTED intptr_t mlirLoopLikeOpInterfaceGetRegionIterArgs(
+    MlirOperation op, intptr_t n, MlirValue *args);
+
+/// Moves the given loop-invariant operation out of the loop. The loop operation
+/// must implement LoopLikeOpInterface.
+MLIR_CAPI_EXPORTED void
+mlirLoopLikeOpInterfaceMoveOutOfLoop(MlirOperation loopOp, MlirOperation op);
+
+/// Returns the static trip count of the loop, or -1 if it cannot be determined
+/// statically. The operation must implement LoopLikeOpInterface.
+MLIR_CAPI_EXPORTED int64_t
+mlirLoopLikeOpInterfaceGetStaticTripCount(MlirOperation op);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/mlir/lib/CAPI/Interfaces/CMakeLists.txt b/mlir/lib/CAPI/Interfaces/CMakeLists.txt
index dbe3f33975980..3bf07011ed25c 100644
--- a/mlir/lib/CAPI/Interfaces/CMakeLists.txt
+++ b/mlir/lib/CAPI/Interfaces/CMakeLists.txt
@@ -3,4 +3,5 @@ add_mlir_upstream_c_api_library(MLIRCAPIInterfaces
 
   LINK_LIBS PUBLIC
   MLIRInferTypeOpInterface
+  MLIRLoopLikeInterface
   MLIRSideEffectInterfaces)
diff --git a/mlir/lib/CAPI/Interfaces/Interfaces.cpp b/mlir/lib/CAPI/Interfaces/Interfaces.cpp
index 35a2bd562a8a1..8d18cd9a6ed4f 100644
--- a/mlir/lib/CAPI/Interfaces/Interfaces.cpp
+++ b/mlir/lib/CAPI/Interfaces/Interfaces.cpp
@@ -16,6 +16,7 @@
 #include "mlir/CAPI/Wrap.h"
 #include "mlir/IR/ValueRange.h"
 #include "mlir/Interfaces/InferTypeOpInterface.h"
+#include "mlir/Interfaces/LoopLikeInterface.h"
 #include "llvm/ADT/ScopeExit.h"
 #include <optional>
 
@@ -345,3 +346,63 @@ void mlirMemoryEffectsOpInterfaceAttachFallbackModel(
   assert(model && "Failed to get MemoryEffectOpInterfaceFallbackModel");
   model->setCallbacks(callbacks);
 }
+
+//===---------------------------------------------------------------------===//
+// LoopLikeOpInterface
+//===---------------------------------------------------------------------===//
+
+MlirTypeID mlirLoopLikeOpInterfaceTypeID(void) {
+  return wrap(LoopLikeOpInterface::getInterfaceID());
+}
+
+intptr_t mlirLoopLikeOpInterfaceGetLoopRegions(MlirOperation op, intptr_t n,
+                                               MlirRegion *regions) {
+  SmallVector<Region *> loopRegions =
+      cast<LoopLikeOpInterface>(unwrap(op)).getLoopRegions();
+  intptr_t count = static_cast<intptr_t>(loopRegions.size());
+  for (intptr_t i = 0; i < count && i < n; ++i)
+    regions[i] = wrap(loopRegions[i]);
+  return count;
+}
+
+bool mlirLoopLikeOpInterfaceIsDefinedOutsideOfLoop(MlirOperation op,
+                                                   MlirValue value) {
+  return cast<LoopLikeOpInterface>(unwrap(op))
+      .isDefinedOutsideOfLoop(unwrap(value));
+}
+
+intptr_t mlirLoopLikeOpInterfaceGetLoopInductionVars(MlirOperation op,
+                                                     intptr_t n,
+                                                     MlirValue *vars) {
+  std::optional<SmallVector<Value>> inductionVars =
+      cast<LoopLikeOpInterface>(unwrap(op)).getLoopInductionVars();
+  if (!inductionVars)
+    return -1;
+  intptr_t count = static_cast<intptr_t>(inductionVars->size());
+  for (intptr_t i = 0; i < count && i < n; ++i)
+    vars[i] = wrap((*inductionVars)[i]);
+  return count;
+}
+
+intptr_t mlirLoopLikeOpInterfaceGetRegionIterArgs(MlirOperation op, intptr_t n,
+                                                  MlirValue *args) {
+  Block::BlockArgListType iterArgs =
+      cast<LoopLikeOpInterface>(unwrap(op)).getRegionIterArgs();
+  intptr_t count = static_cast<intptr_t>(iterArgs.size());
+  for (intptr_t i = 0; i < count && i < n; ++i)
+    args[i] = wrap(iterArgs[i]);
+  return count;
+}
+
+void mlirLoopLikeOpInterfaceMoveOutOfLoop(MlirOperation loopOp,
+                                          MlirOperation op) {
+  cast<LoopLikeOpInterface>(unwrap(loopOp)).moveOutOfLoop(unwrap(op));
+}
+
+int64_t mlirLoopLikeOpInterfaceGetStaticTripCount(MlirOperation op) {
+  std::optional<llvm::APInt> tripCount =
+      cast<LoopLikeOpInterface>(unwrap(op)).getStaticTripCount();
+  if (!tripCount)
+    return -1;
+  return static_cast<int64_t>(tripCount->getZExtValue());
+}
diff --git a/mlir/test/CAPI/ir.c b/mlir/test/CAPI/ir.c
index 57ae8b9a2819b..70bc2d9c6106a 100644
--- a/mlir/test/CAPI/ir.c
+++ b/mlir/test/CAPI/ir.c
@@ -2900,6 +2900,85 @@ int testDominanceInfo(MlirContext ctx) {
   return 0;
 }
 
+int testLoopLikeOpInterface(MlirContext ctx) {
+  fprintf(stderr, "@testLoopLikeOpInterface\n");
+  // CHECK-LABEL: @testLoopLikeOpInterface
+
+  mlirContextGetOrLoadDialect(ctx, mlirStringRefCreateFromCString("arith"));
+  mlirContextGetOrLoadDialect(ctx, mlirStringRefCreateFromCString("scf"));
+
+  const char *moduleStr =
+      "func.func @f(%arg0: i32) -> i32 {\n"
+      "  %lb = arith.constant 0 : index\n"
+      "  %ub = arith.constant 10 : index\n"
+      "  %step = arith.constant 1 : index\n"
+      "  %0 = scf.for %i = %lb to %ub step %step iter_args(%acc = %arg0) -> "
+      "(i32) {\n"
+      "    %inv = arith.muli %arg0, %arg0 : i32\n"
+      "    %1 = arith.addi %acc, %inv : i32\n"
+      "    scf.yield %1 : i32\n"
+      "  }\n"
+      "  return %0 : i32\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);
+  MlirValue funcArg0 = mlirBlockGetArgument(funcBody, 0);
+
+  MlirOperation lbOp = mlirBlockGetFirstOperation(funcBody);
+  MlirOperation ubOp = mlirOperationGetNextInBlock(lbOp);
+  MlirOperation stepOp = mlirOperationGetNextInBlock(ubOp);
+  MlirOperation forOp = mlirOperationGetNextInBlock(stepOp);
+
+  MlirRegion forRegion = mlirOperationGetRegion(forOp, 0);
+  MlirBlock forBody = mlirRegionGetFirstBlock(forRegion);
+  MlirValue indVar = mlirBlockGetArgument(forBody, 0);
+  MlirValue iterArg = mlirBlockGetArgument(forBody, 1);
+
+  // The scf.for op implements the interface.
+  MlirTypeID id = mlirLoopLikeOpInterfaceTypeID();
+  assert(!mlirTypeIDIsNull(id));
+  assert(mlirOperationImplementsInterface(forOp, id));
+
+  // It has a single loop region, which is the body region.
+  MlirRegion regions[1];
+  assert(mlirLoopLikeOpInterfaceGetLoopRegions(forOp, 1, regions) == 1);
+  assert(mlirRegionEqual(regions[0], forRegion));
+
+  // The function argument is defined outside the loop; the iter_arg is not.
+  assert(mlirLoopLikeOpInterfaceIsDefinedOutsideOfLoop(forOp, funcArg0));
+  assert(!mlirLoopLikeOpInterfaceIsDefinedOutsideOfLoop(forOp, iterArg));
+
+  // The single induction variable is the loop body's first block argument.
+  MlirValue vars[1];
+  assert(mlirLoopLikeOpInterfaceGetLoopInductionVars(forOp, 1, vars) == 1);
+  assert(mlirValueEqual(vars[0], indVar));
+
+  // The single region iter_arg is the loop body's second block argument.
+  MlirValue iterArgs[1];
+  assert(mlirLoopLikeOpInterfaceGetRegionIterArgs(forOp, 1, iterArgs) == 1);
+  assert(mlirValueEqual(iterArgs[0], iterArg));
+
+  // lb = 0, ub = 10, step = 1 -> static trip count of 10.
+  assert(mlirLoopLikeOpInterfaceGetStaticTripCount(forOp) == 10);
+
+  // Move the loop-invariant `arith.muli` out of the loop; it should land in the
+  // function body alongside the for op.
+  MlirOperation invOp = mlirBlockGetFirstOperation(forBody);
+  mlirLoopLikeOpInterfaceMoveOutOfLoop(forOp, invOp);
+  assert(mlirBlockEqual(mlirOperationGetBlock(invOp), funcBody));
+
+  mlirModuleDestroy(module);
+
+  // CHECK: testLoopLikeOpInterface: PASSED
+  fprintf(stderr, "testLoopLikeOpInterface: PASSED\n");
+  return 0;
+}
+
 int main(void) {
   MlirContext ctx = mlirContextCreate();
   registerAllUpstreamDialects(ctx);
@@ -2955,6 +3034,8 @@ int main(void) {
     return 19;
   if (testDominanceInfo(ctx))
     return 20;
+  if (testLoopLikeOpInterface(ctx))
+    return 21;
 
   // CHECK: DESTROY MAIN CONTEXT
   // CHECK: reportResourceDelete: resource_i64_blob



More information about the Mlir-commits mailing list