408 lines
12 KiB
LLVM
408 lines
12 KiB
LLVM
|
; RUN: opt -S -guard-widening < %s | FileCheck %s
|
||
|
; RUN: opt -S -passes=guard-widening < %s | FileCheck %s
|
||
|
|
||
|
declare void @llvm.experimental.guard(i1,...)
|
||
|
|
||
|
; Basic test case: we wide the first check to check both the
|
||
|
; conditions.
|
||
|
define void @f_0(i1 %cond_0, i1 %cond_1) {
|
||
|
; CHECK-LABEL: @f_0(
|
||
|
entry:
|
||
|
; CHECK: %wide.chk = and i1 %cond_0, %cond_1
|
||
|
; CHECK: call void (i1, ...) @llvm.experimental.guard(i1 %wide.chk) [ "deopt"() ]
|
||
|
; CHECK: ret void
|
||
|
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_0) [ "deopt"() ]
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_1) [ "deopt"() ]
|
||
|
ret void
|
||
|
}
|
||
|
|
||
|
; Same as @f_0, but with using a more general notion of postdominance.
|
||
|
define void @f_1(i1 %cond_0, i1 %cond_1) {
|
||
|
; CHECK-LABEL: @f_1(
|
||
|
entry:
|
||
|
; CHECK: %wide.chk = and i1 %cond_0, %cond_1
|
||
|
; CHECK: call void (i1, ...) @llvm.experimental.guard(i1 %wide.chk) [ "deopt"() ]
|
||
|
; CHECK: br i1 undef, label %left, label %right
|
||
|
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_0) [ "deopt"() ]
|
||
|
br i1 undef, label %left, label %right
|
||
|
|
||
|
left:
|
||
|
br label %merge
|
||
|
|
||
|
right:
|
||
|
br label %merge
|
||
|
|
||
|
merge:
|
||
|
; CHECK: merge:
|
||
|
; CHECK-NOT: call void (i1, ...) @llvm.experimental.guard(
|
||
|
; CHECK: ret void
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_1) [ "deopt"() ]
|
||
|
ret void
|
||
|
}
|
||
|
|
||
|
; Like @f_1, but we have some code we need to hoist before we can
|
||
|
; widen a dominanting check.
|
||
|
define void @f_2(i32 %a, i32 %b) {
|
||
|
; CHECK-LABEL: @f_2(
|
||
|
entry:
|
||
|
; CHECK: %cond_0 = icmp ult i32 %a, 10
|
||
|
; CHECK: %cond_1 = icmp ult i32 %b, 10
|
||
|
; CHECK: %wide.chk = and i1 %cond_0, %cond_1
|
||
|
; CHECK: call void (i1, ...) @llvm.experimental.guard(i1 %wide.chk) [ "deopt"() ]
|
||
|
; CHECK: br i1 undef, label %left, label %right
|
||
|
|
||
|
%cond_0 = icmp ult i32 %a, 10
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_0) [ "deopt"() ]
|
||
|
br i1 undef, label %left, label %right
|
||
|
|
||
|
left:
|
||
|
br label %merge
|
||
|
|
||
|
right:
|
||
|
br label %merge
|
||
|
|
||
|
merge:
|
||
|
%cond_1 = icmp ult i32 %b, 10
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_1) [ "deopt"() ]
|
||
|
ret void
|
||
|
}
|
||
|
|
||
|
; Negative test: don't hoist stuff out of control flow
|
||
|
; indiscriminately, since that can make us do more work than needed.
|
||
|
define void @f_3(i32 %a, i32 %b) {
|
||
|
; CHECK-LABEL: @f_3(
|
||
|
entry:
|
||
|
; CHECK: %cond_0 = icmp ult i32 %a, 10
|
||
|
; CHECK: call void (i1, ...) @llvm.experimental.guard(i1 %cond_0) [ "deopt"() ]
|
||
|
; CHECK: br i1 undef, label %left, label %right
|
||
|
|
||
|
%cond_0 = icmp ult i32 %a, 10
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_0) [ "deopt"() ]
|
||
|
br i1 undef, label %left, label %right
|
||
|
|
||
|
left:
|
||
|
; CHECK: left:
|
||
|
; CHECK: %cond_1 = icmp ult i32 %b, 10
|
||
|
; CHECK: call void (i1, ...) @llvm.experimental.guard(i1 %cond_1) [ "deopt"() ]
|
||
|
; CHECK: ret void
|
||
|
|
||
|
%cond_1 = icmp ult i32 %b, 10
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_1) [ "deopt"() ]
|
||
|
ret void
|
||
|
|
||
|
right:
|
||
|
ret void
|
||
|
}
|
||
|
|
||
|
; But hoisting out of control flow is fine if it makes a loop computed
|
||
|
; condition loop invariant. This behavior may require some tuning in
|
||
|
; the future.
|
||
|
define void @f_4(i32 %a, i32 %b) {
|
||
|
; CHECK-LABEL: @f_4(
|
||
|
entry:
|
||
|
; CHECK: %cond_0 = icmp ult i32 %a, 10
|
||
|
; CHECK: %cond_1 = icmp ult i32 %b, 10
|
||
|
; CHECK: %wide.chk = and i1 %cond_0, %cond_1
|
||
|
; CHECK: call void (i1, ...) @llvm.experimental.guard(i1 %wide.chk) [ "deopt"() ]
|
||
|
; CHECK: br i1 undef, label %loop, label %leave
|
||
|
|
||
|
%cond_0 = icmp ult i32 %a, 10
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_0) [ "deopt"() ]
|
||
|
br i1 undef, label %loop, label %leave
|
||
|
|
||
|
loop:
|
||
|
%cond_1 = icmp ult i32 %b, 10
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_1) [ "deopt"() ]
|
||
|
br i1 undef, label %loop, label %leave
|
||
|
|
||
|
leave:
|
||
|
ret void
|
||
|
}
|
||
|
|
||
|
; Hoisting out of control flow is also fine if we can widen the
|
||
|
; dominating check without doing any extra work.
|
||
|
define void @f_5(i32 %a) {
|
||
|
; CHECK-LABEL: @f_5(
|
||
|
entry:
|
||
|
; CHECK: %wide.chk = icmp uge i32 %a, 11
|
||
|
; CHECK: call void (i1, ...) @llvm.experimental.guard(i1 %wide.chk) [ "deopt"() ]
|
||
|
; CHECK: br i1 undef, label %left, label %right
|
||
|
|
||
|
%cond_0 = icmp ugt i32 %a, 7
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_0) [ "deopt"() ]
|
||
|
br i1 undef, label %left, label %right
|
||
|
|
||
|
left:
|
||
|
%cond_1 = icmp ugt i32 %a, 10
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_1) [ "deopt"() ]
|
||
|
ret void
|
||
|
|
||
|
right:
|
||
|
ret void
|
||
|
}
|
||
|
|
||
|
; Negative test: the load from %a can be safely speculated to before
|
||
|
; the first guard, but there is no guarantee that it will produce the
|
||
|
; same value.
|
||
|
define void @f_6(i1* dereferenceable(32) %a, i1* %b, i1 %unknown) {
|
||
|
; CHECK-LABEL: @f_6(
|
||
|
; CHECK: call void (i1, ...) @llvm.experimental.guard(
|
||
|
; CHECK: call void (i1, ...) @llvm.experimental.guard(
|
||
|
; CHECK: ret void
|
||
|
entry:
|
||
|
%cond_0 = load i1, i1* %a
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_0) [ "deopt"() ]
|
||
|
store i1 %unknown, i1* %b
|
||
|
%cond_1 = load i1, i1* %a
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_1) [ "deopt"() ]
|
||
|
ret void
|
||
|
}
|
||
|
|
||
|
; All else equal, we try to widen the earliest guard we can. This
|
||
|
; heuristic can use some tuning.
|
||
|
define void @f_7(i32 %a, i1* %cond_buf) {
|
||
|
; CHECK-LABEL: @f_7(
|
||
|
entry:
|
||
|
; CHECK: %cond_1 = load volatile i1, i1* %cond_buf
|
||
|
; CHECK: %cond_3 = icmp ult i32 %a, 7
|
||
|
; CHECK: %wide.chk = and i1 %cond_1, %cond_3
|
||
|
; CHECK: call void (i1, ...) @llvm.experimental.guard(i1 %wide.chk) [ "deopt"() ]
|
||
|
; CHECK: %cond_2 = load volatile i1, i1* %cond_buf
|
||
|
; CHECK: call void (i1, ...) @llvm.experimental.guard(i1 %cond_2) [ "deopt"() ]
|
||
|
; CHECK: br i1 undef, label %left, label %right
|
||
|
|
||
|
%cond_1 = load volatile i1, i1* %cond_buf
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_1) [ "deopt"() ]
|
||
|
%cond_2 = load volatile i1, i1* %cond_buf
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_2) [ "deopt"() ]
|
||
|
br i1 undef, label %left, label %right
|
||
|
|
||
|
left:
|
||
|
%cond_3 = icmp ult i32 %a, 7
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_3) [ "deopt"() ]
|
||
|
br label %left
|
||
|
|
||
|
right:
|
||
|
ret void
|
||
|
}
|
||
|
|
||
|
; In this case the earliest dominating guard is in a loop, and we
|
||
|
; don't want to put extra work in there. This heuristic can use some
|
||
|
; tuning.
|
||
|
define void @f_8(i32 %a, i1 %cond_1, i1 %cond_2) {
|
||
|
; CHECK-LABEL: @f_8(
|
||
|
entry:
|
||
|
br label %loop
|
||
|
|
||
|
loop:
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_1) [ "deopt"() ]
|
||
|
br i1 undef, label %loop, label %leave
|
||
|
|
||
|
leave:
|
||
|
; CHECK: leave:
|
||
|
; CHECK: %cond_3 = icmp ult i32 %a, 7
|
||
|
; CHECK: %wide.chk = and i1 %cond_2, %cond_3
|
||
|
; CHECK: call void (i1, ...) @llvm.experimental.guard(i1 %wide.chk) [ "deopt"() ]
|
||
|
; CHECK: br i1 undef, label %loop2, label %leave2
|
||
|
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_2) [ "deopt"() ]
|
||
|
br i1 undef, label %loop2, label %leave2
|
||
|
|
||
|
loop2:
|
||
|
%cond_3 = icmp ult i32 %a, 7
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_3) [ "deopt"() ]
|
||
|
br label %loop2
|
||
|
|
||
|
leave2:
|
||
|
ret void
|
||
|
}
|
||
|
|
||
|
; In cases like these where there isn't any "obviously profitable"
|
||
|
; widening sites, we refuse to do anything.
|
||
|
define void @f_9(i32 %a, i1 %cond_0, i1 %cond_1) {
|
||
|
; CHECK-LABEL: @f_9(
|
||
|
entry:
|
||
|
br label %first_loop
|
||
|
|
||
|
first_loop:
|
||
|
; CHECK: first_loop:
|
||
|
; CHECK: call void (i1, ...) @llvm.experimental.guard(i1 %cond_0) [ "deopt"() ]
|
||
|
; CHECK: br i1 undef, label %first_loop, label %second_loop
|
||
|
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_0) [ "deopt"() ]
|
||
|
br i1 undef, label %first_loop, label %second_loop
|
||
|
|
||
|
second_loop:
|
||
|
; CHECK: second_loop:
|
||
|
; CHECK: call void (i1, ...) @llvm.experimental.guard(i1 %cond_1) [ "deopt"() ]
|
||
|
; CHECK: br label %second_loop
|
||
|
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_1) [ "deopt"() ]
|
||
|
br label %second_loop
|
||
|
}
|
||
|
|
||
|
; Same situation as in @f_9: no "obviously profitable" widening sites,
|
||
|
; so we refuse to do anything.
|
||
|
define void @f_10(i32 %a, i1 %cond_0, i1 %cond_1) {
|
||
|
; CHECK-LABEL: @f_10(
|
||
|
entry:
|
||
|
br label %loop
|
||
|
|
||
|
loop:
|
||
|
; CHECK: loop:
|
||
|
; CHECK: call void (i1, ...) @llvm.experimental.guard(i1 %cond_0) [ "deopt"() ]
|
||
|
; CHECK: br i1 undef, label %loop, label %no_loop
|
||
|
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_0) [ "deopt"() ]
|
||
|
br i1 undef, label %loop, label %no_loop
|
||
|
|
||
|
no_loop:
|
||
|
; CHECK: no_loop:
|
||
|
; CHECK: call void (i1, ...) @llvm.experimental.guard(i1 %cond_1) [ "deopt"() ]
|
||
|
; CHECK: ret void
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_1) [ "deopt"() ]
|
||
|
ret void
|
||
|
}
|
||
|
|
||
|
; With guards in loops, we're okay hoisting out the guard into the
|
||
|
; containing loop.
|
||
|
define void @f_11(i32 %a, i1 %cond_0, i1 %cond_1) {
|
||
|
; CHECK-LABEL: @f_11(
|
||
|
entry:
|
||
|
br label %inner
|
||
|
|
||
|
inner:
|
||
|
; CHECK: inner:
|
||
|
; CHECK: %wide.chk = and i1 %cond_0, %cond_1
|
||
|
; CHECK: call void (i1, ...) @llvm.experimental.guard(i1 %wide.chk) [ "deopt"() ]
|
||
|
; CHECK: br i1 undef, label %inner, label %outer
|
||
|
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_0) [ "deopt"() ]
|
||
|
br i1 undef, label %inner, label %outer
|
||
|
|
||
|
outer:
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_1) [ "deopt"() ]
|
||
|
br label %inner
|
||
|
}
|
||
|
|
||
|
; Checks that we are adequately guarded against exponential-time
|
||
|
; behavior when hoisting code.
|
||
|
define void @f_12(i32 %a0) {
|
||
|
; CHECK-LABEL: @f_12
|
||
|
|
||
|
; Eliding the earlier 29 multiplications for brevity
|
||
|
; CHECK: %a30 = mul i32 %a29, %a29
|
||
|
; CHECK-NEXT: %cond = trunc i32 %a30 to i1
|
||
|
; CHECK-NEXT: %wide.chk = and i1 true, %cond
|
||
|
; CHECK-NEXT: call void (i1, ...) @llvm.experimental.guard(i1 %wide.chk) [ "deopt"() ]
|
||
|
; CHECK-NEXT: ret void
|
||
|
|
||
|
entry:
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 true) [ "deopt"() ]
|
||
|
%a1 = mul i32 %a0, %a0
|
||
|
%a2 = mul i32 %a1, %a1
|
||
|
%a3 = mul i32 %a2, %a2
|
||
|
%a4 = mul i32 %a3, %a3
|
||
|
%a5 = mul i32 %a4, %a4
|
||
|
%a6 = mul i32 %a5, %a5
|
||
|
%a7 = mul i32 %a6, %a6
|
||
|
%a8 = mul i32 %a7, %a7
|
||
|
%a9 = mul i32 %a8, %a8
|
||
|
%a10 = mul i32 %a9, %a9
|
||
|
%a11 = mul i32 %a10, %a10
|
||
|
%a12 = mul i32 %a11, %a11
|
||
|
%a13 = mul i32 %a12, %a12
|
||
|
%a14 = mul i32 %a13, %a13
|
||
|
%a15 = mul i32 %a14, %a14
|
||
|
%a16 = mul i32 %a15, %a15
|
||
|
%a17 = mul i32 %a16, %a16
|
||
|
%a18 = mul i32 %a17, %a17
|
||
|
%a19 = mul i32 %a18, %a18
|
||
|
%a20 = mul i32 %a19, %a19
|
||
|
%a21 = mul i32 %a20, %a20
|
||
|
%a22 = mul i32 %a21, %a21
|
||
|
%a23 = mul i32 %a22, %a22
|
||
|
%a24 = mul i32 %a23, %a23
|
||
|
%a25 = mul i32 %a24, %a24
|
||
|
%a26 = mul i32 %a25, %a25
|
||
|
%a27 = mul i32 %a26, %a26
|
||
|
%a28 = mul i32 %a27, %a27
|
||
|
%a29 = mul i32 %a28, %a28
|
||
|
%a30 = mul i32 %a29, %a29
|
||
|
%cond = trunc i32 %a30 to i1
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond) [ "deopt"() ]
|
||
|
ret void
|
||
|
}
|
||
|
|
||
|
define void @f_13(i32 %a) {
|
||
|
; CHECK-LABEL: @f_13(
|
||
|
entry:
|
||
|
; CHECK: %wide.chk = icmp ult i32 %a, 10
|
||
|
; CHECK: call void (i1, ...) @llvm.experimental.guard(i1 %wide.chk) [ "deopt"() ]
|
||
|
; CHECK: br i1 undef, label %left, label %right
|
||
|
|
||
|
%cond_0 = icmp ult i32 %a, 14
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_0) [ "deopt"() ]
|
||
|
br i1 undef, label %left, label %right
|
||
|
|
||
|
left:
|
||
|
%cond_1 = icmp slt i32 %a, 10
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_1) [ "deopt"() ]
|
||
|
ret void
|
||
|
|
||
|
right:
|
||
|
ret void
|
||
|
}
|
||
|
|
||
|
define void @f_14(i32 %a) {
|
||
|
; CHECK-LABEL: @f_14(
|
||
|
entry:
|
||
|
; CHECK: %cond_0 = icmp ult i32 %a, 14
|
||
|
; CHECK: call void (i1, ...) @llvm.experimental.guard(i1 %cond_0) [ "deopt"() ]
|
||
|
; CHECK: br i1 undef, label %left, label %right
|
||
|
|
||
|
%cond_0 = icmp ult i32 %a, 14
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_0) [ "deopt"() ]
|
||
|
br i1 undef, label %left, label %right
|
||
|
|
||
|
left:
|
||
|
; CHECK: left:
|
||
|
; CHECK: %cond_1 = icmp sgt i32 %a, 10
|
||
|
; CHECK: call void (i1, ...) @llvm.experimental.guard(i1 %cond_1) [ "deopt"() ]
|
||
|
|
||
|
%cond_1 = icmp sgt i32 %a, 10
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_1) [ "deopt"() ]
|
||
|
ret void
|
||
|
|
||
|
right:
|
||
|
ret void
|
||
|
}
|
||
|
|
||
|
; Make sure we do not widen guard by trivial true conditions into something.
|
||
|
define void @f_15(i1 %cond_0, i1 %cond_1) {
|
||
|
; CHECK-LABEL: @f_15(
|
||
|
entry:
|
||
|
; CHECK: call void (i1, ...) @llvm.experimental.guard(i1 %cond_0) [ "deopt"() ]
|
||
|
; CHECK: call void (i1, ...) @llvm.experimental.guard(i1 true) [ "deopt"() ]
|
||
|
; CHECK: ret void
|
||
|
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_0) [ "deopt"() ]
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 true) [ "deopt"() ]
|
||
|
ret void
|
||
|
}
|
||
|
|
||
|
; Make sure we do not widen guard by trivial false conditions into something.
|
||
|
define void @f_16(i1 %cond_0, i1 %cond_1) {
|
||
|
; CHECK-LABEL: @f_16(
|
||
|
entry:
|
||
|
; CHECK: call void (i1, ...) @llvm.experimental.guard(i1 %cond_0) [ "deopt"() ]
|
||
|
; CHECK: call void (i1, ...) @llvm.experimental.guard(i1 false) [ "deopt"() ]
|
||
|
; CHECK: ret void
|
||
|
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond_0) [ "deopt"() ]
|
||
|
call void(i1, ...) @llvm.experimental.guard(i1 false) [ "deopt"() ]
|
||
|
ret void
|
||
|
}
|