[llvm] [Attributor] Drop norecurse when specializing an indirect call closes a cycle (PR #218637)

Larry Meadows via llvm-commits llvm-commits at lists.llvm.org
Tue Aug 25 12:59:39 PDT 2026


================
@@ -2670,6 +2671,42 @@ ChangeStatus Attributor::cleanupIR() {
     Configuration.CGUpdater.removeFunction(*Fn);
   }
 
+  // Specializing an indirect call turns an edge that only existed through a
+  // function pointer into a direct one, which can close a call graph cycle and
+  // make `norecurse` false for every function in it.
+  if (!SpecializedIndirectCallers.empty()) {
+    CallGraph CG(*SpecializedIndirectCallers.front()->getParent());
+    SmallPtrSet<Function *, 8> Handled;
+    for (Function *Caller : SpecializedIndirectCallers) {
+      if (!Handled.insert(Caller).second)
+        continue;
+      CallGraphNode *Node = CG[Caller];
+      for (scc_iterator<CallGraphNode *> It = scc_begin(Node); !It.isAtEnd();
+           ++It) {
+        if (!is_contained(*It, Node))
+          continue;
+        if (!It.hasCycle())
+          break;
+        for (CallGraphNode *N : *It) {
+          Function *Fn = N->getFunction();
+          if (!Fn || ToBeDeletedFunctions.count(Fn) || !Functions.count(Fn))
+            continue;
+          Handled.insert(Fn);
+          if (!Fn->hasFnAttribute(Attribute::NoRecurse))
+            continue;
+          Fn->removeFnAttr(Attribute::NoRecurse);
+          for (User *U : Fn->users())
+            if (auto *CB = dyn_cast<CallBase>(U))
+              if (CB->getCalledFunction() == Fn &&
+                  Functions.count(CB->getFunction()))
+                CB->removeFnAttr(Attribute::NoRecurse);
----------------
lfmeadow wrote:

The `bitcast` example does not arise: with opaque pointers a `ptr`-to-`ptr` bitcast
of a function is not representable, so `call void bitcast (ptr @callee to ptr)()`
parses as `call void @callee()`, and calls built through `FunctionCallee` have the
function itself as the callee operand.

The mechanism is real for `addrspacecast`, though, which does survive as a
constant expression, and there the call site is a user of the expression rather
than of the function. Fixed by matching `getCalledOperand()->stripPointerCasts()`
over the call sites in the functions the Attributor tracks, rather than walking
`Fn->users()`, which also covers any other constant expression form.
`norecurse_call_site_constant_expr.ll` is the regression test; it keeps a stale
`norecurse` on the call site without the change.


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


More information about the llvm-commits mailing list