[Mlir-commits] [mlir] [mlir-c] Add structural operation equivalence (PR #206537)
Maksim Levental
llvmlistbot at llvm.org
Mon Jun 29 11:51:35 PDT 2026
https://github.com/makslevental updated https://github.com/llvm/llvm-project/pull/206537
>From 02c3ebb7fb9976babfa29a91bc3c481cd04cf575 Mon Sep 17 00:00:00 2001
From: makslevental <maksim.levental at gmail.com>
Date: Mon, 29 Jun 2026 10:14:49 -0700
Subject: [PATCH] [mlir-c] Add structural operation equivalence
---
mlir/include/mlir-c/IR.h | 25 ++++++++++++++
mlir/lib/CAPI/IR/IR.cpp | 24 ++++++++++++++
mlir/test/CAPI/ir.c | 70 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 119 insertions(+)
diff --git a/mlir/include/mlir-c/IR.h b/mlir/include/mlir-c/IR.h
index 311234e42df60..0ad4f3885abd2 100644
--- a/mlir/include/mlir-c/IR.h
+++ b/mlir/include/mlir-c/IR.h
@@ -635,6 +635,31 @@ MLIR_CAPI_EXPORTED bool mlirOperationEqual(MlirOperation op,
/// Compute a hash for the given operation.
MLIR_CAPI_EXPORTED size_t mlirOperationHashValue(MlirOperation op);
+/// Flags controlling structural operation equivalence and hashing. These mirror
+/// `mlir::OperationEquivalence::Flags` and may be combined with bitwise OR.
+typedef enum MlirOperationEquivalenceFlags {
+ /// No flags: locations, discardable attributes, properties and
+ /// commutativity are all significant.
+ MLIR_OPERATION_EQUIVALENCE_NONE = 0,
+ /// Ignore the locations attached to operations.
+ MLIR_OPERATION_EQUIVALENCE_IGNORE_LOCATIONS = 1,
+ /// Ignore the discardable attributes attached to operations.
+ MLIR_OPERATION_EQUIVALENCE_IGNORE_DISCARDABLE_ATTRS = 2,
+ /// Ignore the properties attached to operations.
+ MLIR_OPERATION_EQUIVALENCE_IGNORE_PROPERTIES = 4,
+ /// Ignore commutativity, comparing operands in an order-sensitive way.
+ MLIR_OPERATION_EQUIVALENCE_IGNORE_COMMUTATIVITY = 8,
+} MlirOperationEquivalenceFlags;
+
+/// Checks whether two operations are structurally equivalent, i.e. they have
+/// the same name, attributes, operand and result types, and recursively
+/// equivalent regions. Operand equivalence is tracked structurally during the
+/// traversal (operands need not be the exact same SSA values). The comparison
+/// is parameterized by `flags` (see MlirOperationEquivalenceFlags).
+MLIR_CAPI_EXPORTED bool
+mlirOperationIsStructurallyEquivalent(MlirOperation lhs, MlirOperation rhs,
+ MlirOperationEquivalenceFlags flags);
+
/// Gets the context this operation is associated with
MLIR_CAPI_EXPORTED MlirContext mlirOperationGetContext(MlirOperation op);
diff --git a/mlir/lib/CAPI/IR/IR.cpp b/mlir/lib/CAPI/IR/IR.cpp
index 94442e2be19a4..3f29ec66dad5f 100644
--- a/mlir/lib/CAPI/IR/IR.cpp
+++ b/mlir/lib/CAPI/IR/IR.cpp
@@ -656,6 +656,30 @@ size_t mlirOperationHashValue(MlirOperation op) {
return OperationEquivalence::computeHash(unwrap(op));
}
+/// Translates the C equivalence flags to mlir::OperationEquivalence::Flags. The
+/// enumerator values are kept in sync, asserted below.
+static OperationEquivalence::Flags
+unwrapEquivalenceFlags(MlirOperationEquivalenceFlags flags) {
+ static_assert(MLIR_OPERATION_EQUIVALENCE_NONE == OperationEquivalence::None &&
+ MLIR_OPERATION_EQUIVALENCE_IGNORE_LOCATIONS ==
+ OperationEquivalence::IgnoreLocations &&
+ MLIR_OPERATION_EQUIVALENCE_IGNORE_DISCARDABLE_ATTRS ==
+ OperationEquivalence::IgnoreDiscardableAttrs &&
+ MLIR_OPERATION_EQUIVALENCE_IGNORE_PROPERTIES ==
+ OperationEquivalence::IgnoreProperties &&
+ MLIR_OPERATION_EQUIVALENCE_IGNORE_COMMUTATIVITY ==
+ OperationEquivalence::IgnoreCommutativity,
+ "MlirOperationEquivalenceFlags out of sync with "
+ "OperationEquivalence::Flags");
+ return static_cast<OperationEquivalence::Flags>(flags);
+}
+
+bool mlirOperationIsStructurallyEquivalent(
+ MlirOperation lhs, MlirOperation rhs, MlirOperationEquivalenceFlags flags) {
+ return OperationEquivalence::isEquivalentTo(unwrap(lhs), unwrap(rhs),
+ unwrapEquivalenceFlags(flags));
+}
+
MlirContext mlirOperationGetContext(MlirOperation op) {
return wrap(unwrap(op)->getContext());
}
diff --git a/mlir/test/CAPI/ir.c b/mlir/test/CAPI/ir.c
index 57ae8b9a2819b..2554821e0a559 100644
--- a/mlir/test/CAPI/ir.c
+++ b/mlir/test/CAPI/ir.c
@@ -2900,6 +2900,74 @@ int testDominanceInfo(MlirContext ctx) {
return 0;
}
+int testOperationEquivalence(MlirContext ctx) {
+ fprintf(stderr, "@testOperationEquivalence\n");
+ // CHECK-LABEL: @testOperationEquivalence
+
+ mlirContextGetOrLoadDialect(ctx, mlirStringRefCreateFromCString("arith"));
+
+ const char *moduleStr = "func.func @f(%arg0: i32) -> i32 {\n"
+ " %0 = arith.constant 42 : i32\n"
+ " %1 = arith.constant 42 : i32\n"
+ " %2 = arith.constant 7 : i32\n"
+ " %3 = arith.subi %0, %2 : i32\n"
+ " %4 = arith.subi %0, %2 : i32\n"
+ " %5 = arith.subi %2, %0 : i32\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);
+
+ MlirOperation c42a = mlirBlockGetFirstOperation(funcBody);
+ MlirOperation c42b = mlirOperationGetNextInBlock(c42a);
+ MlirOperation c7 = mlirOperationGetNextInBlock(c42b);
+ MlirOperation sub3 = mlirOperationGetNextInBlock(c7);
+ MlirOperation sub4 = mlirOperationGetNextInBlock(sub3);
+ MlirOperation sub5 = mlirOperationGetNextInBlock(sub4);
+
+ // Two identical constants are structurally equivalent when locations are
+ // ignored, even though their result SSA values and source locations differ.
+ assert(mlirOperationIsStructurallyEquivalent(
+ c42a, c42b, MLIR_OPERATION_EQUIVALENCE_IGNORE_LOCATIONS));
+
+ // Without ignoring locations they differ, since they sit on distinct lines.
+ assert(!mlirOperationIsStructurallyEquivalent(
+ c42a, c42b, MLIR_OPERATION_EQUIVALENCE_NONE));
+
+ // A constant with a different value is not equivalent.
+ assert(!mlirOperationIsStructurallyEquivalent(
+ c42a, c7, MLIR_OPERATION_EQUIVALENCE_IGNORE_LOCATIONS));
+
+ // Equivalence is reflexive.
+ assert(mlirOperationIsStructurallyEquivalent(
+ c42a, c42a, MLIR_OPERATION_EQUIVALENCE_IGNORE_LOCATIONS));
+
+ // `%3 = subi %0, %2` and `%4 = subi %0, %2` use the exact same operands, so
+ // they are equivalent.
+ assert(mlirOperationIsStructurallyEquivalent(
+ sub3, sub4, MLIR_OPERATION_EQUIVALENCE_IGNORE_LOCATIONS));
+
+ // `%5 = subi %2, %0` swaps the operands; since subi is not commutative this
+ // is not equivalent to `%3 = subi %0, %2`.
+ assert(!mlirOperationIsStructurallyEquivalent(
+ sub3, sub5, MLIR_OPERATION_EQUIVALENCE_IGNORE_LOCATIONS));
+
+ // A constant and a subtraction are different operations.
+ assert(!mlirOperationIsStructurallyEquivalent(
+ c42a, sub3, MLIR_OPERATION_EQUIVALENCE_IGNORE_LOCATIONS));
+
+ mlirModuleDestroy(module);
+
+ // CHECK: testOperationEquivalence: PASSED
+ fprintf(stderr, "testOperationEquivalence: PASSED\n");
+ return 0;
+}
+
int main(void) {
MlirContext ctx = mlirContextCreate();
registerAllUpstreamDialects(ctx);
@@ -2955,6 +3023,8 @@ int main(void) {
return 19;
if (testDominanceInfo(ctx))
return 20;
+ if (testOperationEquivalence(ctx))
+ return 21;
// CHECK: DESTROY MAIN CONTEXT
// CHECK: reportResourceDelete: resource_i64_blob
More information about the Mlir-commits
mailing list