[Mlir-commits] [mlir] [MLIR][RemoveDeadValues] Mark arguments of a public function Live (PR #162038)

xin liu llvmlistbot at llvm.org
Wed Oct 8 10:39:48 PDT 2025


================
@@ -569,6 +569,24 @@ module @return_void_with_unused_argument {
     call @fn_return_void_with_unused_argument(%arg0, %unused) : (i32, memref<4xi32>) -> ()
     return %unused : memref<4xi32>
   }
+
+  // the function signature is immutable because it is public.
+  func.func public @immutable_fn_with_unused_argument(%arg0: i32, %arg1: memref<4xf32>) -> () {
+    return
+  }
+
+  // CHECK-LABEL: func.func @main2
+  // CHECK: %[[MEM:.*]] = memref.alloc() : memref<4xf32>
+  // CHECK: %[[UNUSED:.*]] = arith.constant 0 : i32
+  // CHECK: call @immutable_fn_with_unused_argument(%[[UNUSED]], %[[MEM]]) : (i32, memref<4xf32>) -> ()
+  func.func @main2() -> () {
+    %one = arith.constant 1 : i32
+    %scalar = arith.addi %one, %one: i32
+    %mem = memref.alloc() : memref<4xf32>
+
+    call @immutable_fn_with_unused_argument(%scalar, %mem) : (i32, memref<4xf32>) -> ()
+    return
+  }
 }
----------------
navyxliu wrote:

hi, @joker-eph 
I did add a testcase as you said. Then I realize that it's non-trivial to propagate liveness in RemoveDeadValues. 

Here is a testcase only for RegionBranchOpinterface. From %0 is live at line 11, we need to mark %0 is live at line 2. After that, we need to mark %c1_i32 at line 4 and c0_i32 at line 7 live as well.  In order words, we need to walk function @main3 *preorder* + backward. 

```
     1	  func.func @main3(%arg0: i1) {
     2	    %0 = scf.if %arg0 -> (i32) {
     3	      %c1_i32 = arith.constant 1 : i32
     4	      scf.yield %c1_i32 : i32
     5	    } else {
     6	      %c0_i32 = arith.constant 0 : i32
     7	      scf.yield %c0_i32 : i32
     8	    }
     9	    %mem = memref.alloc() : memref<4xf32>
    10	
    11	    call @immutable_fn_with_unused_argument(%0, %mem) : (i32, memref<4xf32>) -> ()
    12	    return
    13	  }
```

I manage to fix this in [propagateBackward](https://github.com/llvm/llvm-project/pull/162038/files#diff-29f228261d7d479abc606824f895cae235ca99cc8676da9d66a2fd0f6e672fe8R407). It pretty much redo what liveness analysis has done. TBH, I don't think this is the right way to proceed. RemoveDeadValues should keep its own single responsibility. 

I take a step back and think about why we end up here. The very reason we try to propagate liveness in it because:
1) liveness is immutable. 
2) We somehow need to update the NonLive arguments of a public function. 

How about we just introduce a new pass: 'privatize-public-function' right before 'remove-dead-values'. 

1. It deploys separation of interface and implementation.
2. If nothing changes, we preserve liveness. Otherwise, we invalidate it and let remove-dead-value recompute.  

Here is a demo what this pass transforms. 
i think we can waive cost model because we don't clone function body. We just create a
thin wrapper. 

```
public void foo(int unused){...}

void main() {
arg = compute();
call foo(arg);
}
=> 
public void foo(int unused) { // interface
return __foo_impl(unused);
}

private void __foo_impl(int unused) { //implementation, new function.
... // the function body of the original foo.
}

void main() {
arg = compute();
call __foo_impl(arg);
}
```

This is my prototype here. do you think it's more feasible solution? 
https://github.com/navyxliu/llvm-project/commit/b73f5371715e1968810aac46eb47a166c63064df#diff-904855c22d662d8afbc11c40fe2906259836ba53e907e1cc899e6355358ec482



https://github.com/llvm/llvm-project/pull/162038


More information about the Mlir-commits mailing list