CallGraph: Fix IgnoreAssumeLikeCalls option to Function::hasAddressTaken
authorMatt Arsenault <Matthew.Arsenault@amd.com>
Fri, 2 Dec 2022 23:30:19 +0000 (18:30 -0500)
committerMatt Arsenault <Matthew.Arsenault@amd.com>
Tue, 6 Dec 2022 02:41:59 +0000 (21:41 -0500)
This was added in 29e2d9461a91b and likely never worked in a useful
way.

The test added for it fails when converted to opaque pointers, since
the lifetime intrinsic now directly uses the address. The code was
only trying to handle a user indirectly through a bitcast
instruction. That would never have been useful; a bitcast of a global
value would be folded to a ConstantExpr cast.

I also don't understand why it was special casing use_empty on the
cast. Relax the check to be either BitCastOperator or
AddrSpaceCastOperator. In practice, BitCastOperator won't appear
today.

I believe the change in parallel_deletion_cg_update is a correct
improvement but I didn't fully follow it. .omp_outlined..0 is used in
a constant expression cast to a call which ends up getting deleted.

llvm/lib/IR/Function.cpp
llvm/test/Analysis/CallGraph/ignore-assumelike-calls.ll
llvm/test/Transforms/OpenMP/parallel_deletion_cg_update.ll

index 9912e1a..792e714 100644 (file)
@@ -1876,6 +1876,10 @@ std::optional<Function *> Intrinsic::remangleIntrinsicFunction(Function *F) {
   return NewDecl;
 }
 
+static bool isPointerCastOperator(const User *U) {
+  return isa<AddrSpaceCastOperator>(U) || isa<BitCastOperator>(U);
+}
+
 /// hasAddressTaken - returns true if there are any uses of this function
 /// other than direct calls or invokes to it. Optionally ignores callback
 /// uses, assume like pointer annotation calls, and references in llvm.used
