[llvm] [Attributor] Skip the dead-internal-function walk for AAs that will not be updated (PR #227187)

Farid Zakaria via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 28 21:06:06 PDT 2026


https://github.com/fzakaria created https://github.com/llvm/llvm-project/pull/227187

AAIsDeadFunction::initialize calls isAssumedDeadInternalFunction, which runs checkForAllCallSites and so creates an AAIsDead for every internal caller, each of which repeats the walk. Initialization therefore recurses through the whole cone of internal callers.

When the AA will not be updated (outside of deduction, or for a function the Attributor is not run on), getOrCreateAAFor fixes it pessimistically right after initialize returns, discarding the walk's result. Skip the walk in that case and assume the entry block live.

This is the dominant cost of the CGSCC OpenMPOpt pass, whose cleanupIR queries the liveness of callers outside the current SCC on every SCC, giving (#SCCs) x (depth of the internal caller chain). #222226 removed the callee-seeding half of this cost; this removes the walk itself.

cleanup-no-seeding.ll drops a debug check that only the removed walk printed.

### Synthetic benchmark
A chain of n static noinline functions with one `#pragma omp parallel` at the top, compiled to IR with `-fopenmp`, then `opt -passes='default<O2>'` (Release+assertions, X86):

| n | before | after |
|---|---|---|
| 1000 | 0.57s | 0.27s |
| 4000 | 2.42s | 1.15s |
| 8000 | 5.04s | 2.38s |

At n=8000, OpenMPOptCGSCC alone goes from 2.49s to 0.18s. The optimized IR is unchanged on the repro and on the inputs of `llvm/test/Transforms/OpenMP`.

### Additional Context
I profiled the compilers of a large production C++ build that compiles all C/C++ with `-fopenmp` at Meta:
- **Overall:** OpenMPOpt is ~0.17% of all compiler cycles and shows up in ~1.2% of compiles.
- **Long compiles:** in compiles that had been running for 256s or longer, stacks under OpenMPOpt are ~14% of samples, versus ~0.19% across all compiles. They are spread across dozens of distinct translation units.
- **Where the time goes:** about 78% of the OpenMPOpt time is in `cleanupIR` / `identifyDeadInternalFunctions` / `checkForAllCallSites` / `getOrCreateAAFor`, and ~20% is tearing down the per-SCC Attributor state.
- For reference, before #222226 the same repro took 27.3s (clang-21); with both changes it's 2.9s.

>From 43f6b06de81b8a68a72cd7fb98fea84c6d50db58 Mon Sep 17 00:00:00 2001
From: Farid Zakaria <fmzakari at meta.com>
Date: Mon, 28 Sep 2026 20:45:46 -0700
Subject: [PATCH] [Attributor] Skip the dead-internal-function walk for AAs
 that will not be updated

AAIsDeadFunction::initialize calls isAssumedDeadInternalFunction, which
runs checkForAllCallSites and so creates an AAIsDead for every internal
caller, each of which repeats the walk. Initialization therefore recurses
through the whole cone of internal callers.

When the AA will not be updated (outside of deduction, or for a function
the Attributor is not run on), getOrCreateAAFor fixes it pessimistically
right after initialize returns, discarding the walk's result. Skip the
walk in that case and assume the entry block live.

This is the dominant cost of the CGSCC OpenMPOpt pass, whose cleanupIR
queries the liveness of callers outside the current SCC on every SCC,
giving (#SCCs) x (depth of the internal caller chain). #222226 removed
the callee-seeding half of this cost; this removes the walk itself.

cleanup-no-seeding.ll drops a debug check that only the removed walk
printed.

Assisted-by: Claude
---
 llvm/lib/Transforms/IPO/AttributorAttributes.cpp      | 7 ++++++-
 llvm/test/Transforms/Attributor/cleanup-no-seeding.ll | 1 -
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
index c2a896be0064d..ae12d8e32cd3a 100644
--- a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
+++ b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
@@ -4534,7 +4534,12 @@ struct AAIsDeadFunction : public AAIsDead {
   void initialize(Attributor &A) override {
     Function *F = getAnchorScope();
     assert(F && "Did expect an anchor function");
-    if (!isAssumedDeadInternalFunction(A)) {
+    // An AA that will not be updated is fixed pessimistically right after
+    // initialization, so an assumed-dead result would be discarded. Checking
+    // the call sites recursively initializes the liveness of every internal
+    // caller, which in CGSCC cleanup repeats for each callee SCC.
+    if (!A.shouldUpdateAA<AAIsDead>(getIRPosition()) ||
+        !isAssumedDeadInternalFunction(A)) {
       ToBeExploredFrom.insert(&F->getEntryBlock().front());
       assumeLive(A, F->getEntryBlock());
     }
diff --git a/llvm/test/Transforms/Attributor/cleanup-no-seeding.ll b/llvm/test/Transforms/Attributor/cleanup-no-seeding.ll
index 4126b0a7254a8..ad7ce15d92988 100644
--- a/llvm/test/Transforms/Attributor/cleanup-no-seeding.ll
+++ b/llvm/test/Transforms/Attributor/cleanup-no-seeding.ll
@@ -14,7 +14,6 @@
 ;
 ; 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 =
 ;



More information about the llvm-commits mailing list