Files
clang-p2996/llvm/test/Transforms/NewGVN/invariant.group.ll
Arthur Eubanks b493124ae2 [MemorySSA] Support invariant.group metadata
The implementation is mostly copied from MemDepAnalysis. We want to look
at all loads and stores to the same pointer operand. Bitcasts and zero
GEPs of a pointer are considered the same pointer value. We choose the
most dominating instruction.

Since updating MemorySSA with invariant.group is non-trivial, for now
handling of invariant.group is not cached in any way, so it's part of
the walker. The number of loads/stores with invariant.group is small for
now anyway. We can revisit if this actually noticeably affects compile
times.

To avoid invariant.group affecting optimized uses, we need to have
optimizeUsesInBlock() not use invariant.group in any way.

Co-authored-by: Piotr Padlewski <prazek@google.com>

Reviewed By: asbirlea, nikic, Prazek

Differential Revision: https://reviews.llvm.org/D109134
2021-09-08 13:06:12 -07:00

451 lines
14 KiB
LLVM

; RUN: opt < %s -passes=newgvn -S | FileCheck %s
%struct.A = type { i32 (...)** }
@_ZTV1A = available_externally unnamed_addr constant [3 x i8*] [i8* null, i8* bitcast (i8** @_ZTI1A to i8*), i8* bitcast (void (%struct.A*)* @_ZN1A3fooEv to i8*)], align 8
@_ZTI1A = external constant i8*
@unknownPtr = external global i8
; CHECK-LABEL: define i8 @simple() {
define i8 @simple() {
entry:
%ptr = alloca i8
store i8 42, i8* %ptr, !invariant.group !0
call void @foo(i8* %ptr)
%a = load i8, i8* %ptr, !invariant.group !0
%b = load i8, i8* %ptr, !invariant.group !0
%c = load i8, i8* %ptr, !invariant.group !0
; CHECK: ret i8 42
ret i8 %a
}
; CHECK-LABEL: define i8 @optimizable1() {
define i8 @optimizable1() {
entry:
%ptr = alloca i8
store i8 42, i8* %ptr, !invariant.group !0
%ptr2 = call i8* @llvm.launder.invariant.group.p0i8(i8* %ptr)
%a = load i8, i8* %ptr, !invariant.group !0
call void @foo(i8* %ptr2); call to use %ptr2
; CHECK: ret i8 42
ret i8 %a
}
; CHECK-LABEL: define i8 @optimizable2() {
define i8 @optimizable2() {
entry:
%ptr = alloca i8
store i8 42, i8* %ptr, !invariant.group !0
call void @foo(i8* %ptr)
store i8 13, i8* %ptr ; can't use this store with invariant.group
%a = load i8, i8* %ptr
call void @bar(i8 %a) ; call to use %a
call void @foo(i8* %ptr)
%b = load i8, i8* %ptr, !invariant.group !0
; CHECK: ret i8 42
ret i8 %b
}
; CHECK-LABEL: define i1 @proveEqualityForStrip(
define i1 @proveEqualityForStrip(i8* %a) {
%b1 = call i8* @llvm.strip.invariant.group.p0i8(i8* %a)
; CHECK-NOT: llvm.strip.invariant.group
%b2 = call i8* @llvm.strip.invariant.group.p0i8(i8* %a)
%r = icmp eq i8* %b1, %b2
; CHECK: ret i1 true
ret i1 %r
}
; CHECK-LABEL: define i8 @unoptimizable1() {
define i8 @unoptimizable1() {
entry:
%ptr = alloca i8
store i8 42, i8* %ptr
call void @foo(i8* %ptr)
%a = load i8, i8* %ptr, !invariant.group !0
; CHECK: ret i8 %a
ret i8 %a
}
; NewGVN doesn't support assumes.
; CHECK-LABEL: define void @indirectLoads() {
define void @indirectLoads() {
entry:
%a = alloca %struct.A*, align 8
%0 = bitcast %struct.A** %a to i8*
%call = call i8* @getPointer(i8* null)
%1 = bitcast i8* %call to %struct.A*
call void @_ZN1AC1Ev(%struct.A* %1)
%2 = bitcast %struct.A* %1 to i8***
; CHECK: %vtable = load {{.*}} !invariant.group
%vtable = load i8**, i8*** %2, align 8, !invariant.group !0
%cmp.vtables = icmp eq i8** %vtable, getelementptr inbounds ([3 x i8*], [3 x i8*]* @_ZTV1A, i64 0, i64 2)
call void @llvm.assume(i1 %cmp.vtables)
store %struct.A* %1, %struct.A** %a, align 8
%3 = load %struct.A*, %struct.A** %a, align 8
%4 = bitcast %struct.A* %3 to void (%struct.A*)***
; FIXME: call void @_ZN1A3fooEv(
%vtable1 = load void (%struct.A*)**, void (%struct.A*)*** %4, align 8, !invariant.group !0
%vfn = getelementptr inbounds void (%struct.A*)*, void (%struct.A*)** %vtable1, i64 0
%5 = load void (%struct.A*)*, void (%struct.A*)** %vfn, align 8
call void %5(%struct.A* %3)
%6 = load %struct.A*, %struct.A** %a, align 8
%7 = bitcast %struct.A* %6 to void (%struct.A*)***
; FIXME: call void @_ZN1A3fooEv(
%vtable2 = load void (%struct.A*)**, void (%struct.A*)*** %7, align 8, !invariant.group !0
%vfn3 = getelementptr inbounds void (%struct.A*)*, void (%struct.A*)** %vtable2, i64 0
%8 = load void (%struct.A*)*, void (%struct.A*)** %vfn3, align 8
call void %8(%struct.A* %6)
%9 = load %struct.A*, %struct.A** %a, align 8
%10 = bitcast %struct.A* %9 to void (%struct.A*)***
%vtable4 = load void (%struct.A*)**, void (%struct.A*)*** %10, align 8, !invariant.group !0
%vfn5 = getelementptr inbounds void (%struct.A*)*, void (%struct.A*)** %vtable4, i64 0
%11 = load void (%struct.A*)*, void (%struct.A*)** %vfn5, align 8
; FIXME: call void @_ZN1A3fooEv(
call void %11(%struct.A* %9)
%vtable5 = load i8**, i8*** %2, align 8, !invariant.group !0
%vfn6 = getelementptr inbounds i8*, i8** %vtable5, i64 0
%12 = bitcast i8** %vfn6 to void (%struct.A*)**
%13 = load void (%struct.A*)*, void (%struct.A*)** %12, align 8
; FIXME: call void @_ZN1A3fooEv(
call void %13(%struct.A* %9)
ret void
}
; NewGVN won't CSE loads with different pointee types.
; CHECK-LABEL: define void @combiningBitCastWithLoad() {
define void @combiningBitCastWithLoad() {
entry:
%a = alloca %struct.A*, align 8
%0 = bitcast %struct.A** %a to i8*
%call = call i8* @getPointer(i8* null)
%1 = bitcast i8* %call to %struct.A*
call void @_ZN1AC1Ev(%struct.A* %1)
%2 = bitcast %struct.A* %1 to i8***
; CHECK: %vtable = load {{.*}} !invariant.group
%vtable = load i8**, i8*** %2, align 8, !invariant.group !0
%cmp.vtables = icmp eq i8** %vtable, getelementptr inbounds ([3 x i8*], [3 x i8*]* @_ZTV1A, i64 0, i64 2)
store %struct.A* %1, %struct.A** %a, align 8
; FIXME-NOT: !invariant.group
%3 = load %struct.A*, %struct.A** %a, align 8
%4 = bitcast %struct.A* %3 to void (%struct.A*)***
%vtable1 = load void (%struct.A*)**, void (%struct.A*)*** %4, align 8, !invariant.group !0
%vfn = getelementptr inbounds void (%struct.A*)*, void (%struct.A*)** %vtable1, i64 0
%5 = load void (%struct.A*)*, void (%struct.A*)** %vfn, align 8
call void %5(%struct.A* %3)
ret void
}
; CHECK-LABEL:define void @loadCombine() {
define void @loadCombine() {
enter:
%ptr = alloca i8
store i8 42, i8* %ptr
call void @foo(i8* %ptr)
; CHECK: %[[A:.*]] = load i8, i8* %ptr, align 1, !invariant.group
%a = load i8, i8* %ptr, !invariant.group !0
; CHECK-NOT: load
%b = load i8, i8* %ptr, !invariant.group !0
; CHECK: call void @bar(i8 %[[A]])
call void @bar(i8 %a)
; CHECK: call void @bar(i8 %[[A]])
call void @bar(i8 %b)
ret void
}
; CHECK-LABEL: define void @loadCombine1() {
define void @loadCombine1() {
enter:
%ptr = alloca i8
store i8 42, i8* %ptr
call void @foo(i8* %ptr)
; CHECK: %[[D:.*]] = load i8, i8* %ptr, align 1, !invariant.group
%c = load i8, i8* %ptr
; CHECK-NOT: load
%d = load i8, i8* %ptr, !invariant.group !0
; CHECK: call void @bar(i8 %[[D]])
call void @bar(i8 %c)
; CHECK: call void @bar(i8 %[[D]])
call void @bar(i8 %d)
ret void
}
; CHECK-LABEL: define void @loadCombine2() {
define void @loadCombine2() {
enter:
%ptr = alloca i8
store i8 42, i8* %ptr
call void @foo(i8* %ptr)
; CHECK: %[[E:.*]] = load i8, i8* %ptr, align 1, !invariant.group
%e = load i8, i8* %ptr, !invariant.group !0
; CHECK-NOT: load
%f = load i8, i8* %ptr
; CHECK: call void @bar(i8 %[[E]])
call void @bar(i8 %e)
; CHECK: call void @bar(i8 %[[E]])
call void @bar(i8 %f)
ret void
}
; CHECK-LABEL: define void @loadCombine3() {
define void @loadCombine3() {
enter:
%ptr = alloca i8
store i8 42, i8* %ptr
call void @foo(i8* %ptr)
; CHECK: %[[E:.*]] = load i8, i8* %ptr, align 1, !invariant.group
%e = load i8, i8* %ptr, !invariant.group !0
; CHECK-NOT: load
%f = load i8, i8* %ptr, !invariant.group !0
; CHECK: call void @bar(i8 %[[E]])
call void @bar(i8 %e)
; CHECK: call void @bar(i8 %[[E]])
call void @bar(i8 %f)
ret void
}
; CHECK-LABEL: define i8 @unoptimizable2() {
define i8 @unoptimizable2() {
entry:
%ptr = alloca i8
store i8 42, i8* %ptr
call void @foo(i8* %ptr)
%a = load i8, i8* %ptr
call void @foo(i8* %ptr)
%b = load i8, i8* %ptr, !invariant.group !0
; CHECK: ret i8 %a
ret i8 %a
}
; CHECK-LABEL: define i8 @unoptimizable3() {
define i8 @unoptimizable3() {
entry:
%ptr = alloca i8
store i8 42, i8* %ptr, !invariant.group !0
%ptr2 = call i8* @getPointer(i8* %ptr)
%a = load i8, i8* %ptr2, !invariant.group !0
; CHECK: ret i8 %a
ret i8 %a
}
; NewGVN cares about the launder for some reason.
; CHECK-LABEL: define i8 @optimizable4() {
define i8 @optimizable4() {
entry:
%ptr = alloca i8
store i8 42, i8* %ptr
%ptr2 = call i8* @llvm.launder.invariant.group.p0i8(i8* %ptr)
; FIXME-NOT: load
%a = load i8, i8* %ptr2
; FIXME: ret i8 42
ret i8 %a
}
; CHECK-LABEL: define i8 @volatile1() {
define i8 @volatile1() {
entry:
%ptr = alloca i8
store i8 42, i8* %ptr, !invariant.group !0
call void @foo(i8* %ptr)
%a = load i8, i8* %ptr, !invariant.group !0
%b = load volatile i8, i8* %ptr
; CHECK: call void @bar(i8 %b)
call void @bar(i8 %b)
%c = load volatile i8, i8* %ptr, !invariant.group !0
; We might be able to optimize this, but nobody cares
; CHECK: call void @bar(i8 %c)
call void @bar(i8 %c)
; CHECK: ret i8 42
ret i8 %a
}
; CHECK-LABEL: define i8 @volatile2() {
define i8 @volatile2() {
entry:
%ptr = alloca i8
store i8 42, i8* %ptr, !invariant.group !0
call void @foo(i8* %ptr)
%a = load i8, i8* %ptr, !invariant.group !0
%b = load volatile i8, i8* %ptr
; CHECK: call void @bar(i8 %b)
call void @bar(i8 %b)
%c = load volatile i8, i8* %ptr, !invariant.group !0
; We might be able to optimize this, but nobody cares
; CHECK: call void @bar(i8 %c)
call void @bar(i8 %c)
; CHECK: ret i8 42
ret i8 %a
}
; CHECK-LABEL: define void @fun() {
define void @fun() {
entry:
%ptr = alloca i8
store i8 42, i8* %ptr, !invariant.group !0
call void @foo(i8* %ptr)
%a = load i8, i8* %ptr, !invariant.group !0 ; Can assume that value under %ptr didn't change
; CHECK: call void @bar(i8 42)
call void @bar(i8 %a)
ret void
}
; FIXME: NewGVN doesn't run instsimplify on a load from a vtable definition?
; This test checks if invariant.group understands gep with zeros
; CHECK-LABEL: define void @testGEP0() {
define void @testGEP0() {
%a = alloca %struct.A, align 8
%1 = bitcast %struct.A* %a to i8*
%2 = getelementptr inbounds %struct.A, %struct.A* %a, i64 0, i32 0
store i32 (...)** bitcast (i8** getelementptr inbounds ([3 x i8*], [3 x i8*]* @_ZTV1A, i64 0, i64 2) to i32 (...)**), i32 (...)*** %2, align 8, !invariant.group !0
; CHECK: call void @_ZN1A3fooEv(%struct.A* nonnull dereferenceable(8) %a)
call void @_ZN1A3fooEv(%struct.A* nonnull dereferenceable(8) %a) ; This call may change vptr
%3 = load i8, i8* @unknownPtr, align 4
%4 = icmp eq i8 %3, 0
br i1 %4, label %_Z1gR1A.exit, label %5
; This should be devirtualized by invariant.group
%6 = bitcast %struct.A* %a to void (%struct.A*)***
%7 = load void (%struct.A*)**, void (%struct.A*)*** %6, align 8, !invariant.group !0
%8 = load void (%struct.A*)*, void (%struct.A*)** %7, align 8
; FIXME: call void @_ZN1A3fooEv(%struct.A* nonnull %a)
call void %8(%struct.A* nonnull %a)
br label %_Z1gR1A.exit
_Z1gR1A.exit: ; preds = %0, %5
ret void
}
; Check if no optimizations are performed with global pointers.
; FIXME: we could do the optimizations if we would check if dependency comes
; from the same function.
; CHECK-LABEL: define void @testGlobal() {
define void @testGlobal() {
; CHECK: %a = load i8, i8* @unknownPtr, align 1, !invariant.group !0
%a = load i8, i8* @unknownPtr, !invariant.group !0
call void @foo2(i8* @unknownPtr, i8 %a)
; CHECK: %1 = load i8, i8* @unknownPtr, align 1, !invariant.group !0
%1 = load i8, i8* @unknownPtr, !invariant.group !0
call void @bar(i8 %1)
%b0 = bitcast i8* @unknownPtr to i1*
call void @fooBit(i1* %b0, i1 1)
; Adding regex because of canonicalization of bitcasts
; CHECK: %2 = load i1, i1* {{.*}}, !invariant.group !0
%2 = load i1, i1* %b0, !invariant.group !0
call void @fooBit(i1* %b0, i1 %2)
; CHECK: %3 = load i1, i1* {{.*}}, !invariant.group !0
%3 = load i1, i1* %b0, !invariant.group !0
call void @fooBit(i1* %b0, i1 %3)
ret void
}
; Might be similar to above where NewGVN doesn't handle loads of different types from the same location.
; Not super important anyway.
; CHECK-LABEL: define void @testTrunc() {
define void @testTrunc() {
%a = alloca i8
call void @foo(i8* %a)
; CHECK: %b = load i8, i8* %a, align 1, !invariant.group !0
%b = load i8, i8* %a, !invariant.group !0
call void @foo2(i8* %a, i8 %b)
%1 = load i8, i8* %a, !invariant.group !0
; CHECK: call void @bar(i8 %b)
call void @bar(i8 %1)
%b0 = bitcast i8* %a to i1*
call void @fooBit(i1* %b0, i1 1)
; FIXME: %1 = trunc i8 %b to i1
%2 = load i1, i1* %b0, !invariant.group !0
; FIXME-NEXT: call void @fooBit(i1* %b0, i1 %1)
call void @fooBit(i1* %b0, i1 %2)
%3 = load i1, i1* %b0, !invariant.group !0
; FIXME-NEXT: call void @fooBit(i1* %b0, i1 %1)
call void @fooBit(i1* %b0, i1 %3)
ret void
}
; See comment in @testGEP0 on what NewGVN is lacking.
; CHECK-LABEL: define void @handling_loops()
define void @handling_loops() {
%a = alloca %struct.A, align 8
%1 = bitcast %struct.A* %a to i8*
%2 = getelementptr inbounds %struct.A, %struct.A* %a, i64 0, i32 0
store i32 (...)** bitcast (i8** getelementptr inbounds ([3 x i8*], [3 x i8*]* @_ZTV1A, i64 0, i64 2) to i32 (...)**), i32 (...)*** %2, align 8, !invariant.group !0
%3 = load i8, i8* @unknownPtr, align 4
%4 = icmp sgt i8 %3, 0
br i1 %4, label %.lr.ph.i, label %_Z2g2R1A.exit
.lr.ph.i: ; preds = %0
%5 = bitcast %struct.A* %a to void (%struct.A*)***
%6 = load i8, i8* @unknownPtr, align 4
%7 = icmp sgt i8 %6, 1
br i1 %7, label %._crit_edge.preheader, label %_Z2g2R1A.exit
._crit_edge.preheader: ; preds = %.lr.ph.i
br label %._crit_edge
._crit_edge: ; preds = %._crit_edge.preheader, %._crit_edge
%8 = phi i8 [ %10, %._crit_edge ], [ 1, %._crit_edge.preheader ]
%.pre = load void (%struct.A*)**, void (%struct.A*)*** %5, align 8, !invariant.group !0
%9 = load void (%struct.A*)*, void (%struct.A*)** %.pre, align 8
; FIXME: call void @_ZN1A3fooEv(%struct.A* nonnull %a)
call void %9(%struct.A* nonnull %a) #3
; FIXME-NOT: call void %
%10 = add nuw nsw i8 %8, 1
%11 = load i8, i8* @unknownPtr, align 4
%12 = icmp slt i8 %10, %11
br i1 %12, label %._crit_edge, label %_Z2g2R1A.exit.loopexit
_Z2g2R1A.exit.loopexit: ; preds = %._crit_edge
br label %_Z2g2R1A.exit
_Z2g2R1A.exit: ; preds = %_Z2g2R1A.exit.loopexit, %.lr.ph.i, %0
ret void
}
declare void @foo(i8*)
declare void @foo2(i8*, i8)
declare void @bar(i8)
declare i8* @getPointer(i8*)
declare void @_ZN1A3fooEv(%struct.A*)
declare void @_ZN1AC1Ev(%struct.A*)
declare void @fooBit(i1*, i1)
declare i8* @llvm.launder.invariant.group.p0i8(i8*)
declare i8* @llvm.strip.invariant.group.p0i8(i8*)
; Function Attrs: nounwind
declare void @llvm.assume(i1 %cmp.vtables) #0
attributes #0 = { nounwind }
!0 = !{}