@@ -1897,17 +1901,15 @@ bool Function::hasAddressTaken(const User **PutOffender,
 
     const auto *Call = dyn_cast<CallBase>(FU);
     if (!Call) {
-      if (IgnoreAssumeLikeCalls) {
-        if (const auto *FI = dyn_cast<Instruction>(FU)) {
-          if (FI->isCast() && !FI->user_empty() &&
-              llvm::all_of(FU->users(), [](const User *U) {
-                if (const auto *I = dyn_cast<IntrinsicInst>(U))
-                  return I->isAssumeLikeIntrinsic();
-                return false;
-              }))
-            continue;
-        }
+      if (IgnoreAssumeLikeCalls && isPointerCastOperator(FU) &&
+          all_of(FU->users(), [](const User *U) {
+            if (const auto *I = dyn_cast<IntrinsicInst>(U))
+              return I->isAssumeLikeIntrinsic();
+            return false;
+          })) {
+        continue;
       }
+
       if (IgnoreLLVMUsed && !FU->user_empty()) {
         const User *FUU = FU;
         if (isa<BitCastOperator>(FU) && FU->hasOneUse() &&
@@ -1926,6 +1928,13 @@ bool Function::hasAddressTaken(const User **PutOffender,
         *PutOffender = FU;
       return true;
     }
+
+    if (IgnoreAssumeLikeCalls) {
+      if (const auto *I = dyn_cast<IntrinsicInst>(Call))
+        if (I->isAssumeLikeIntrinsic())
+          continue;
+    }
+
     if (!Call->isCallee(&U) || Call->getFunctionType() != getFunctionType()) {
       if (IgnoreARCAttachedCall &&
           Call->isOperandBundleOfType(LLVMContext::OB_clang_arc_attachedcall,
index 4bacec4..1c559f2 100644 (file)
@@ -1,26 +1,63 @@
 ; RUN: opt < %s -print-callgraph -disable-output 2>&1 | FileCheck %s
 ; CHECK: Call graph node <<null function>><<{{.*}}>>  #uses=0
-; CHECK-NEXT:   CS<None> calls function 'cast_only'
-; CHECK-NEXT:   CS<None> calls function 'llvm.lifetime.start.p0i8'
+; CHECK-NEXT:   CS<None> calls function 'other_intrinsic_use'
+; CHECK-NEXT:   CS<None> calls function 'other_cast_intrinsic_use'
+; CHECK-NEXT:   CS<None> calls function 'llvm.lifetime.start.p0'
+; CHECK-NEXT:   CS<None> calls function 'llvm.memset.p0.i64'
+; CHECK-NEXT:   CS<None> calls function 'llvm.memset.p1.i64'
 ; CHECK-EMPTY:
-; CHECK-NEXT:   Call graph node for function: 'cast_only'<<{{.*}}>>  #uses=1
+; CHECK-NEXT:   Call graph node for function: 'addrspacecast_only'<<{{.*}}>>  #uses=0
 ; CHECK-EMPTY:
-; CHECK-NEXT:   Call graph node for function: 'llvm.lifetime.start.p0i8'<<{{.*}}>>  #uses=1
+; CHECK-NEXT:   Call graph node for function: 'bitcast_only'<<{{.*}}>>  #uses=0
+; CHECK-EMPTY:
+; CHECK-NEXT:   Call graph node for function: 'llvm.lifetime.start.p0'<<{{.*}}>>  #uses=1
+; CHECK-EMPTY:
+; CHECK-NEXT:   Call graph node for function: 'llvm.memset.p0.i64'<<{{.*}}>>  #uses=1
+; CHECK-EMPTY:
+; CHECK-NEXT:   Call graph node for function: 'llvm.memset.p1.i64'<<{{.*}}>>  #uses=1
+; CHECK-EMPTY:
+; CHECK-NEXT:   Call graph node for function: 'other_cast_intrinsic_use'<<{{.*}}>>  #uses=1
+; CHECK-EMPTY:
+; CHECK-NEXT:   Call graph node for function: 'other_intrinsic_use'<<{{.*}}>>  #uses=1
 ; CHECK-EMPTY:
 ; CHECK-NEXT:   Call graph node for function: 'used_by_lifetime'<<{{.*}}>>  #uses=0
 ; CHECK-EMPTY:
+; CHECK-NEXT:   Call graph node for function: 'used_by_lifetime_cast'<<{{.*}}>>  #uses=0
+; CHECK-EMPTY:
 
 define internal void @used_by_lifetime() {
 entry:
-  %c = bitcast void()* @used_by_lifetime to i8*
-  call void @llvm.lifetime.start.p0i8(i64 4, i8* %c)
+  call void @llvm.lifetime.start.p0(i64 4, ptr @used_by_lifetime)
   ret void
 }
 
-define internal void @cast_only() {
+define internal void @used_by_lifetime_cast() addrspace(1) {
+  call void @llvm.lifetime.start.p0(i64 4, ptr addrspacecast (ptr addrspace(1) @used_by_lifetime_cast to ptr))
+  ret void
+}
+
+define internal void @bitcast_only() {
 entry:
-  %c = bitcast void()* @cast_only to i8*
+  %c = bitcast ptr @bitcast_only to ptr
+  ret void
+}
+
+define internal void @addrspacecast_only() addrspace(1) {
+entry:
+  %c = addrspacecast ptr addrspace(1) @addrspacecast_only to ptr
+  ret void
+}
+
+define internal void @other_intrinsic_use() {
+  call void @llvm.memset.p0.i64(ptr @other_intrinsic_use, i8 0, i64 1024, i1 false)
+  ret void
+}
+
+define internal void @other_cast_intrinsic_use() {
+  call void @llvm.memset.p1.i64(ptr addrspace(1) addrspacecast (ptr @other_cast_intrinsic_use to ptr addrspace(1)), i8 0, i64 1024, i1 false)
   ret void
 }
 
-declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture)
+declare void @llvm.lifetime.start.p0(i64, ptr nocapture)
+declare void @llvm.memset.p0.i64(ptr, i8, i64, i1 immarg)
+declare void @llvm.memset.p1.i64(ptr addrspace(1), i8, i64, i1 immarg)
index 3e3efee..17db5d0 100644 (file)
@@ -1,15 +1,14 @@
 ; RUN: opt < %s -passes='function(instcombine),cgscc(attributor-cgscc),print-callgraph' -disable-output 2>&1 | FileCheck %s
 
 ; CHECK: Call graph node <<null function>><<{{.*}}>>  #uses=0
-; CHECK:   CS<None> calls function 'dead_fork_call'
-; CHECK:   CS<None> calls function '.omp_outlined..0'
-; CHECK:   CS<None> calls function '__kmpc_fork_call'
-; CHECK:   CS<None> calls function 'live_fork_call'
-; CHECK:   CS<None> calls function '.omp_outlined..1'
-; CHECK:   CS<None> calls function 'd'
+; CHECK-NEXT:   CS<None> calls function 'dead_fork_call'
+; CHECK-NEXT:   CS<None> calls function '__kmpc_fork_call'
+; CHECK-NEXT:   CS<None> calls function 'live_fork_call'
+; CHECK-NEXT:   CS<None> calls function '.omp_outlined..1'
+; CHECK-NEXT:   CS<None> calls function 'd'
+;
+; CHECK: Call graph node for function: '.omp_outlined..0'<<{{.*}}>>  #uses=0
 ;
-; CHECK: Call graph node for function: '.omp_outlined..0'<<{{.*}}>>  #uses=1
-; 
 ; CHECK: Call graph node for function: '.omp_outlined..1'<<{{.*}}>>  #uses=3
 ; CHECK:   CS<{{.*}}> calls function 'd'
 ;