[Mlir-commits] [mlir] [MLIR][RemoveDeadValues] Privatize public function with NonLive arguments before RDV. (PR #162038)

Mehdi Amini llvmlistbot at llvm.org
Mon Oct 20 07:54:26 PDT 2025


================
@@ -869,10 +870,151 @@ struct RemoveDeadValues : public impl::RemoveDeadValuesBase<RemoveDeadValues> {
 };
 } // namespace
 
+/// If the target of CallOp is a public function and at least one argument is
+/// NonLive, privatize the function. Our strategy here is separation interface
+/// and implementation. eg.
+///
+/// public void foo(int unused){...}
+/// =>
+/// public void foo(int unused) {          // old function, interface
+///   return __foo_privatized(unused);
+/// }
+///
+/// private void __foo_privatized(int unused) { // the new private function, or
+/// implementation.
+///  ...                                        // the function body of the
+///  original function.
+/// }
+///
+/// Returns true if any IR changes were made, false otherwise.
+static bool processCallOp(CallOpInterface callOp, ModuleOp moduleOp,
+                          RunLivenessAnalysis &la) {
+  Operation *callableOp = callOp.resolveCallable();
+  auto funcOp = dyn_cast<FunctionOpInterface>(callableOp);
+  if (!funcOp || !funcOp.isPublic())
+    return false;
+
+  LDBG() << "Processing callOp " << callOp << " target is a public function: "
+         << funcOp.getOperation()->getName();
+
+  // Get the list of unnecessary (non-live) arguments in `nonLiveArgs`.
+  SmallVector<Value> arguments(callOp.getArgOperands());
+  BitVector nonLiveArgs = markLives(arguments, DenseSet<Value>(), la);
+  nonLiveArgs = nonLiveArgs.flip();
+
+  if (nonLiveArgs.count() > 0) {
+    OpBuilder rewriter(moduleOp.getContext());
+
+    // Clone function and create private version
+    FunctionOpInterface clonedFunc = cast<FunctionOpInterface>(funcOp.clone());
+
+    // Set visibility = 'private' and a new name for the cloned function
+    SymbolTable::setSymbolVisibility(clonedFunc,
+                                     SymbolTable::Visibility::Private);
+    std::string newName = "__" + funcOp.getName().str() + "_privatized";
+    clonedFunc.setName(newName);
+
+    // Insert the cloned function into the module
+    rewriter.setInsertionPointAfter(funcOp);
+    rewriter.insert(clonedFunc);
+
+    // Replace ALL callsites of the original function to call the cloned
+    // function directly
+    LogicalResult result = SymbolTable::replaceAllSymbolUses(
+        funcOp, clonedFunc.getNameAttr(), moduleOp);
+
+    if (result.failed()) {
+      LDBG() << "Failed to replace all symbol uses for " << funcOp.getName();
+      return false;
+    }
+
+    LDBG() << "Redirected all callsites from " << funcOp.getName() << " to "
+           << newName;
+
+    // Transform the original funcOp into a wrapper that calls the cloned
+    // function
+    Region &funcBody = funcOp.getFunctionBody();
+
+    // Clean the original function body
+    funcBody.dropAllReferences();
+    funcBody.getBlocks().clear();
+
+    // Create a new entry block for the wrapper function
+    Block *wrapperBlock = rewriter.createBlock(&funcBody);
+
+    // Add block arguments that match the function signature
+    for (Type argType : funcOp.getArgumentTypes()) {
+      wrapperBlock->addArgument(argType, funcOp.getLoc());
+    }
+
+    // Set insertion point to the new block
+    rewriter.setInsertionPointToStart(wrapperBlock);
+
+    // Clone the original call operation and update its callee
+    auto clonedCallOp = cast<CallOpInterface>(callOp->clone());
+    // Update the callee symbol reference to point to the new private function
+    auto symbolRef =
+        SymbolRefAttr::get(funcOp.getContext(), clonedFunc.getName());
+    clonedCallOp.setCalleeFromCallable(symbolRef);
+    // Set the call arguments to use the wrapper block's arguments
+    clonedCallOp->setOperands(wrapperBlock->getArguments());
+    rewriter.insert(clonedCallOp);
+
+    // Create return operation of the same type as the original function's
+    // return
+    Operation *returnOp = nullptr;
+    for (Block &block : clonedFunc.getFunctionBody()) {
+      if (block.getNumSuccessors() > 0)
+        continue;
+
+      Operation *terminator = block.getTerminator();
+      if (terminator && terminator->hasTrait<OpTrait::ReturnLike>()) {
+        returnOp = terminator;
+        break; // Use first return as template
+      }
+    }
+
+    if (returnOp) {
+      Operation *newReturnOp = returnOp->clone();
+      newReturnOp->setOperands(clonedCallOp->getResults());
+      newReturnOp->setLoc(funcOp.getLoc());
+      rewriter.insert(newReturnOp);
+    }
+    return true; // Changes were made
+  }
+
+  return false;
+}
+
 void RemoveDeadValues::runOnOperation() {
-  auto &la = getAnalysis<RunLivenessAnalysis>();
+  AnalysisManager am = getAnalysisManager();
+  RunLivenessAnalysis *la = &am.getAnalysis<RunLivenessAnalysis>();
   Operation *module = getOperation();
 
+  // Only privatize public functions if liveness analysis is inter-procedural.
+  if (la->getSolverConfig().isInterprocedural()) {
+    bool changed = false;
+    module->walk([&](CallOpInterface callOp) {
+      if (processCallOp(callOp, cast<ModuleOp>(module), *la)) {
----------------
joker-eph wrote:

This cast does not seem safe to me: the pass isn't a modulePass right now.


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


More information about the Mlir-commits mailing list