Nikita Popov 6d88f6efd4 Reapply [LVI] Normalize pointer behavior
This got reverted because a dependency was reverted. It has since
been reapplied, so reapply this as well.

-----

Related to D69686. As noted there, LVI currently behaves differently
for integer and pointer values: For integers, the block value is always
valid inside the basic block, while for pointers it is only valid at
the end of the basic block. I believe the integer behavior is the
correct one, and CVP relies on it via its getConstantRange() uses.

The reason for the special pointer behavior is that LVI checks whether
a pointer is dereferenced in a given basic block and marks it as
non-null in that case. Of course, this information is valid only after
the dereferencing instruction, or in conservative approximation,
at the end of the block.

This patch changes the treatment of dereferencability: Instead of
including it inside the block value, we instead treat it as something
similar to an assume (it essentially is a non-nullness assume) and
incorporate this information in intersectAssumeOrGuardBlockValueConstantRange()
if the context instruction is the terminator of the basic block.
This happens either when determining an edge-value internally in LVI,
or when a terminator was explicitly passed to getValueAt(). The latter
case makes this more powerful than the previous implementation as
a side-effect, and this does actually seem benefitial in practice.

Of course, we do not want to recompute dereferencability on each
intersectAssume call, so we need a new cache for this. The
dereferencability analysis requires walking the entire basic block
and computing underlying objects of all memory operands. This was
previously done separately for each queried pointer value. In the
new implementation (both because this makes the caching simpler,
and because it is faster), I instead only walk the full BB once and
cache all the dereferenced pointers. So the traversal is now performed
only once per BB, instead of once per queried pointer value.

I think the overall model now makes more sense than before, and there
will be no more pitfalls due to differing integer/pointer behavior.

Differential Revision: https://reviews.llvm.org/D69914
2020-08-29 21:17:03 +02:00

348 lines
9.9 KiB
LLVM

; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
; RUN: opt < %s -correlated-propagation -S | FileCheck %s
define void @test1(i8* %ptr) {
; CHECK-LABEL: @test1(
; CHECK-NEXT: [[A:%.*]] = load i8, i8* [[PTR:%.*]], align 1
; CHECK-NEXT: br label [[BB:%.*]]
; CHECK: bb:
; CHECK-NEXT: ret void
;
%A = load i8, i8* %ptr
br label %bb
bb:
icmp ne i8* %ptr, null
ret void
}
define void @test1_no_null_opt(i8* %ptr) #0 {
; CHECK-LABEL: @test1_no_null_opt(
; CHECK-NEXT: [[A:%.*]] = load i8, i8* [[PTR:%.*]], align 1
; CHECK-NEXT: br label [[BB:%.*]]
; CHECK: bb:
; CHECK-NEXT: [[TMP1:%.*]] = icmp ne i8* [[PTR]], null
; CHECK-NEXT: ret void
;
%A = load i8, i8* %ptr
br label %bb
bb:
icmp ne i8* %ptr, null
ret void
}
define void @test2(i8* %ptr) {
; CHECK-LABEL: @test2(
; CHECK-NEXT: store i8 0, i8* [[PTR:%.*]], align 1
; CHECK-NEXT: br label [[BB:%.*]]
; CHECK: bb:
; CHECK-NEXT: ret void
;
store i8 0, i8* %ptr
br label %bb
bb:
icmp ne i8* %ptr, null
ret void
}
define void @test2_no_null_opt(i8* %ptr) #0 {
; CHECK-LABEL: @test2_no_null_opt(
; CHECK-NEXT: store i8 0, i8* [[PTR:%.*]], align 1
; CHECK-NEXT: br label [[BB:%.*]]
; CHECK: bb:
; CHECK-NEXT: [[TMP1:%.*]] = icmp ne i8* [[PTR]], null
; CHECK-NEXT: ret void
;
store i8 0, i8* %ptr
br label %bb
bb:
icmp ne i8* %ptr, null
ret void
}
define void @test3() {
; CHECK-LABEL: @test3(
; CHECK-NEXT: [[PTR:%.*]] = alloca i8, align 1
; CHECK-NEXT: br label [[BB:%.*]]
; CHECK: bb:
; CHECK-NEXT: ret void
;
%ptr = alloca i8
br label %bb
bb:
icmp ne i8* %ptr, null
ret void
}
;; OK to remove icmp here since ptr is coming from alloca.
define void @test3_no_null_opt() #0 {
; CHECK-LABEL: @test3_no_null_opt(
; CHECK-NEXT: [[PTR:%.*]] = alloca i8, align 1
; CHECK-NEXT: br label [[BB:%.*]]
; CHECK: bb:
; CHECK-NEXT: ret void
;
%ptr = alloca i8
br label %bb
bb:
icmp ne i8* %ptr, null
ret void
}
declare void @llvm.memcpy.p0i8.p0i8.i32(i8*, i8*, i32, i1)
define void @test4(i8* %dest, i8* %src) {
; CHECK-LABEL: @test4(
; CHECK-NEXT: call void @llvm.memcpy.p0i8.p0i8.i32(i8* [[DEST:%.*]], i8* [[SRC:%.*]], i32 1, i1 false)
; CHECK-NEXT: br label [[BB:%.*]]
; CHECK: bb:
; CHECK-NEXT: ret void
;
call void @llvm.memcpy.p0i8.p0i8.i32(i8* %dest, i8* %src, i32 1, i1 false)
br label %bb
bb:
icmp ne i8* %dest, null
icmp ne i8* %src, null
ret void
}
define void @test4_no_null_opt(i8* %dest, i8* %src) #0 {
; CHECK-LABEL: @test4_no_null_opt(
; CHECK-NEXT: call void @llvm.memcpy.p0i8.p0i8.i32(i8* [[DEST:%.*]], i8* [[SRC:%.*]], i32 1, i1 false)
; CHECK-NEXT: br label [[BB:%.*]]
; CHECK: bb:
; CHECK-NEXT: [[TMP1:%.*]] = icmp ne i8* [[DEST]], null
; CHECK-NEXT: [[TMP2:%.*]] = icmp ne i8* [[SRC]], null
; CHECK-NEXT: ret void
;
call void @llvm.memcpy.p0i8.p0i8.i32(i8* %dest, i8* %src, i32 1, i1 false)
br label %bb
bb:
icmp ne i8* %dest, null
icmp ne i8* %src, null
ret void
}
declare void @llvm.memmove.p0i8.p0i8.i32(i8*, i8*, i32, i1)
define void @test5(i8* %dest, i8* %src) {
; CHECK-LABEL: @test5(
; CHECK-NEXT: call void @llvm.memmove.p0i8.p0i8.i32(i8* [[DEST:%.*]], i8* [[SRC:%.*]], i32 1, i1 false)
; CHECK-NEXT: br label [[BB:%.*]]
; CHECK: bb:
; CHECK-NEXT: ret void
;
call void @llvm.memmove.p0i8.p0i8.i32(i8* %dest, i8* %src, i32 1, i1 false)
br label %bb
bb:
icmp ne i8* %dest, null
icmp ne i8* %src, null
ret void
}
define void @test5_no_null_opt(i8* %dest, i8* %src) #0 {
; CHECK-LABEL: @test5_no_null_opt(
; CHECK-NEXT: call void @llvm.memmove.p0i8.p0i8.i32(i8* [[DEST:%.*]], i8* [[SRC:%.*]], i32 1, i1 false)
; CHECK-NEXT: br label [[BB:%.*]]
; CHECK: bb:
; CHECK-NEXT: [[TMP1:%.*]] = icmp ne i8* [[DEST]], null
; CHECK-NEXT: [[TMP2:%.*]] = icmp ne i8* [[SRC]], null
; CHECK-NEXT: ret void
;
call void @llvm.memmove.p0i8.p0i8.i32(i8* %dest, i8* %src, i32 1, i1 false)
br label %bb
bb:
icmp ne i8* %dest, null
icmp ne i8* %src, null
ret void
}
declare void @llvm.memset.p0i8.i32(i8*, i8, i32, i1)
define void @test6(i8* %dest) {
; CHECK-LABEL: @test6(
; CHECK-NEXT: call void @llvm.memset.p0i8.i32(i8* [[DEST:%.*]], i8 -1, i32 1, i1 false)
; CHECK-NEXT: br label [[BB:%.*]]
; CHECK: bb:
; CHECK-NEXT: ret void
;
call void @llvm.memset.p0i8.i32(i8* %dest, i8 255, i32 1, i1 false)
br label %bb
bb:
icmp ne i8* %dest, null
ret void
}
define void @test6_no_null_opt(i8* %dest) #0 {
; CHECK-LABEL: @test6_no_null_opt(
; CHECK-NEXT: call void @llvm.memset.p0i8.i32(i8* [[DEST:%.*]], i8 -1, i32 1, i1 false)
; CHECK-NEXT: br label [[BB:%.*]]
; CHECK: bb:
; CHECK-NEXT: [[TMP1:%.*]] = icmp ne i8* [[DEST]], null
; CHECK-NEXT: ret void
;
call void @llvm.memset.p0i8.i32(i8* %dest, i8 255, i32 1, i1 false)
br label %bb
bb:
icmp ne i8* %dest, null
ret void
}
define void @test7(i8* %dest, i8* %src, i32 %len) {
; CHECK-LABEL: @test7(
; CHECK-NEXT: call void @llvm.memcpy.p0i8.p0i8.i32(i8* [[DEST:%.*]], i8* [[SRC:%.*]], i32 [[LEN:%.*]], i1 false)
; CHECK-NEXT: br label [[BB:%.*]]
; CHECK: bb:
; CHECK-NEXT: [[KEEP1:%.*]] = icmp ne i8* [[DEST]], null
; CHECK-NEXT: [[KEEP2:%.*]] = icmp ne i8* [[SRC]], null
; CHECK-NEXT: ret void
;
call void @llvm.memcpy.p0i8.p0i8.i32(i8* %dest, i8* %src, i32 %len, i1 false)
br label %bb
bb:
%KEEP1 = icmp ne i8* %dest, null
%KEEP2 = icmp ne i8* %src, null
ret void
}
declare void @llvm.memcpy.p1i8.p1i8.i32(i8 addrspace(1) *, i8 addrspace(1) *, i32, i1)
define void @test8(i8 addrspace(1) * %dest, i8 addrspace(1) * %src) {
; CHECK-LABEL: @test8(
; CHECK-NEXT: call void @llvm.memcpy.p1i8.p1i8.i32(i8 addrspace(1)* [[DEST:%.*]], i8 addrspace(1)* [[SRC:%.*]], i32 1, i1 false)
; CHECK-NEXT: br label [[BB:%.*]]
; CHECK: bb:
; CHECK-NEXT: [[KEEP1:%.*]] = icmp ne i8 addrspace(1)* [[DEST]], null
; CHECK-NEXT: [[KEEP2:%.*]] = icmp ne i8 addrspace(1)* [[SRC]], null
; CHECK-NEXT: ret void
;
call void @llvm.memcpy.p1i8.p1i8.i32(i8 addrspace(1) * %dest, i8 addrspace(1) * %src, i32 1, i1 false)
br label %bb
bb:
%KEEP1 = icmp ne i8 addrspace(1) * %dest, null
%KEEP2 = icmp ne i8 addrspace(1) * %src, null
ret void
}
define void @test9(i8* %dest, i8* %src) {
; CHECK-LABEL: @test9(
; CHECK-NEXT: call void @llvm.memcpy.p0i8.p0i8.i32(i8* [[DEST:%.*]], i8* [[SRC:%.*]], i32 1, i1 true)
; CHECK-NEXT: br label [[BB:%.*]]
; CHECK: bb:
; CHECK-NEXT: [[KEEP1:%.*]] = icmp ne i8* [[DEST]], null
; CHECK-NEXT: [[KEEP2:%.*]] = icmp ne i8* [[SRC]], null
; CHECK-NEXT: ret void
;
call void @llvm.memcpy.p0i8.p0i8.i32(i8* %dest, i8* %src, i32 1, i1 true)
br label %bb
bb:
%KEEP1 = icmp ne i8* %dest, null
%KEEP2 = icmp ne i8* %src, null
ret void
}
declare void @test10_helper(i8* %arg1, i8* %arg2, i32 %non-pointer-arg)
define void @test10(i8* %arg1, i8* %arg2, i32 %non-pointer-arg) {
; CHECK-LABEL: @test10(
; CHECK-NEXT: entry:
; CHECK-NEXT: [[IS_NULL:%.*]] = icmp eq i8* [[ARG1:%.*]], null
; CHECK-NEXT: br i1 [[IS_NULL]], label [[NULL:%.*]], label [[NON_NULL:%.*]]
; CHECK: non_null:
; CHECK-NEXT: call void @test10_helper(i8* nonnull [[ARG1]], i8* [[ARG2:%.*]], i32 [[NON_POINTER_ARG:%.*]])
; CHECK-NEXT: br label [[NULL]]
; CHECK: null:
; CHECK-NEXT: call void @test10_helper(i8* [[ARG1]], i8* [[ARG2]], i32 [[NON_POINTER_ARG]])
; CHECK-NEXT: ret void
;
entry:
%is_null = icmp eq i8* %arg1, null
br i1 %is_null, label %null, label %non_null
non_null:
call void @test10_helper(i8* %arg1, i8* %arg2, i32 %non-pointer-arg)
br label %null
null:
call void @test10_helper(i8* %arg1, i8* %arg2, i32 %non-pointer-arg)
ret void
}
declare void @test11_helper(i8* %arg)
define void @test11(i8* %arg1, i8** %arg2) {
; CHECK-LABEL: @test11(
; CHECK-NEXT: entry:
; CHECK-NEXT: [[IS_NULL:%.*]] = icmp eq i8* [[ARG1:%.*]], null
; CHECK-NEXT: br i1 [[IS_NULL]], label [[NULL:%.*]], label [[NON_NULL:%.*]]
; CHECK: non_null:
; CHECK-NEXT: br label [[MERGE:%.*]]
; CHECK: null:
; CHECK-NEXT: [[ANOTHER_ARG:%.*]] = alloca i8, align 1
; CHECK-NEXT: br label [[MERGE]]
; CHECK: merge:
; CHECK-NEXT: [[MERGED_ARG:%.*]] = phi i8* [ [[ANOTHER_ARG]], [[NULL]] ], [ [[ARG1]], [[NON_NULL]] ]
; CHECK-NEXT: call void @test11_helper(i8* nonnull [[MERGED_ARG]])
; CHECK-NEXT: ret void
;
entry:
%is_null = icmp eq i8* %arg1, null
br i1 %is_null, label %null, label %non_null
non_null:
br label %merge
null:
%another_arg = alloca i8
br label %merge
merge:
%merged_arg = phi i8* [%another_arg, %null], [%arg1, %non_null]
call void @test11_helper(i8* %merged_arg)
ret void
}
declare void @test12_helper(i8* %arg)
define void @test12(i8* %arg1, i8** %arg2) {
; CHECK-LABEL: @test12(
; CHECK-NEXT: entry:
; CHECK-NEXT: [[IS_NULL:%.*]] = icmp eq i8* [[ARG1:%.*]], null
; CHECK-NEXT: br i1 [[IS_NULL]], label [[NULL:%.*]], label [[NON_NULL:%.*]]
; CHECK: non_null:
; CHECK-NEXT: br label [[MERGE:%.*]]
; CHECK: null:
; CHECK-NEXT: [[ANOTHER_ARG:%.*]] = load i8*, i8** [[ARG2:%.*]], align 8, !nonnull !0
; CHECK-NEXT: br label [[MERGE]]
; CHECK: merge:
; CHECK-NEXT: [[MERGED_ARG:%.*]] = phi i8* [ [[ANOTHER_ARG]], [[NULL]] ], [ [[ARG1]], [[NON_NULL]] ]
; CHECK-NEXT: call void @test12_helper(i8* nonnull [[MERGED_ARG]])
; CHECK-NEXT: ret void
;
entry:
%is_null = icmp eq i8* %arg1, null
br i1 %is_null, label %null, label %non_null
non_null:
br label %merge
null:
%another_arg = load i8*, i8** %arg2, !nonnull !{}
br label %merge
merge:
%merged_arg = phi i8* [%another_arg, %null], [%arg1, %non_null]
call void @test12_helper(i8* %merged_arg)
ret void
}
define i1 @test_store_same_block(i8* %arg) {
; CHECK-LABEL: @test_store_same_block(
; CHECK-NEXT: store i8 0, i8* [[ARG:%.*]], align 1
; CHECK-NEXT: [[CMP:%.*]] = icmp ne i8* [[ARG]], null
; CHECK-NEXT: ret i1 true
;
store i8 0, i8* %arg
%cmp = icmp ne i8* %arg, null
ret i1 %cmp
}
attributes #0 = { null_pointer_is_valid }