Index: llvm/lib/Analysis/GlobalsModRef.cpp =================================================================== --- llvm/lib/Analysis/GlobalsModRef.cpp +++ llvm/lib/Analysis/GlobalsModRef.cpp @@ -354,7 +354,24 @@ 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(); + if (!F) + return true; + if (!F->isDeclaration() || !F->hasFnAttribute(Attribute::NoCallback)) + return true; + if (!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)) { Index: llvm/test/Analysis/GlobalsModRef/noescape-nocapture-nocallback.ll =================================================================== --- /dev/null +++ llvm/test/Analysis/GlobalsModRef/noescape-nocapture-nocallback.ll @@ -0,0 +1,152 @@ +; RUN: opt < %s -aa-pipeline=basic-aa,globals-aa -S -passes='require,function(loop-mssa(licm))' | FileCheck %s + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +%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 + +define dso_local void @test0() { +; 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: call void @nocapture_nocallback_func(ptr @obj0) +; CHECK: load ptr, ptr @obj0 +; CHECK: br label %[[exit_latch:.*]] +; CHECK: [[exit_latch]]: +; CHECK: br i1 %{{.*}}, label %[[body:.*]], label %[[exit:.*]] +; CHECK: [[body]]: +; CHECK-NOT: load ptr, ptr @obj0 +; CHECK: br label %[[inc_latch:.*]] +; CHECK: [[inc_latch]]: +; CHECK: br label %[[exit_latch]] +; CHECK: [[exit]]: +; CHECK: ret void + +entry: + %i = alloca i32, align 4 + call void @nocapture_nocallback_func(ptr @obj0) + store i32 0, ptr %i, align 4 + br label %for.cond + +for.cond: ; preds = %for.inc, %entry + %0 = load i32, ptr %i, align 4 + %cmp = icmp slt i32 %0, 1000 + br i1 %cmp, label %for.body, label %for.end + +for.body: ; preds = %for.cond + %1 = load ptr, ptr @obj0, align 8 + %2 = load i32, ptr %i, align 4 + %idxprom = sext i32 %2 to i64 + %arrayidx = getelementptr inbounds i32, ptr %1, i64 %idxprom + store i32 -1, ptr %arrayidx, align 4 + br label %for.inc + +for.inc: ; preds = %for.body + %3 = load i32, ptr %i, align 4 + %inc = add nsw i32 %3, 1 + store i32 %inc, ptr %i, align 4 + br label %for.cond + +for.end: ; preds = %for.cond + ret void +} + +define dso_local void @test1() { +; Check that load from @obj1 is not hoisted from the loop, +; because 'nocallback' is missing: +; CHECK-LABEL: @test1 +; CHECK: call void @nocapture_func(ptr @obj1) +; CHECK: br label %[[exit_latch:.*]] +; CHECK: [[exit_latch]]: +; CHECK: br i1 %cmp, label %[[body:.*]], label %[[exit:.*]] +; CHECK: [[body]]: +; CHECK: load ptr, ptr @obj1 +; CHECK: br label %[[inc_latch:.*]] +; CHECK: [[inc_latch]]: +; CHECK: br label %[[exit_latch]] +; CHECK: [[exit]]: +; CHECK: ret void + +entry: + %i = alloca i32, align 4 + call void @nocapture_func(ptr @obj1) + store i32 0, ptr %i, align 4 + br label %for.cond + +for.cond: ; preds = %for.inc, %entry + %0 = load i32, ptr %i, align 4 + %cmp = icmp slt i32 %0, 1000 + br i1 %cmp, label %for.body, label %for.end + +for.body: ; preds = %for.cond + %1 = load ptr, ptr @obj1, align 8 + %2 = load i32, ptr %i, align 4 + %idxprom = sext i32 %2 to i64 + %arrayidx = getelementptr inbounds i32, ptr %1, i64 %idxprom + store i32 -1, ptr %arrayidx, align 4 + br label %for.inc + +for.inc: ; preds = %for.body + %3 = load i32, ptr %i, align 4 + %inc = add nsw i32 %3, 1 + store i32 %inc, ptr %i, align 4 + br label %for.cond + +for.end: ; preds = %for.cond + ret void +} + +define dso_local void @test2() { +; Check that load from @obj2 is not hoisted from the loop, +; because 'nocapture' is missing: +; CHECK-LABEL: @test2 +; CHECK: call void @nocallback_func(ptr @obj2) +; CHECK: br label %[[exit_latch:.*]] +; CHECK: [[exit_latch]]: +; CHECK: br i1 %cmp, label %[[body:.*]], label %[[exit:.*]] +; CHECK: [[body]]: +; CHECK: load ptr, ptr @obj2 +; CHECK: br label %[[inc_latch:.*]] +; CHECK: [[inc_latch]]: +; CHECK: br label %[[exit_latch]] +; CHECK: [[exit]]: +; CHECK: ret void + +entry: + %i = alloca i32, align 4 + call void @nocallback_func(ptr @obj2) + store i32 0, ptr %i, align 4 + br label %for.cond + +for.cond: ; preds = %for.inc, %entry + %0 = load i32, ptr %i, align 4 + %cmp = icmp slt i32 %0, 1000 + br i1 %cmp, label %for.body, label %for.end + +for.body: ; preds = %for.cond + %1 = load ptr, ptr @obj2, align 8 + %2 = load i32, ptr %i, align 4 + %idxprom = sext i32 %2 to i64 + %arrayidx = getelementptr inbounds i32, ptr %1, i64 %idxprom + store i32 -1, ptr %arrayidx, align 4 + br label %for.inc + +for.inc: ; preds = %for.body + %3 = load i32, ptr %i, align 4 + %inc = add nsw i32 %3, 1 + store i32 %inc, ptr %i, align 4 + br label %for.cond + +for.end: ; preds = %for.cond + ret void +} + +declare void @nocapture_nocallback_func(ptr nocapture) #0 +declare void @nocapture_func(ptr nocapture) +declare void @nocallback_func(ptr) #0 + +attributes #0 = { nocallback }