diff --git a/llvm/lib/Analysis/GlobalsModRef.cpp b/llvm/lib/Analysis/GlobalsModRef.cpp --- a/llvm/lib/Analysis/GlobalsModRef.cpp +++ b/llvm/lib/Analysis/GlobalsModRef.cpp @@ -354,7 +354,31 @@ 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(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 --- /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,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