[llvm] [Attributor] Treat a use by an externally visible global as an escape (PR #228283)

Mian Miftah via llvm-commits llvm-commits at lists.llvm.org
Thu Oct 1 18:00:59 PDT 2026


https://github.com/mmiftahx updated https://github.com/llvm/llvm-project/pull/228283

>From 2d786a621ea4fcc346cc3ed2bdf2a2d1b43a2c1c Mon Sep 17 00:00:00 2001
From: mmiftahx <mmiftah.duna at gmail.com>
Date: Wed, 30 Sep 2026 22:23:24 -0500
Subject: [PATCH 1/2] [Attributor] Treat a use by an externally visible global
 as an escape

AAGlobalValueInfo collects the in-module uses of a local function, and
AAIndirectCallInfo uses them to drop !callees targets whose address
cannot reach the call. It follows uses through global initializers
without checking whether the global is visible outside the module, so
it misses an address that escapes that way.

For an internal function whose address is stored in an external global
and that is called through a pointer argument, the Attributor drops the
only !callees target and replaces the call with unreachable. This
happens in the module pass, in the CGSCC pass when the function and the
caller are in one SCC, and in OpenMPOpt, which runs the Attributor on
OpenMP device code at -O1 and above.

Treat a use by a global without local linkage as an escape unless the
module is a closed world.
---
 .../Transforms/IPO/AttributorAttributes.cpp   |  5 ++
 .../Attributor/callees-metadata-escape.ll     | 59 +++++++++++++++++++
 2 files changed, 64 insertions(+)
 create mode 100644 llvm/test/Transforms/Attributor/callees-metadata-escape.ll

diff --git a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
index c2a896be0064d..3bf737743749a 100644
--- a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
+++ b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
@@ -12250,6 +12250,11 @@ struct AAGlobalValueInfoFloating : public AAGlobalValueInfo {
                 SmallVectorImpl<const Value *> &Worklist) {
     Instruction *UInst = dyn_cast<Instruction>(U.getUser());
     if (!UInst) {
+      // Outside a closed world, code outside the module can read an
+      // externally visible global, so the value escapes through it.
+      if (auto *GV = dyn_cast<GlobalValue>(U.getUser()))
+        if (!GV->hasLocalLinkage() && !A.isClosedWorldModule())
+          return false;
       Follow = true;
       return true;
     }
diff --git a/llvm/test/Transforms/Attributor/callees-metadata-escape.ll b/llvm/test/Transforms/Attributor/callees-metadata-escape.ll
new file mode 100644
index 0000000000000..0b5af754954bc
--- /dev/null
+++ b/llvm/test/Transforms/Attributor/callees-metadata-escape.ll
@@ -0,0 +1,59 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals all --version 6
+; RUN: opt -passes=attributor -S < %s | FileCheck %s --check-prefixes=CHECK,TUNIT
+; RUN: opt -passes=attributor-cgscc -S < %s | FileCheck %s --check-prefixes=CHECK,CGSCC
+
+; @f is internal, but its address is stored in the externally visible @h,
+; so code outside the module can load it and pass it to @call_through.
+; The only !callees target must not be dropped. @f and @call_through call
+; each other directly so that the CGSCC pass sees them in one SCC.
+
+ at h = constant ptr @f
+ at sink = global i32 0
+
+;.
+; CHECK: @h = constant ptr @f
+; CHECK: @sink = global i32 0
+;.
+define internal void @f() {
+; CHECK-LABEL: define internal void @f(
+; CHECK-SAME: ) #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT:    store volatile i32 1, ptr @sink, align 4
+; CHECK-NEXT:    call void @call_through(ptr nofree noundef null, i1 noundef false) #[[ATTR0]]
+; CHECK-NEXT:    ret void
+;
+  store volatile i32 1, ptr @sink
+  call void @call_through(ptr null, i1 false)
+  ret void
+}
+
+define void @call_through(ptr %p, i1 %go) {
+; CHECK-LABEL: define void @call_through(
+; CHECK-SAME: ptr nofree captures(none) [[P:%.*]], i1 noundef [[GO:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    br i1 [[GO]], label %[[DO:.*]], label %[[DONE:.*]]
+; CHECK:       [[DO]]:
+; CHECK-NEXT:    call void @f() #[[ATTR0]]
+; CHECK-NEXT:    call void @f() #[[ATTR0]]
+; CHECK-NEXT:    br label %[[DONE]]
+; CHECK:       [[DONE]]:
+; CHECK-NEXT:    ret void
+;
+entry:
+  br i1 %go, label %do, label %done
+
+do:
+  call void %p(), !callees !0
+  call void @f()
+  br label %done
+
+done:
+  ret void
+}
+
+!0 = !{ptr @f}
+;.
+; CHECK: attributes #[[ATTR0]] = { nofree nosync nounwind }
+;.
+;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
+; CGSCC: {{.*}}
+; TUNIT: {{.*}}

>From 8015e0be0c26c54679ca2cd94a6ba45559a3fe51 Mon Sep 17 00:00:00 2001
From: mmiftahx <mmiftah.duna at gmail.com>
Date: Thu, 1 Oct 2026 19:58:23 -0500
Subject: [PATCH 2/2] Add an internal global case to the test and drop volatile

---
 .../Attributor/callees-metadata-escape.ll     | 42 ++++++++++++++++---
 1 file changed, 36 insertions(+), 6 deletions(-)

diff --git a/llvm/test/Transforms/Attributor/callees-metadata-escape.ll b/llvm/test/Transforms/Attributor/callees-metadata-escape.ll
index 0b5af754954bc..20fa0a25e7933 100644
--- a/llvm/test/Transforms/Attributor/callees-metadata-escape.ll
+++ b/llvm/test/Transforms/Attributor/callees-metadata-escape.ll
@@ -6,22 +6,28 @@
 ; so code outside the module can load it and pass it to @call_through.
 ; The only !callees target must not be dropped. @f and @call_through call
 ; each other directly so that the CGSCC pass sees them in one SCC.
+;
+; @g's address is only stored in the internal @hg, which nothing reads, so
+; the module pass still drops the target in @call_g. The CGSCC pass doesn't
+; see @g's uses from @call_g's SCC and keeps it.
 
 @h = constant ptr @f
+ at hg = internal constant ptr @g
 @sink = global i32 0
 
 ;.
 ; CHECK: @h = constant ptr @f
+; CHECK: @hg = internal constant ptr @g
 ; CHECK: @sink = global i32 0
 ;.
 define internal void @f() {
 ; CHECK-LABEL: define internal void @f(
 ; CHECK-SAME: ) #[[ATTR0:[0-9]+]] {
-; CHECK-NEXT:    store volatile i32 1, ptr @sink, align 4
+; CHECK-NEXT:    store i32 1, ptr @sink, align 4
 ; CHECK-NEXT:    call void @call_through(ptr nofree noundef null, i1 noundef false) #[[ATTR0]]
 ; CHECK-NEXT:    ret void
 ;
-  store volatile i32 1, ptr @sink
+  store i32 1, ptr @sink
   call void @call_through(ptr null, i1 false)
   ret void
 }
@@ -50,10 +56,34 @@ done:
   ret void
 }
 
+define internal void @g() {
+; CHECK-LABEL: define internal void @g(
+; CHECK-SAME: ) #[[ATTR1:[0-9]+]] {
+; CHECK-NEXT:    ret void
+;
+  ret void
+}
+
+define void @call_g(ptr %p) {
+; TUNIT-LABEL: define void @call_g(
+; TUNIT-SAME: ptr nofree noundef nonnull readnone captures(none) [[P:%.*]]) #[[ATTR1]] {
+; TUNIT-NEXT:    unreachable
+;
+; CGSCC-LABEL: define void @call_g(
+; CGSCC-SAME: ptr nofree noundef nonnull captures(none) [[P:%.*]]) {
+; CGSCC-NEXT:    call void @g()
+; CGSCC-NEXT:    ret void
+;
+  call void %p(), !callees !1
+  ret void
+}
+
 !0 = !{ptr @f}
+!1 = !{ptr @g}
+;.
+; TUNIT: attributes #[[ATTR0]] = { nofree nosync nounwind memory(write) }
+; TUNIT: attributes #[[ATTR1]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(none) }
 ;.
-; CHECK: attributes #[[ATTR0]] = { nofree nosync nounwind }
+; CGSCC: attributes #[[ATTR0]] = { nofree nosync nounwind memory(write) }
+; CGSCC: attributes #[[ATTR1]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(none) }
 ;.
-;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
-; CGSCC: {{.*}}
-; TUNIT: {{.*}}



More information about the llvm-commits mailing list