Skip to content

Commit

Permalink
[AA] A global cannot escape through nocapture/nocallback call.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
vzakhari committed Nov 28, 2022
1 parent 1a5be52 commit 5bd8175
Show file tree
Hide file tree
Showing 2 changed files with 353 additions and 1 deletion.
26 changes: 25 additions & 1 deletion llvm/lib/Analysis/GlobalsModRef.cpp
Expand Up @@ -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)) {
Expand Down
328 changes: 328 additions & 0 deletions 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 }

@obj0 = internal global %struct.str zeroinitializer, align 8
@obj1 = internal global %struct.str zeroinitializer, align 8
@obj2 = internal global %struct.str zeroinitializer, align 8
@obj3 = internal global %struct.str zeroinitializer, align 8
@obj4 = internal global %struct.str zeroinitializer, align 8
@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

0 comments on commit 5bd8175

Please sign in to comment.