[Mlir-commits] [mlir] [MLIR][LivenessAnalysis] Treat a public function as an external (PR #160648)

xin liu llvmlistbot at llvm.org
Thu Sep 25 20:12:22 PDT 2025


https://github.com/navyxliu updated https://github.com/llvm/llvm-project/pull/160648

>From d54a007d86485a4a4914668fe0eaa1b49c93d6c1 Mon Sep 17 00:00:00 2001
From: Xin Liu <xxinliu at meta.com>
Date: Wed, 24 Sep 2025 22:13:51 -0700
Subject: [PATCH 1/3] [MLIR][LivenessAnalysis] Treat a public function as an
 external

This change treats a public function as an external function in the
inter-procedural liveness analysis. This prohibits NonLive arguments
from propagating to caller. RemoveDeadValues deletes unused values based
on the results of liveness Analysis. On the other side, it can not
change the signature of a public function.
---
 mlir/include/mlir/Analysis/DataFlow/LivenessAnalysis.h | 2 +-
 mlir/lib/Analysis/DataFlow/LivenessAnalysis.cpp        | 2 +-
 mlir/lib/Analysis/DataFlow/SparseAnalysis.cpp          | 9 ++++++++-
 3 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/mlir/include/mlir/Analysis/DataFlow/LivenessAnalysis.h b/mlir/include/mlir/Analysis/DataFlow/LivenessAnalysis.h
index cf1fd6e2d48ca..dffa781543243 100644
--- a/mlir/include/mlir/Analysis/DataFlow/LivenessAnalysis.h
+++ b/mlir/include/mlir/Analysis/DataFlow/LivenessAnalysis.h
@@ -36,7 +36,7 @@ namespace mlir::dataflow {
 ///
 /// A value is considered "live" iff it:
 ///   (1) has memory effects OR
-///   (2) is returned by a public function OR
+///   (2) is an argument of or is returned by a public function OR
 ///   (3) is used to compute a value of type (1) or (2).
 /// It is also to be noted that a value could be of multiple types (1/2/3) at
 /// the same time.
diff --git a/mlir/lib/Analysis/DataFlow/LivenessAnalysis.cpp b/mlir/lib/Analysis/DataFlow/LivenessAnalysis.cpp
index 65df355216f74..9eebc161914c3 100644
--- a/mlir/lib/Analysis/DataFlow/LivenessAnalysis.cpp
+++ b/mlir/lib/Analysis/DataFlow/LivenessAnalysis.cpp
@@ -52,7 +52,7 @@ ChangeResult Liveness::meet(const AbstractSparseLattice &other) {
 ///
 /// A value is considered "live" iff it:
 ///   (1) has memory effects OR
-///   (2) is returned by a public function OR
+///   (2) is an argument of or is returned by a public function OR
 ///   (3) is used to compute a value of type (1) or (2) OR
 ///   (4) is returned by a return-like op whose parent isn't a callable
 ///       nor a RegionBranchOpInterface (e.g.: linalg.yield, gpu.yield,...)
diff --git a/mlir/lib/Analysis/DataFlow/SparseAnalysis.cpp b/mlir/lib/Analysis/DataFlow/SparseAnalysis.cpp
index 0d2e2ed85549d..f03290660326a 100644
--- a/mlir/lib/Analysis/DataFlow/SparseAnalysis.cpp
+++ b/mlir/lib/Analysis/DataFlow/SparseAnalysis.cpp
@@ -17,6 +17,7 @@
 #include "mlir/IR/ValueRange.h"
 #include "mlir/Interfaces/CallInterfaces.h"
 #include "mlir/Interfaces/ControlFlowInterfaces.h"
+#include "mlir/Interfaces/FunctionInterfaces.h"
 #include "mlir/Support/LLVM.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/Support/DebugLog.h"
@@ -505,12 +506,18 @@ AbstractSparseBackwardDataFlowAnalysis::visitOperation(Operation *op) {
       // If the call invokes an external function (or a function treated as
       // external due to config), defer to the corresponding extension hook.
       // By default, it just does `visitCallOperand` for all operands.
+      //
+      // If callable is a public function, treat it as an external function.
+      // Transforms like RemoveDeadValues cannot change the arguments or returns
+      // of it.
       OperandRange argOperands = call.getArgOperands();
       MutableArrayRef<OpOperand> argOpOperands =
           operandsToOpOperands(argOperands);
       Region *region = callable.getCallableRegion();
+      bool isPublicFunc = isa<FunctionOpInterface>(callableOp)
+                       && cast<FunctionOpInterface>(callableOp).isPublic();
       if (!region || region->empty() ||
-          !getSolverConfig().isInterprocedural()) {
+          !getSolverConfig().isInterprocedural() || isPublicFunc) {
         visitExternalCallImpl(call, operandLattices, resultLattices);
         return success();
       }

>From ed25cb7371626a1497dcb5b8f3bb6a901a243562 Mon Sep 17 00:00:00 2001
From: Xin Liu <xxinliu at meta.com>
Date: Wed, 24 Sep 2025 22:26:46 -0700
Subject: [PATCH 2/3] Fix formatter error.

---
 mlir/lib/Analysis/DataFlow/SparseAnalysis.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/mlir/lib/Analysis/DataFlow/SparseAnalysis.cpp b/mlir/lib/Analysis/DataFlow/SparseAnalysis.cpp
index f03290660326a..a509aaaa1e1f1 100644
--- a/mlir/lib/Analysis/DataFlow/SparseAnalysis.cpp
+++ b/mlir/lib/Analysis/DataFlow/SparseAnalysis.cpp
@@ -514,8 +514,8 @@ AbstractSparseBackwardDataFlowAnalysis::visitOperation(Operation *op) {
       MutableArrayRef<OpOperand> argOpOperands =
           operandsToOpOperands(argOperands);
       Region *region = callable.getCallableRegion();
-      bool isPublicFunc = isa<FunctionOpInterface>(callableOp)
-                       && cast<FunctionOpInterface>(callableOp).isPublic();
+      bool isPublicFunc = isa<FunctionOpInterface>(callableOp) &&
+                          cast<FunctionOpInterface>(callableOp).isPublic();
       if (!region || region->empty() ||
           !getSolverConfig().isInterprocedural() || isPublicFunc) {
         visitExternalCallImpl(call, operandLattices, resultLattices);

>From 46496d366b27ea15da37e9503cb01ce4dbde8754 Mon Sep 17 00:00:00 2001
From: Xin Liu <xxinliu at meta.com>
Date: Thu, 25 Sep 2025 20:04:42 -0700
Subject: [PATCH 3/3] Update the comment and also add lit test.

---
 mlir/lib/Analysis/DataFlow/SparseAnalysis.cpp | 15 ++++++++-------
 mlir/test/Transforms/remove-dead-values.mlir  | 17 +++++++++++++++++
 2 files changed, 25 insertions(+), 7 deletions(-)

diff --git a/mlir/lib/Analysis/DataFlow/SparseAnalysis.cpp b/mlir/lib/Analysis/DataFlow/SparseAnalysis.cpp
index a509aaaa1e1f1..fc9e9d6ec0edc 100644
--- a/mlir/lib/Analysis/DataFlow/SparseAnalysis.cpp
+++ b/mlir/lib/Analysis/DataFlow/SparseAnalysis.cpp
@@ -507,17 +507,18 @@ AbstractSparseBackwardDataFlowAnalysis::visitOperation(Operation *op) {
       // external due to config), defer to the corresponding extension hook.
       // By default, it just does `visitCallOperand` for all operands.
       //
-      // If callable is a public function, treat it as an external function.
-      // Transforms like RemoveDeadValues cannot change the arguments or returns
-      // of it.
+      // If callable is a public function, the signature is immutable.
+      // We need to be conservative and consider all arguments Live.
       OperandRange argOperands = call.getArgOperands();
       MutableArrayRef<OpOperand> argOpOperands =
           operandsToOpOperands(argOperands);
       Region *region = callable.getCallableRegion();
-      bool isPublicFunc = isa<FunctionOpInterface>(callableOp) &&
-                          cast<FunctionOpInterface>(callableOp).isPublic();
-      if (!region || region->empty() ||
-          !getSolverConfig().isInterprocedural() || isPublicFunc) {
+      auto isPublicFunction = [&]() {
+        auto funcOp = dyn_cast<FunctionOpInterface>(callableOp);
+        return funcOp && funcOp.isPublic();
+      };
+      if (!getSolverConfig().isInterprocedural() || !region || region->empty() ||
+          isPublicFunction()) {
         visitExternalCallImpl(call, operandLattices, resultLattices);
         return success();
       }
diff --git a/mlir/test/Transforms/remove-dead-values.mlir b/mlir/test/Transforms/remove-dead-values.mlir
index fa2c145bd3701..f4ae5118b966d 100644
--- a/mlir/test/Transforms/remove-dead-values.mlir
+++ b/mlir/test/Transforms/remove-dead-values.mlir
@@ -569,6 +569,23 @@ module @return_void_with_unused_argument {
     call @fn_return_void_with_unused_argument(%arg0, %unused) : (i32, memref<4xi32>) -> ()
     return %unused : memref<4xi32>
   }
+  // the function is immutable because it is public.
+  func.func public @immutable_fn_return_void_with_unused_argument(%arg0: i32, %unused: i32) -> () {
+    %sum = arith.addi %arg0, %arg0 : i32
+    %c0 = arith.constant 0 : index
+    %buf = memref.alloc() : memref<1xi32>
+    memref.store %sum, %buf[%c0] : memref<1xi32>
+    return
+  }
+  // CHECK-LABEL: func.func @main2
+  // CHECK-SAME: (%[[ARG0_MAIN:.*]]: i32)
+  // CHECK: %[[UNUSED:.*]] = arith.constant 0 : i32
+  // CHECK: call @immutable_fn_return_void_with_unused_argument(%[[ARG0_MAIN]], %[[UNUSED]]) : (i32, i32) -> ()
+  func.func @main2(%arg0: i32) -> () {
+    %zero = arith.constant 0 : i32
+    call @immutable_fn_return_void_with_unused_argument(%arg0, %zero) : (i32, i32) -> ()
+    return
+  }
 }
 
 // -----



More information about the Mlir-commits mailing list