diff --git a/llvm/lib/Transforms/IPO/GlobalOpt.cpp b/llvm/lib/Transforms/IPO/GlobalOpt.cpp --- a/llvm/lib/Transforms/IPO/GlobalOpt.cpp +++ b/llvm/lib/Transforms/IPO/GlobalOpt.cpp @@ -1990,12 +1990,13 @@ return true; } + bool Changed = false; + // If the global is never loaded (but may be stored to), it is dead. // Delete it now. if (!GS.IsLoaded) { LLVM_DEBUG(dbgs() << "GLOBAL NEVER LOADED: " << *GV << "\n"); - bool Changed; if (isLeakCheckerRoot(GV)) { // Delete any constant stores to the global. Changed = CleanupPointerRootUsers(GV, GetTLI); @@ -2021,11 +2022,14 @@ // Don't actually mark a global constant if it's atomic because atomic loads // are implemented by a trivial cmpxchg in some edge-cases and that usually // requires write access to the variable even if it's not actually changed. - if (GS.Ordering == AtomicOrdering::NotAtomic) + if (GS.Ordering == AtomicOrdering::NotAtomic) { + assert(!GV->isConstant() && "Expected a non-constant global"); GV->setConstant(true); + Changed = true; + } // Clean up any obviously simplifiable users now. - CleanupConstantGlobalUsers(GV, GV->getInitializer(), DL, GetTLI); + Changed |= CleanupConstantGlobalUsers(GV, GV->getInitializer(), DL, GetTLI); // If the global is dead now, just nuke it. if (GV->use_empty()) { @@ -2085,7 +2089,7 @@ } } - return false; + return Changed; } /// Analyze the specified global variable and optimize it if possible. If we diff --git a/llvm/test/Transforms/GlobalOpt/const-return-status-atomic.ll b/llvm/test/Transforms/GlobalOpt/const-return-status-atomic.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/GlobalOpt/const-return-status-atomic.ll @@ -0,0 +1,27 @@ +; RUN: opt -globalopt < %s -S -o - | FileCheck %s + +; When simplifying users of a global variable, the pass could incorrectly +; return false if there were still some uses left, and no further optimizations +; was done. This was caught by the pass return status check that is hidden +; under EXPENSIVE_CHECKS. + +@GV1 = internal unnamed_addr global i64 1, align 8 + +; CHECK: @GV1 = internal unnamed_addr global i64 1, align 8 + +define void @test1() local_unnamed_addr { +; CHECK-LABEL: @test1 +; CHECK-NEXT: %val = load atomic i8 +; CHECK-NEXT: ret void + + %val = load atomic i8, i8* bitcast (i64* @GV1 to i8*) acquire, align 8 + ret void +} + +define i64 @test2() local_unnamed_addr { +; CHECK-LABEL: @test2 +; CHECK-NEXT: ret i64 1 + + %val = load atomic i64, i64* @GV1 acquire, align 8 + ret i64 %val +} diff --git a/llvm/test/Transforms/GlobalOpt/const-return-status.ll b/llvm/test/Transforms/GlobalOpt/const-return-status.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/GlobalOpt/const-return-status.ll @@ -0,0 +1,28 @@ +; RUN: opt -globalopt < %s -S -o - | FileCheck %s + +; When simplifying users of a global variable, the pass could incorrectly +; return false if there were still some uses left, and no further optimizations +; was done. This was caught by the pass return status check that is hidden +; under EXPENSIVE_CHECKS. + +; CHECK: @src = internal unnamed_addr constant + +; CHECK: entry: +; CHECK-NEXT: %call = call i32 @f(i32 0) +; CHECK-NEXT: call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 4 bitcast (i32* @dst to i8*), i8* align 4 bitcast ([1 x i32]* @src to i8*), i64 1, i1 false) +; CHECK-NEXT: ret void + +@src = internal unnamed_addr global [1 x i32] zeroinitializer, align 4 +@dst = external dso_local local_unnamed_addr global i32, align 4 + +define dso_local void @d() local_unnamed_addr { +entry: + %0 = load i32, i32* getelementptr inbounds ([1 x i32], [1 x i32]* @src, i64 0, i64 0), align 4 + %call = call i32 @f(i32 %0) + call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 4 bitcast (i32* @dst to i8*), i8* align 4 bitcast ([1 x i32]* @src to i8*), i64 1, i1 false) + ret void +} + +declare dso_local i32 @f(i32) local_unnamed_addr + +declare void @llvm.memcpy.p0i8.p0i8.i64(i8* noalias nocapture writeonly, i8* noalias nocapture readonly, i64, i1 immarg)