[llvm] 5bd8175 - [AA] A global cannot escape through nocapture/nocallback call.

Slava Zakharin via llvm-commits llvm-commits at lists.llvm.org
Mon Nov 28 12:57:53 PST 2022


Author: Slava Zakharin
Date: 2022-11-28T12:50:31-08:00
New Revision: 5bd8175dd70416221c58c3c92f06886427680cb5

URL: https://github.com/llvm/llvm-project/commit/5bd8175dd70416221c58c3c92f06886427680cb5
DIFF: https://github.com/llvm/llvm-project/commit/5bd8175dd70416221c58c3c92f06886427680cb5.diff

LOG: [AA] A global cannot escape through nocapture/nocallback call.

When an internal global is passed to a 'nocallback' call as
a 'nocapture' pointer, it cannot escape through this call and
be indirectly referenced in this module.
So it must not alias with any pointer in the module.

This may provide some remedy for Fortran module-private array descriptors
that are usually passed by address to some runtime functions
(e.g. to allocation/deallocation functions). In general, a good aliasing
information derived from Fortran language rules would solve the same issue,
but I think this change may be beneficial as-is (given that nocapture,
nocallback attributes are properly set).

Reviewed By: jdoerfert

Differential Revision: https://reviews.llvm.org/D138336

Added: 
    llvm/test/Analysis/GlobalsModRef/noescape-nocapture-nocallback.ll

Modified: 
    llvm/lib/Analysis/GlobalsModRef.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/lib/Analysis/GlobalsModRef.cpp b/llvm/lib/Analysis/GlobalsModRef.cpp
