[llvm] [Attributor] Only seed live internal callees during deduction (PR #222226)

via llvm-commits llvm-commits at lists.llvm.org
Fri Sep 11 00:19:34 PDT 2026


https://github.com/int-zjt updated https://github.com/llvm/llvm-project/pull/222226

>From 95c56d032bb4d36d3573bcc38808c587162665eb Mon Sep 17 00:00:00 2001
From: "zhangjiatong.0" <zhangjiatong.0 at bytedance.com>
Date: Wed, 9 Sep 2026 11:40:27 +0800
Subject: [PATCH 1/5] [Attributor] Only seed live internal callees during
 deduction

---
 llvm/include/llvm/Transforms/IPO/Attributor.h |  9 +++-
 .../Transforms/IPO/AttributorAttributes.cpp   |  3 ++
 .../Transforms/IPO/AttributorTest.cpp         | 48 +++++++++++++++++++
 3 files changed, 58 insertions(+), 2 deletions(-)

diff --git a/llvm/include/llvm/Transforms/IPO/Attributor.h b/llvm/include/llvm/Transforms/IPO/Attributor.h
index 00b20eee4d5df..4cce13397a882 100644
--- a/llvm/include/llvm/Transforms/IPO/Attributor.h
+++ b/llvm/include/llvm/Transforms/IPO/Attributor.h
@@ -1705,7 +1705,7 @@ struct Attributor {
     AAPtr = &AA;
 
     // Register AA with the synthetic root only before the manifest stage.
-    if (Phase == AttributorPhase::SEEDING || Phase == AttributorPhase::UPDATE)
+    if (isDuringDeduction())
       DG.SyntheticRoot.Deps.insert(
           AADepGraphNode::DepTy(&AA, unsigned(DepClassTy::REQUIRED)));
 
@@ -1745,7 +1745,7 @@ struct Attributor {
   template <typename AAType> bool shouldUpdateAA(const IRPosition &IRP) {
     // If this is queried in the manifest stage, we force the AA to indicate
     // pessimistic fixpoint immediately.
-    if (Phase == AttributorPhase::MANIFEST || Phase == AttributorPhase::CLEANUP)
+    if (!isDuringDeduction())
       return false;
 
     Function *AssociatedFn = IRP.getAssociatedFunction();
@@ -1821,6 +1821,11 @@ struct Attributor {
            (Configuration.IPOAmendableCB && Configuration.IPOAmendableCB(F));
   }
 
+  /// Return whether attributes can participate in fixed-point deduction.
+  bool isDuringDeduction() const {
+    return Phase == AttributorPhase::SEEDING || Phase == AttributorPhase::UPDATE;
+  }
+
   /// Mark the internal function \p F as live.
   ///
   /// This will trigger the identification and initialization of attributes for
diff --git a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
index 22af6f7741544..ac4268f78143d 100644
--- a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
+++ b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
@@ -4673,6 +4673,9 @@ struct AAIsDeadFunction : public AAIsDead {
     if (!AssumedLiveBlocks.insert(&BB).second)
       return false;
 
+    if (!A.isDuringDeduction())
+      return true;
+
     // We assume that all of BB is (probably) live now and if there are calls to
     // internal functions we will assume that those are now live as well. This
     // is a performance optimization for blocks with calls to a lot of internal
diff --git a/llvm/unittests/Transforms/IPO/AttributorTest.cpp b/llvm/unittests/Transforms/IPO/AttributorTest.cpp
index 8d90b308f840c..9d3d64f75a458 100644
--- a/llvm/unittests/Transforms/IPO/AttributorTest.cpp
+++ b/llvm/unittests/Transforms/IPO/AttributorTest.cpp
@@ -212,4 +212,52 @@ TEST_F(AttributorTestBase, AAReachabilityTest) {
   ASSERT_FALSE(F9AA.instructionCanReach(A, F9FirstInst, F4));
 }
 
+TEST_F(AttributorTestBase, CleanupDoesNotSeedLiveCallees) {
+  for (bool SeedCaller : {false, true}) {
+    SCOPED_TRACE(SeedCaller);
+    Module &M = parseModule(R"(
+      define internal void @leaf() { ret void }
+      define internal void @sibling() { ret void }
+      define void @caller() {
+        call void @leaf()
+        call void @sibling()
+        ret void
+      }
+    )");
+    SetVector<Function *> Functions;
+    for (Function &F : M)
+      Functions.insert(&F);
+    AnalysisGetter AG;
+    CallGraphUpdater CGUpdater;
+    BumpPtrAllocator Allocator;
+    InformationCache InfoCache(M, AG, Allocator, nullptr);
+    AttributorConfig AC(CGUpdater);
+    AC.DefaultInitializeLiveInternals = false;
+    unsigned NumInitializations = 0;
+    // This callback observes markLiveInternalFunction without seeding other
+    // AAs.
+    AC.InitializationCallback = [&](Attributor &, const Function &) {
+      ++NumInitializations;
+    };
+    Attributor A(Functions, InfoCache, AC);
+    auto CallerPos = IRPosition::function(*M.getFunction("caller"));
+    EXPECT_EQ(A.lookupAAFor<AAIsDead>(CallerPos, nullptr, DepClassTy::NONE,
+                                      /*AllowInvalidState=*/true),
+              nullptr);
+    if (SeedCaller) {
+      // Control: discovering a live block during seeding still seeds callees.
+      A.getOrCreateAAFor<AAIsDead>(CallerPos);
+      EXPECT_EQ(NumInitializations, 2u);
+    }
+
+    // Without an initial AA, cleanup's dead-function check creates caller's
+    // liveness AA. Its initializer must not seed leaf or sibling.
+    EXPECT_EQ(A.run(), ChangeStatus::UNCHANGED);
+    EXPECT_NE(A.lookupAAFor<AAIsDead>(CallerPos, nullptr, DepClassTy::NONE,
+                                      /*AllowInvalidState=*/true),
+              nullptr);
+    EXPECT_EQ(NumInitializations, SeedCaller ? 2u : 0u);
+  }
+}
+
 } // namespace llvm

>From 9e090d13512b376ea01a8397905d5cb3b52ae4c9 Mon Sep 17 00:00:00 2001
From: "zhangjiatong.0" <zhangjiatong.0 at bytedance.com>
Date: Wed, 9 Sep 2026 13:46:22 +0800
Subject: [PATCH 2/5] clang format

---
 llvm/include/llvm/Transforms/IPO/Attributor.h | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/llvm/include/llvm/Transforms/IPO/Attributor.h b/llvm/include/llvm/Transforms/IPO/Attributor.h
index 4cce13397a882..066883afafb95 100644
--- a/llvm/include/llvm/Transforms/IPO/Attributor.h
+++ b/llvm/include/llvm/Transforms/IPO/Attributor.h
@@ -1823,7 +1823,8 @@ struct Attributor {
 
   /// Return whether attributes can participate in fixed-point deduction.
   bool isDuringDeduction() const {
-    return Phase == AttributorPhase::SEEDING || Phase == AttributorPhase::UPDATE;
+    return Phase == AttributorPhase::SEEDING ||
+           Phase == AttributorPhase::UPDATE;
   }
 
   /// Mark the internal function \p F as live.

>From 125b07bcd41112df0b37c1f90668494583af8cd6 Mon Sep 17 00:00:00 2001
From: "zhangjiatong.0" <zhangjiatong.0 at bytedance.com>
Date: Thu, 10 Sep 2026 14:08:50 +0800
Subject: [PATCH 3/5] Add IR test

---
 .../Transforms/IPO/AttributorAttributes.cpp   |  6 +-
 .../Attributor/cleanup-no-seeding.ll          | 55 +++++++++++++++++++
 .../Transforms/IPO/AttributorTest.cpp         | 48 ----------------
 3 files changed, 60 insertions(+), 49 deletions(-)
 create mode 100644 llvm/test/Transforms/Attributor/cleanup-no-seeding.ll

diff --git a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
index ac4268f78143d..613d30922fddd 100644
--- a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
+++ b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
@@ -4683,8 +4683,12 @@ struct AAIsDeadFunction : public AAIsDead {
     for (const Instruction &I : BB)
       if (const auto *CB = dyn_cast<CallBase>(&I))
         if (auto *F = dyn_cast_if_present<Function>(CB->getCalledOperand()))
-          if (F->hasLocalLinkage())
+          if (F->hasLocalLinkage()) {
+            LLVM_DEBUG(dbgs() << "[AAIsDead] Seeding live internal callee "
+                              << F->getName() << " from "
+                              << BB.getParent()->getName() << "\n");
             A.markLiveInternalFunction(*F);
+          }
     return true;
   }
 
diff --git a/llvm/test/Transforms/Attributor/cleanup-no-seeding.ll b/llvm/test/Transforms/Attributor/cleanup-no-seeding.ll
new file mode 100644
index 0000000000000..88092927aa60b
--- /dev/null
+++ b/llvm/test/Transforms/Attributor/cleanup-no-seeding.ll
@@ -0,0 +1,55 @@
+; REQUIRES: asserts
+; RUN: split-file %s %t
+; RUN: opt -passes=attributor -debug-only=attributor -S %t/dead.ll 2>&1 | FileCheck %s --check-prefix=CLEANUP --implicit-check-not="Seeding live internal callee" --implicit-check-not="define "
+; RUN: opt -passes=attributor -debug-only=attributor -disable-output %t/live.ll 2>&1 | FileCheck %s --check-prefix=SEED
+;
+; With no external entry into the internal cycle, deduction does not seed any
+; AAs. Cleanup's dead-function check creates liveness AAs while inspecting the
+; calls in the cycle. Initializing a live block at this point must not seed
+; its internal callees: deduction and manifestation have already finished.
+;
+; Without the phase check, initializing b's liveness AA seeds a and c during
+; cleanup. All three functions are deleted either way, so checking their
+; deletion alone would not cover the fix.
+;
+; CLEANUP: Identified and initialized 0 abstract attributes.
+; CLEANUP: Delete/replace at least 0 functions
+; CLEANUP: Call site callback failed for {{ *}}call void @b()
+; CLEANUP: Deleted 3 functions after manifest.
+; CLEANUP: source_filename =
+;
+; A live external caller still seeds its internal callees during deduction.
+; SEED: [AAIsDead] Seeding live internal callee a from caller
+; SEED: [AAIsDead] Seeding live internal callee b from caller
+; SEED: Identified and initialized
+
+;--- dead.ll
+define internal void @a() {
+  call void @b()
+  ret void
+}
+
+define internal void @b() {
+  call void @a()
+  call void @c()
+  ret void
+}
+
+define internal void @c() {
+  ret void
+}
+
+;--- live.ll
+define void @caller() {
+  call void @a()
+  call void @b()
+  ret void
+}
+
+define internal void @a() {
+  ret void
+}
+
+define internal void @b() {
+  ret void
+}
diff --git a/llvm/unittests/Transforms/IPO/AttributorTest.cpp b/llvm/unittests/Transforms/IPO/AttributorTest.cpp
index 9d3d64f75a458..8d90b308f840c 100644
--- a/llvm/unittests/Transforms/IPO/AttributorTest.cpp
+++ b/llvm/unittests/Transforms/IPO/AttributorTest.cpp
@@ -212,52 +212,4 @@ TEST_F(AttributorTestBase, AAReachabilityTest) {
   ASSERT_FALSE(F9AA.instructionCanReach(A, F9FirstInst, F4));
 }
 
-TEST_F(AttributorTestBase, CleanupDoesNotSeedLiveCallees) {
-  for (bool SeedCaller : {false, true}) {
-    SCOPED_TRACE(SeedCaller);
-    Module &M = parseModule(R"(
-      define internal void @leaf() { ret void }
-      define internal void @sibling() { ret void }
-      define void @caller() {
-        call void @leaf()
-        call void @sibling()
-        ret void
-      }
-    )");
-    SetVector<Function *> Functions;
-    for (Function &F : M)
-      Functions.insert(&F);
-    AnalysisGetter AG;
-    CallGraphUpdater CGUpdater;
-    BumpPtrAllocator Allocator;
-    InformationCache InfoCache(M, AG, Allocator, nullptr);
-    AttributorConfig AC(CGUpdater);
-    AC.DefaultInitializeLiveInternals = false;
-    unsigned NumInitializations = 0;
-    // This callback observes markLiveInternalFunction without seeding other
-    // AAs.
-    AC.InitializationCallback = [&](Attributor &, const Function &) {
-      ++NumInitializations;
-    };
-    Attributor A(Functions, InfoCache, AC);
-    auto CallerPos = IRPosition::function(*M.getFunction("caller"));
-    EXPECT_EQ(A.lookupAAFor<AAIsDead>(CallerPos, nullptr, DepClassTy::NONE,
-                                      /*AllowInvalidState=*/true),
-              nullptr);
-    if (SeedCaller) {
-      // Control: discovering a live block during seeding still seeds callees.
-      A.getOrCreateAAFor<AAIsDead>(CallerPos);
-      EXPECT_EQ(NumInitializations, 2u);
-    }
-
-    // Without an initial AA, cleanup's dead-function check creates caller's
-    // liveness AA. Its initializer must not seed leaf or sibling.
-    EXPECT_EQ(A.run(), ChangeStatus::UNCHANGED);
-    EXPECT_NE(A.lookupAAFor<AAIsDead>(CallerPos, nullptr, DepClassTy::NONE,
-                                      /*AllowInvalidState=*/true),
-              nullptr);
-    EXPECT_EQ(NumInitializations, SeedCaller ? 2u : 0u);
-  }
-}
-
 } // namespace llvm

>From bf3a71a5ab57672f5ffb7e78cd3152f12095d272 Mon Sep 17 00:00:00 2001
From: "zhangjiatong.0" <zhangjiatong.0 at bytedance.com>
Date: Thu, 10 Sep 2026 14:36:32 +0800
Subject: [PATCH 4/5] Avoid adding more LLVM_DEBUG for test

---
 .../Transforms/IPO/AttributorAttributes.cpp   |  6 +--
 .../Attributor/cleanup-no-seeding.ll          | 39 +++++++++++--------
 2 files changed, 23 insertions(+), 22 deletions(-)

diff --git a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
index 613d30922fddd..ac4268f78143d 100644
--- a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
+++ b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
@@ -4683,12 +4683,8 @@ struct AAIsDeadFunction : public AAIsDead {
     for (const Instruction &I : BB)
       if (const auto *CB = dyn_cast<CallBase>(&I))
         if (auto *F = dyn_cast_if_present<Function>(CB->getCalledOperand()))
-          if (F->hasLocalLinkage()) {
-            LLVM_DEBUG(dbgs() << "[AAIsDead] Seeding live internal callee "
-                              << F->getName() << " from "
-                              << BB.getParent()->getName() << "\n");
+          if (F->hasLocalLinkage())
             A.markLiveInternalFunction(*F);
-          }
     return true;
   }
 
diff --git a/llvm/test/Transforms/Attributor/cleanup-no-seeding.ll b/llvm/test/Transforms/Attributor/cleanup-no-seeding.ll
index 88092927aa60b..822319f9c8fe9 100644
--- a/llvm/test/Transforms/Attributor/cleanup-no-seeding.ll
+++ b/llvm/test/Transforms/Attributor/cleanup-no-seeding.ll
@@ -1,27 +1,32 @@
 ; REQUIRES: asserts
 ; RUN: split-file %s %t
-; RUN: opt -passes=attributor -debug-only=attributor -S %t/dead.ll 2>&1 | FileCheck %s --check-prefix=CLEANUP --implicit-check-not="Seeding live internal callee" --implicit-check-not="define "
-; RUN: opt -passes=attributor -debug-only=attributor -disable-output %t/live.ll 2>&1 | FileCheck %s --check-prefix=SEED
+; RUN: opt -passes=attributor -stats -stats-json -S %t/dead.ll 2>&1 | FileCheck %s --check-prefix=CLEANUP --implicit-check-not="define "
+; RUN: opt -passes=attributor -S %t/live.ll | FileCheck %s --check-prefix=LIVE --implicit-check-not="define internal"
 ;
-; With no external entry into the internal cycle, deduction does not seed any
-; AAs. Cleanup's dead-function check creates liveness AAs while inspecting the
-; calls in the cycle. Initializing a live block at this point must not seed
-; its internal callees: deduction and manifestation have already finished.
+; With no external entry into the internal cycle, no AAs are seeded during
+; deduction. Cleanup only needs the liveness AAs for a and b to inspect uses
+; and delete all three functions. It must not seed additional callee AAs.
 ;
-; Without the phase check, initializing b's liveness AA seeds a and c during
-; cleanup. All three functions are deleted either way, so checking their
-; deletion alone would not cover the fix.
+; Without the phase check in assumeLive, initializing b's liveness AA seeds
+; additional AAs for a and c. All three functions are deleted either way, so
+; checking their deletion alone would not cover the fix.
+;
+; The expected two AAs are AAIsDeadFunction for a and b. Checking the call to c
+; only needs b's liveness AA, since b contains that call.
+; Without the phase check, assumeLive(b) calls markLiveInternalFunction for a
+; and c, triggering default AA initialization. This can create additional
+; function AAs, such as memory behavior and heap-to-stack, as well as AAs for
+; a's call to b. The exact additional set depends on the enabled analyses;
+; the test checks that only the required liveness AAs are created.
 ;
-; CLEANUP: Identified and initialized 0 abstract attributes.
-; CLEANUP: Delete/replace at least 0 functions
-; CLEANUP: Call site callback failed for {{ *}}call void @b()
-; CLEANUP: Deleted 3 functions after manifest.
 ; CLEANUP: source_filename =
+; CLEANUP: "attributor.NumAAs": 2,
+; CLEANUP: "attributor.NumFnDeleted": 3,
 ;
-; A live external caller still seeds its internal callees during deduction.
-; SEED: [AAIsDead] Seeding live internal callee a from caller
-; SEED: [AAIsDead] Seeding live internal callee b from caller
-; SEED: Identified and initialized
+; The external caller allows normal deduction to optimize away its calls.
+; LIVE-LABEL: define void @caller()
+; LIVE-NEXT:    ret void
+; LIVE-NEXT:  }
 
 ;--- dead.ll
 define internal void @a() {

>From 6566fb9ae1c28fa480c1b597c47df969c3ca0cac Mon Sep 17 00:00:00 2001
From: "zhangjiatong.0" <zhangjiatong.0 at bytedance.com>
Date: Fri, 11 Sep 2026 15:18:52 +0800
Subject: [PATCH 5/5] Switch back to debug output checks

---
 .../Transforms/IPO/AttributorAttributes.cpp   | 10 ++++-
 .../Attributor/cleanup-no-seeding.ll          | 39 ++++++++-----------
 2 files changed, 26 insertions(+), 23 deletions(-)

diff --git a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
index ac4268f78143d..663f191c6681c 100644
--- a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
+++ b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
@@ -4683,8 +4683,16 @@ struct AAIsDeadFunction : public AAIsDead {
     for (const Instruction &I : BB)
       if (const auto *CB = dyn_cast<CallBase>(&I))
         if (auto *F = dyn_cast_if_present<Function>(CB->getCalledOperand()))
-          if (F->hasLocalLinkage())
+          if (F->hasLocalLinkage()) {
+            LLVM_DEBUG({
+              dbgs() << "[AAIsDead] Seeding live internal callee ";
+              F->printAsOperand(dbgs(), /*PrintType=*/false);
+              dbgs() << " from ";
+              BB.getParent()->printAsOperand(dbgs(), /*PrintType=*/false);
+              dbgs() << "\n";
+            });
             A.markLiveInternalFunction(*F);
+          }
     return true;
   }
 
diff --git a/llvm/test/Transforms/Attributor/cleanup-no-seeding.ll b/llvm/test/Transforms/Attributor/cleanup-no-seeding.ll
index 822319f9c8fe9..4126b0a7254a8 100644
--- a/llvm/test/Transforms/Attributor/cleanup-no-seeding.ll
+++ b/llvm/test/Transforms/Attributor/cleanup-no-seeding.ll
@@ -1,32 +1,27 @@
 ; REQUIRES: asserts
 ; RUN: split-file %s %t
-; RUN: opt -passes=attributor -stats -stats-json -S %t/dead.ll 2>&1 | FileCheck %s --check-prefix=CLEANUP --implicit-check-not="define "
-; RUN: opt -passes=attributor -S %t/live.ll | FileCheck %s --check-prefix=LIVE --implicit-check-not="define internal"
+; RUN: opt -passes=attributor -debug-only=attributor -S %t/dead.ll 2>&1 | FileCheck %s --check-prefix=CLEANUP --implicit-check-not="Seeding live internal callee" --implicit-check-not="define "
+; RUN: opt -passes=attributor -debug-only=attributor -disable-output %t/live.ll 2>&1 | FileCheck %s --check-prefix=SEED
 ;
-; With no external entry into the internal cycle, no AAs are seeded during
-; deduction. Cleanup only needs the liveness AAs for a and b to inspect uses
-; and delete all three functions. It must not seed additional callee AAs.
+; With no external entry into the internal cycle, deduction does not seed any
+; AAs. Cleanup's dead-function check creates liveness AAs while inspecting the
+; calls in the cycle. Initializing a live block at this point must not seed
+; its internal callees: deduction and manifestation have already finished.
 ;
-; Without the phase check in assumeLive, initializing b's liveness AA seeds
-; additional AAs for a and c. All three functions are deleted either way, so
-; checking their deletion alone would not cover the fix.
-;
-; The expected two AAs are AAIsDeadFunction for a and b. Checking the call to c
-; only needs b's liveness AA, since b contains that call.
-; Without the phase check, assumeLive(b) calls markLiveInternalFunction for a
-; and c, triggering default AA initialization. This can create additional
-; function AAs, such as memory behavior and heap-to-stack, as well as AAs for
-; a's call to b. The exact additional set depends on the enabled analyses;
-; the test checks that only the required liveness AAs are created.
+; Without the phase check, initializing b's liveness AA seeds a and c during
+; cleanup. All three functions are deleted either way, so checking their
+; deletion alone would not cover the fix.
 ;
+; CLEANUP: Identified and initialized 0 abstract attributes.
+; CLEANUP: Delete/replace at least 0 functions
+; CLEANUP: Call site callback failed for {{ *}}call void @b()
+; CLEANUP: Deleted 3 functions after manifest.
 ; CLEANUP: source_filename =
-; CLEANUP: "attributor.NumAAs": 2,
-; CLEANUP: "attributor.NumFnDeleted": 3,
 ;
-; The external caller allows normal deduction to optimize away its calls.
-; LIVE-LABEL: define void @caller()
-; LIVE-NEXT:    ret void
-; LIVE-NEXT:  }
+; A live external caller still seeds its internal callees during deduction.
+; SEED: [AAIsDead] Seeding live internal callee @a from @caller
+; SEED: [AAIsDead] Seeding live internal callee @b from @caller
+; SEED: Identified and initialized
 
 ;--- dead.ll
 define internal void @a() {



More information about the llvm-commits mailing list