[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