index 1fe4aa9d414c9..96f41c36001de 100644
--- a/llvm/lib/Analysis/GlobalsModRef.cpp
+++ b/llvm/lib/Analysis/GlobalsModRef.cpp
@@ -354,7 +354,31 @@ bool GlobalsAAResult::AnalyzeUsesOfPointer(Value *V,
           if (Writers)
             Writers->insert(Call->getParent()->getParent());
         } else {
-          return true; // Argument of an unknown call.
+          // In general, we return true for unknown calls, but there are
+          // some simple checks that we can do for functions that
+          // will never call back into the module.
+          auto *F = Call->getCalledFunction();
+          // TODO: we should be able to remove isDeclaration() check
+          // and let the function body analysis check for captures,
+          // and collect the mod-ref effects. This information will
+          // be later propagated via the call graph.
+          if (!F || !F->isDeclaration())
+            return true;
+          // Note that the NoCallback check here is a little bit too
+          // conservative. If there are no captures of the global
+          // in the module, then this call may not be a capture even
+          // if it does not have NoCallback.
+          if (!Call->hasFnAttr(Attribute::NoCallback) ||
+              !Call->isArgOperand(&U) ||
+              !Call->doesNotCapture(Call->getArgOperandNo(&U)))
+            return true;
+
+          // Conservatively, assume the call reads and writes the global.
+          // We could use memory attributes to make it more precise.
+          if (Readers)
+            Readers->insert(Call->getParent()->getParent());
+          if (Writers)
+            Writers->insert(Call->getParent()->getParent());
         }
       }
     } else if (ICmpInst *ICI = dyn_cast<ICmpInst>(I)) {

diff  --git a/llvm/test/Analysis/GlobalsModRef/noescape-nocapture-nocallback.ll b/llvm/test/Analysis/GlobalsModRef/noescape-nocapture-nocallback.ll
new file mode 100644
index 0000000000000..042bfe34c1261
--- /dev/null
+++ b/llvm/test/Analysis/GlobalsModRef/noescape-nocapture-nocallback.ll
@@ -0,0 +1,328 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -aa-pipeline=basic-aa,globals-aa -S -passes='require<globals-aa>,function(loop-mssa(licm))' | FileCheck %s
+
+;Reference C code:
+;struct str {
+;  void **p;
+;};
+;static struct str obj;
+;extern void nocapture_nocallback_func(struct str *);
+;void test(void *p) {
+;  nocapture_nocallback_func(&obj);
+;  for (int i = 0; i < 1000; ++i) {
+;    unknown_call(); // optional
+;    obj.p[i] = p;
+;  }
+;}
+
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+
+%struct.str = type { ptr }
+
+ at obj0 = internal global %struct.str zeroinitializer, align 8
+ at obj1 = internal global %struct.str zeroinitializer, align 8
+ at obj2 = internal global %struct.str zeroinitializer, align 8
+ at obj3 = internal global %struct.str zeroinitializer, align 8
+ at obj4 = internal global %struct.str zeroinitializer, align 8
+ at obj5 = internal global %struct.str zeroinitializer, align 8
+
+define dso_local void @test0(ptr %p) {
+; Check that load from @obj0 is hoisted from the loop, meaning
+; that it does not conflict with the store inside the loop:
+; CHECK-LABEL: @test0(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    call void @nocapture_nocallback_func(ptr @obj0)
+; CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr @obj0, align 8
+; CHECK-NEXT:    br label [[FOR_COND:%.*]]
+; CHECK:       for.cond:
+; CHECK-NEXT:    [[I_0:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[INC:%.*]], [[FOR_INC:%.*]] ]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i32 [[I_0]], 1000
+; CHECK-NEXT:    br i1 [[CMP]], label [[FOR_BODY:%.*]], label [[FOR_END:%.*]]
+; CHECK:       for.body:
+; CHECK-NEXT:    [[IDXPROM:%.*]] = sext i32 [[I_0]] to i64
+; CHECK-NEXT:    [[ARRAYIDX:%.*]] = getelementptr inbounds ptr, ptr [[TMP0]], i64 [[IDXPROM]]
+; CHECK-NEXT:    store ptr [[P:%.*]], ptr [[ARRAYIDX]], align 8
+; CHECK-NEXT:    br label [[FOR_INC]]
+; CHECK:       for.inc:
+; CHECK-NEXT:    [[INC]] = add nsw i32 [[I_0]], 1
+; CHECK-NEXT:    br label [[FOR_COND]]
+; CHECK:       for.end:
+; CHECK-NEXT:    ret void
+;
+
+entry:
+  call void @nocapture_nocallback_func(ptr @obj0)
+  br label %for.cond
+
+for.cond:                                         ; preds = %for.inc, %entry
+  %i.0 = phi i32 [ 0, %entry ], [ %inc, %for.inc ]
+  %cmp = icmp slt i32 %i.0, 1000
+  br i1 %cmp, label %for.body, label %for.end
+
+for.body:                                         ; preds = %for.cond
+  %0 = load ptr, ptr @obj0, align 8
+  %idxprom = sext i32 %i.0 to i64
+  %arrayidx = getelementptr inbounds ptr, ptr %0, i64 %idxprom
+  store ptr %p, ptr %arrayidx, align 8
+  br label %for.inc
+
+for.inc:                                          ; preds = %for.body
+  %inc = add nsw i32 %i.0, 1
+  br label %for.cond
+
+for.end:                                          ; preds = %for.cond
+  ret void
+}
+
+define dso_local void @test1(ptr %p) {
+; Check that load from @obj1 is not hoisted from the loop,
+; because 'nocallback' is missing:
+; CHECK-LABEL: @test1(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    call void @nocapture_func(ptr @obj1)
+; CHECK-NEXT:    br label [[FOR_COND:%.*]]
+; CHECK:       for.cond:
+; CHECK-NEXT:    [[I_0:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[INC:%.*]], [[FOR_INC:%.*]] ]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i32 [[I_0]], 1000
+; CHECK-NEXT:    br i1 [[CMP]], label [[FOR_BODY:%.*]], label [[FOR_END:%.*]]
+; CHECK:       for.body:
+; CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr @obj1, align 8
+; CHECK-NEXT:    [[IDXPROM:%.*]] = sext i32 [[I_0]] to i64
+; CHECK-NEXT:    [[ARRAYIDX:%.*]] = getelementptr inbounds ptr, ptr [[TMP0]], i64 [[IDXPROM]]
+; CHECK-NEXT:    store ptr [[P:%.*]], ptr [[ARRAYIDX]], align 8
+; CHECK-NEXT:    br label [[FOR_INC]]
+; CHECK:       for.inc:
+; CHECK-NEXT:    [[INC]] = add nsw i32 [[I_0]], 1
+; CHECK-NEXT:    br label [[FOR_COND]]
+; CHECK:       for.end:
+; CHECK-NEXT:    ret void
+;
+
+entry:
+  call void @nocapture_func(ptr @obj1)
+  br label %for.cond
+
+for.cond:                                         ; preds = %for.inc, %entry
+  %i.0 = phi i32 [ 0, %entry ], [ %inc, %for.inc ]
+  %cmp = icmp slt i32 %i.0, 1000
+  br i1 %cmp, label %for.body, label %for.end
+
+for.body:                                         ; preds = %for.cond
+  %0 = load ptr, ptr @obj1, align 8
+  %idxprom = sext i32 %i.0 to i64
+  %arrayidx = getelementptr inbounds ptr, ptr %0, i64 %idxprom
+  store ptr %p, ptr %arrayidx, align 8
+  br label %for.inc
+
+for.inc:                                          ; preds = %for.body
+  %inc = add nsw i32 %i.0, 1
+  br label %for.cond
+
+for.end:                                          ; preds = %for.cond
+  ret void
+}
+
+define dso_local void @test2(ptr %p) {
+; Check that load from @obj2 is not hoisted from the loop,
+; because 'nocapture' is missing:
+; CHECK-LABEL: @test2(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    call void @nocallback_func(ptr @obj2)
+; CHECK-NEXT:    br label [[FOR_COND:%.*]]
+; CHECK:       for.cond:
+; CHECK-NEXT:    [[I_0:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[INC:%.*]], [[FOR_INC:%.*]] ]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i32 [[I_0]], 1000
+; CHECK-NEXT:    br i1 [[CMP]], label [[FOR_BODY:%.*]], label [[FOR_END:%.*]]
+; CHECK:       for.body:
+; CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr @obj2, align 8
+; CHECK-NEXT:    [[IDXPROM:%.*]] = sext i32 [[I_0]] to i64
+; CHECK-NEXT:    [[ARRAYIDX:%.*]] = getelementptr inbounds ptr, ptr [[TMP0]], i64 [[IDXPROM]]
+; CHECK-NEXT:    store ptr [[P:%.*]], ptr [[ARRAYIDX]], align 8
+; CHECK-NEXT:    br label [[FOR_INC]]
+; CHECK:       for.inc:
+; CHECK-NEXT:    [[INC]] = add nsw i32 [[I_0]], 1
+; CHECK-NEXT:    br label [[FOR_COND]]
+; CHECK:       for.end:
+; CHECK-NEXT:    ret void
+;
+
+entry:
+  call void @nocallback_func(ptr @obj2)
+  br label %for.cond
+
+for.cond:                                         ; preds = %for.inc, %entry
+  %i.0 = phi i32 [ 0, %entry ], [ %inc, %for.inc ]
+  %cmp = icmp slt i32 %i.0, 1000
+  br i1 %cmp, label %for.body, label %for.end
+
+for.body:                                         ; preds = %for.cond
+  %0 = load ptr, ptr @obj2, align 8
+  %idxprom = sext i32 %i.0 to i64
+  %arrayidx = getelementptr inbounds ptr, ptr %0, i64 %idxprom
+  store ptr %p, ptr %arrayidx, align 8
+  br label %for.inc
+
+for.inc:                                          ; preds = %for.body
+  %inc = add nsw i32 %i.0, 1
+  br label %for.cond
+
+for.end:                                          ; preds = %for.cond
+  ret void
+}
+
+define dso_local void @test3(ptr %p) {
+; Check that load from @obj3 is hoisted from the loop, even though
+; there is unknown call in the loop.
+; CHECK-LABEL: @test3(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    call void @nocapture_nocallback_func(ptr @obj3)
+; CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr @obj3, align 8
+; CHECK-NEXT:    br label [[FOR_COND:%.*]]
+; CHECK:       for.cond:
+; CHECK-NEXT:    [[I_0:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[INC:%.*]], [[FOR_INC:%.*]] ]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i32 [[I_0]], 1000
+; CHECK-NEXT:    br i1 [[CMP]], label [[FOR_BODY:%.*]], label [[FOR_END:%.*]]
+; CHECK:       for.body:
+; CHECK-NEXT:    call void @unknown_call()
+; CHECK-NEXT:    [[IDXPROM:%.*]] = sext i32 [[I_0]] to i64
+; CHECK-NEXT:    [[ARRAYIDX:%.*]] = getelementptr inbounds ptr, ptr [[TMP0]], i64 [[IDXPROM]]
+; CHECK-NEXT:    store ptr [[P:%.*]], ptr [[ARRAYIDX]], align 8
+; CHECK-NEXT:    br label [[FOR_INC]]
+; CHECK:       for.inc:
+; CHECK-NEXT:    [[INC]] = add nsw i32 [[I_0]], 1
+; CHECK-NEXT:    br label [[FOR_COND]]
+; CHECK:       for.end:
+; CHECK-NEXT:    ret void
+;
+
+entry:
+  call void @nocapture_nocallback_func(ptr @obj3)
+  br label %for.cond
+
+for.cond:                                         ; preds = %for.inc, %entry
+  %i.0 = phi i32 [ 0, %entry ], [ %inc, %for.inc ]
+  %cmp = icmp slt i32 %i.0, 1000
+  br i1 %cmp, label %for.body, label %for.end
+
+for.body:                                         ; preds = %for.cond
+  %0 = load ptr, ptr @obj3, align 8
+  call void @unknown_call()
+  %idxprom = sext i32 %i.0 to i64
+  %arrayidx = getelementptr inbounds ptr, ptr %0, i64 %idxprom
+  store ptr %p, ptr %arrayidx, align 8
+  br label %for.inc
+
+for.inc:                                          ; preds = %for.body
+  %inc = add nsw i32 %i.0, 1
+  br label %for.cond
+
+for.end:                                          ; preds = %for.cond
+  ret void
+}
+
+define dso_local void @test4(ptr %p) {
+; Check that load from @obj4 is not hoisted from the loop,
+; because 'nocallback' is missing:
+; CHECK-LABEL: @test4(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    call void @nocapture_func(ptr @obj4)
+; CHECK-NEXT:    br label [[FOR_COND:%.*]]
+; CHECK:       for.cond:
+; CHECK-NEXT:    [[I_0:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[INC:%.*]], [[FOR_INC:%.*]] ]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i32 [[I_0]], 1000
+; CHECK-NEXT:    br i1 [[CMP]], label [[FOR_BODY:%.*]], label [[FOR_END:%.*]]
+; CHECK:       for.body:
+; CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr @obj4, align 8
+; CHECK-NEXT:    call void @unknown_call()
+; CHECK-NEXT:    [[IDXPROM:%.*]] = sext i32 [[I_0]] to i64
+; CHECK-NEXT:    [[ARRAYIDX:%.*]] = getelementptr inbounds ptr, ptr [[TMP0]], i64 [[IDXPROM]]
+; CHECK-NEXT:    store ptr [[P:%.*]], ptr [[ARRAYIDX]], align 8
+; CHECK-NEXT:    br label [[FOR_INC]]
+; CHECK:       for.inc:
+; CHECK-NEXT:    [[INC]] = add nsw i32 [[I_0]], 1
+; CHECK-NEXT:    br label [[FOR_COND]]
+; CHECK:       for.end:
+; CHECK-NEXT:    ret void
+;
+
+entry:
+  call void @nocapture_func(ptr @obj4)
+  br label %for.cond
+
+for.cond:                                         ; preds = %for.inc, %entry
+  %i.0 = phi i32 [ 0, %entry ], [ %inc, %for.inc ]
+  %cmp = icmp slt i32 %i.0, 1000
+  br i1 %cmp, label %for.body, label %for.end
+
+for.body:                                         ; preds = %for.cond
+  %0 = load ptr, ptr @obj4, align 8
+  call void @unknown_call()
+  %idxprom = sext i32 %i.0 to i64
+  %arrayidx = getelementptr inbounds ptr, ptr %0, i64 %idxprom
+  store ptr %p, ptr %arrayidx, align 8
+  br label %for.inc
+
+for.inc:                                          ; preds = %for.body
+  %inc = add nsw i32 %i.0, 1
+  br label %for.cond
+
+for.end:                                          ; preds = %for.cond
+  ret void
+}
+
+define dso_local void @test5(ptr %p) {
+; Check that load from @obj5 is not hoisted from the loop,
+; because 'nocapture' is missing:
+; CHECK-LABEL: @test5(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    call void @nocallback_func(ptr @obj5)
+; CHECK-NEXT:    br label [[FOR_COND:%.*]]
+; CHECK:       for.cond:
+; CHECK-NEXT:    [[I_0:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[INC:%.*]], [[FOR_INC:%.*]] ]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i32 [[I_0]], 1000
+; CHECK-NEXT:    br i1 [[CMP]], label [[FOR_BODY:%.*]], label [[FOR_END:%.*]]
+; CHECK:       for.body:
+; CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr @obj5, align 8
+; CHECK-NEXT:    call void @unknown_call()
+; CHECK-NEXT:    [[IDXPROM:%.*]] = sext i32 [[I_0]] to i64
+; CHECK-NEXT:    [[ARRAYIDX:%.*]] = getelementptr inbounds ptr, ptr [[TMP0]], i64 [[IDXPROM]]
+; CHECK-NEXT:    store ptr [[P:%.*]], ptr [[ARRAYIDX]], align 8
+; CHECK-NEXT:    br label [[FOR_INC]]
+; CHECK:       for.inc:
+; CHECK-NEXT:    [[INC]] = add nsw i32 [[I_0]], 1
+; CHECK-NEXT:    br label [[FOR_COND]]
+; CHECK:       for.end:
+; CHECK-NEXT:    ret void
+;
+
+entry:
+  call void @nocallback_func(ptr @obj5)
+  br label %for.cond
+
+for.cond:                                         ; preds = %for.inc, %entry
+  %i.0 = phi i32 [ 0, %entry ], [ %inc, %for.inc ]
+  %cmp = icmp slt i32 %i.0, 1000
+  br i1 %cmp, label %for.body, label %for.end
+
+for.body:                                         ; preds = %for.cond
+  %0 = load ptr, ptr @obj5, align 8
+  call void @unknown_call()
+  %idxprom = sext i32 %i.0 to i64
+  %arrayidx = getelementptr inbounds ptr, ptr %0, i64 %idxprom
+  store ptr %p, ptr %arrayidx, align 8
+  br label %for.inc
+
+for.inc:                                          ; preds = %for.body
+  %inc = add nsw i32 %i.0, 1
+  br label %for.cond
+
+for.end:                                          ; preds = %for.cond
+  ret void
+}
+
+declare void @nocapture_nocallback_func(ptr nocapture) nocallback
+declare void @nocapture_func(ptr nocapture)
+declare void @nocallback_func(ptr) nocallback
+; nosync and nocallback are required, otherwise the call
+; will by ModRef for any global:
+declare void @unknown_call() nosync nocallback


        


More information about the llvm-commits mailing list