mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2025-02-01 05:01:59 +01:00
73d7a6bc49
PRE in JumpThreading should not be able to hoist copy of non-speculable loads across instructions that don't always transfer execution to their successors, otherwise they may introduce an unsafe load which otherwise would not be executed. The same problem for GVN was fixed as rL316975. Differential Revision: https://reviews.llvm.org/D40347 llvm-svn: 321063
384 lines
10 KiB
LLVM
384 lines
10 KiB
LLVM
; RUN: opt < %s -jump-threading -dce -S | FileCheck %s
|
|
|
|
declare void @llvm.experimental.guard(i1, ...)
|
|
|
|
declare i32 @f1()
|
|
declare i32 @f2()
|
|
|
|
define i32 @branch_implies_guard(i32 %a) {
|
|
; CHECK-LABEL: @branch_implies_guard(
|
|
%cond = icmp slt i32 %a, 10
|
|
br i1 %cond, label %T1, label %F1
|
|
|
|
T1:
|
|
; CHECK: T1.split
|
|
; CHECK: %v1 = call i32 @f1()
|
|
; CHECK-NEXT: %retVal
|
|
; CHECK-NEXT: br label %Merge
|
|
%v1 = call i32 @f1()
|
|
br label %Merge
|
|
|
|
F1:
|
|
; CHECK: F1.split
|
|
; CHECK: %v2 = call i32 @f2()
|
|
; CHECK-NEXT: %retVal
|
|
; CHECK-NEXT: %condGuard
|
|
; CHECK-NEXT: call void (i1, ...) @llvm.experimental.guard(i1 %condGuard
|
|
; CHECK-NEXT: br label %Merge
|
|
%v2 = call i32 @f2()
|
|
br label %Merge
|
|
|
|
Merge:
|
|
; CHECK: Merge
|
|
; CHECK-NOT: call void(i1, ...) @llvm.experimental.guard(
|
|
%retPhi = phi i32 [ %v1, %T1 ], [ %v2, %F1 ]
|
|
%retVal = add i32 %retPhi, 10
|
|
%condGuard = icmp slt i32 %a, 20
|
|
call void(i1, ...) @llvm.experimental.guard(i1 %condGuard) [ "deopt"() ]
|
|
ret i32 %retVal
|
|
}
|
|
|
|
define i32 @not_branch_implies_guard(i32 %a) {
|
|
; CHECK-LABEL: @not_branch_implies_guard(
|
|
%cond = icmp slt i32 %a, 20
|
|
br i1 %cond, label %T1, label %F1
|
|
|
|
T1:
|
|
; CHECK: T1.split:
|
|
; CHECK-NEXT: %v1 = call i32 @f1()
|
|
; CHECK-NEXT: %retVal
|
|
; CHECK-NEXT: %condGuard
|
|
; CHECK-NEXT: call void (i1, ...) @llvm.experimental.guard(i1 %condGuard
|
|
; CHECK-NEXT: br label %Merge
|
|
%v1 = call i32 @f1()
|
|
br label %Merge
|
|
|
|
F1:
|
|
; CHECK: F1.split:
|
|
; CHECK-NEXT: %v2 = call i32 @f2()
|
|
; CHECK-NEXT: %retVal
|
|
; CHECK-NEXT: br label %Merge
|
|
%v2 = call i32 @f2()
|
|
br label %Merge
|
|
|
|
Merge:
|
|
; CHECK: Merge
|
|
; CHECK-NOT: call void(i1, ...) @llvm.experimental.guard(
|
|
%retPhi = phi i32 [ %v1, %T1 ], [ %v2, %F1 ]
|
|
%retVal = add i32 %retPhi, 10
|
|
%condGuard = icmp sgt i32 %a, 10
|
|
call void(i1, ...) @llvm.experimental.guard(i1 %condGuard) [ "deopt"() ]
|
|
ret i32 %retVal
|
|
}
|
|
|
|
define i32 @branch_overlaps_guard(i32 %a) {
|
|
; CHECK-LABEL: @branch_overlaps_guard(
|
|
%cond = icmp slt i32 %a, 20
|
|
br i1 %cond, label %T1, label %F1
|
|
|
|
T1:
|
|
; CHECK: T1:
|
|
; CHECK-NEXT: %v1 = call i32 @f1()
|
|
; CHECK-NEXT: br label %Merge
|
|
%v1 = call i32 @f1()
|
|
br label %Merge
|
|
|
|
F1:
|
|
; CHECK: F1:
|
|
; CHECK-NEXT: %v2 = call i32 @f2()
|
|
; CHECK-NEXT: br label %Merge
|
|
%v2 = call i32 @f2()
|
|
br label %Merge
|
|
|
|
Merge:
|
|
; CHECK: Merge
|
|
; CHECK: %condGuard = icmp slt i32 %a, 10
|
|
; CHECK-NEXT: call void (i1, ...) @llvm.experimental.guard(i1 %condGuard) [ "deopt"() ]
|
|
%retPhi = phi i32 [ %v1, %T1 ], [ %v2, %F1 ]
|
|
%retVal = add i32 %retPhi, 10
|
|
%condGuard = icmp slt i32 %a, 10
|
|
call void(i1, ...) @llvm.experimental.guard(i1 %condGuard) [ "deopt"() ]
|
|
ret i32 %retVal
|
|
}
|
|
|
|
define i32 @branch_doesnt_overlap_guard(i32 %a) {
|
|
; CHECK-LABEL: @branch_doesnt_overlap_guard(
|
|
%cond = icmp slt i32 %a, 10
|
|
br i1 %cond, label %T1, label %F1
|
|
|
|
T1:
|
|
; CHECK: T1:
|
|
; CHECK-NEXT: %v1 = call i32 @f1()
|
|
; CHECK-NEXT: br label %Merge
|
|
%v1 = call i32 @f1()
|
|
br label %Merge
|
|
|
|
F1:
|
|
; CHECK: F1:
|
|
; CHECK-NEXT: %v2 = call i32 @f2()
|
|
; CHECK-NEXT: br label %Merge
|
|
%v2 = call i32 @f2()
|
|
br label %Merge
|
|
|
|
Merge:
|
|
; CHECK: Merge
|
|
; CHECK: %condGuard = icmp sgt i32 %a, 20
|
|
; CHECK-NEXT: call void (i1, ...) @llvm.experimental.guard(i1 %condGuard) [ "deopt"() ]
|
|
%retPhi = phi i32 [ %v1, %T1 ], [ %v2, %F1 ]
|
|
%retVal = add i32 %retPhi, 10
|
|
%condGuard = icmp sgt i32 %a, 20
|
|
call void(i1, ...) @llvm.experimental.guard(i1 %condGuard) [ "deopt"() ]
|
|
ret i32 %retVal
|
|
}
|
|
|
|
define i32 @not_a_diamond1(i32 %a, i1 %cond1) {
|
|
; CHECK-LABEL: @not_a_diamond1(
|
|
br i1 %cond1, label %Pred, label %Exit
|
|
|
|
Pred:
|
|
; CHECK: Pred:
|
|
; CHECK-NEXT: switch i32 %a, label %Exit
|
|
switch i32 %a, label %Exit [
|
|
i32 10, label %Merge
|
|
i32 20, label %Merge
|
|
]
|
|
|
|
Merge:
|
|
; CHECK: Merge:
|
|
; CHECK-NEXT: call void (i1, ...) @llvm.experimental.guard(i1 %cond1) [ "deopt"() ]
|
|
; CHECK-NEXT: br label %Exit
|
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond1) [ "deopt"() ]
|
|
br label %Exit
|
|
|
|
Exit:
|
|
; CHECK: Exit:
|
|
; CHECK-NEXT: ret i32 %a
|
|
ret i32 %a
|
|
}
|
|
|
|
define void @not_a_diamond2(i32 %a, i1 %cond1) {
|
|
; CHECK-LABEL: @not_a_diamond2(
|
|
br label %Parent
|
|
|
|
Merge:
|
|
call void(i1, ...) @llvm.experimental.guard(i1 %cond1)[ "deopt"() ]
|
|
ret void
|
|
|
|
Pred:
|
|
; CHECK-NEXT: Pred:
|
|
; CHECK-NEXT: switch i32 %a, label %Exit
|
|
switch i32 %a, label %Exit [
|
|
i32 10, label %Merge
|
|
i32 20, label %Merge
|
|
]
|
|
|
|
Parent:
|
|
br label %Pred
|
|
|
|
Exit:
|
|
; CHECK: Merge:
|
|
; CHECK-NEXT: call void (i1, ...) @llvm.experimental.guard(i1 %cond1) [ "deopt"() ]
|
|
; CHECK-NEXT: ret void
|
|
ret void
|
|
}
|
|
|
|
declare void @never_called(i1)
|
|
|
|
; LVI uses guard to identify value of %c2 in branch as true, we cannot replace that
|
|
; guard with guard(true & c1).
|
|
define void @dont_fold_guard(i8* %addr, i32 %i, i32 %length) {
|
|
; CHECK-LABEL: dont_fold_guard
|
|
; CHECK: %wide.chk = and i1 %c1, %c2
|
|
; CHECK-NEXT: experimental.guard(i1 %wide.chk)
|
|
; CHECK-NEXT: call void @never_called(i1 true)
|
|
; CHECK-NEXT: ret void
|
|
%c1 = icmp ult i32 %i, %length
|
|
%c2 = icmp eq i32 %i, 0
|
|
%wide.chk = and i1 %c1, %c2
|
|
call void(i1, ...) @llvm.experimental.guard(i1 %wide.chk) [ "deopt"() ]
|
|
br i1 %c2, label %BB1, label %BB2
|
|
|
|
BB1:
|
|
call void @never_called(i1 %c2)
|
|
ret void
|
|
|
|
BB2:
|
|
ret void
|
|
}
|
|
|
|
declare void @dummy(i1) nounwind argmemonly
|
|
; same as dont_fold_guard1 but there's a use immediately after guard and before
|
|
; branch. We can fold that use.
|
|
define void @dont_fold_guard2(i8* %addr, i32 %i, i32 %length) {
|
|
; CHECK-LABEL: dont_fold_guard2
|
|
; CHECK: %wide.chk = and i1 %c1, %c2
|
|
; CHECK-NEXT: experimental.guard(i1 %wide.chk)
|
|
; CHECK-NEXT: dummy(i1 true)
|
|
; CHECK-NEXT: call void @never_called(i1 true)
|
|
; CHECK-NEXT: ret void
|
|
%c1 = icmp ult i32 %i, %length
|
|
%c2 = icmp eq i32 %i, 0
|
|
%wide.chk = and i1 %c1, %c2
|
|
call void(i1, ...) @llvm.experimental.guard(i1 %wide.chk) [ "deopt"() ]
|
|
call void @dummy(i1 %c2)
|
|
br i1 %c2, label %BB1, label %BB2
|
|
|
|
BB1:
|
|
call void @never_called(i1 %c2)
|
|
ret void
|
|
|
|
BB2:
|
|
ret void
|
|
}
|
|
|
|
; same as dont_fold_guard1 but condition %cmp is not an instruction.
|
|
; We cannot fold the guard under any circumstance.
|
|
; FIXME: We can merge unreachableBB2 into not_zero.
|
|
define void @dont_fold_guard3(i8* %addr, i1 %cmp, i32 %i, i32 %length) {
|
|
; CHECK-LABEL: dont_fold_guard3
|
|
; CHECK: guard(i1 %cmp)
|
|
call void(i1, ...) @llvm.experimental.guard(i1 %cmp) [ "deopt"() ]
|
|
br i1 %cmp, label %BB1, label %BB2
|
|
|
|
BB1:
|
|
call void @never_called(i1 %cmp)
|
|
ret void
|
|
|
|
BB2:
|
|
ret void
|
|
}
|
|
|
|
declare void @f(i1)
|
|
; Same as dont_fold_guard1 but use switch instead of branch.
|
|
; triggers source code `ProcessThreadableEdges`.
|
|
define void @dont_fold_guard4(i1 %cmp1, i32 %i) nounwind {
|
|
; CHECK-LABEL: dont_fold_guard4
|
|
; CHECK-LABEL: L2:
|
|
; CHECK-NEXT: %cmp = icmp eq i32 %i, 0
|
|
; CHECK-NEXT: guard(i1 %cmp)
|
|
; CHECK-NEXT: dummy(i1 true)
|
|
; CHECK-NEXT: @f(i1 true)
|
|
; CHECK-NEXT: ret void
|
|
entry:
|
|
br i1 %cmp1, label %L0, label %L3
|
|
L0:
|
|
%cmp = icmp eq i32 %i, 0
|
|
call void(i1, ...) @llvm.experimental.guard(i1 %cmp) [ "deopt"() ]
|
|
call void @dummy(i1 %cmp)
|
|
switch i1 %cmp, label %L3 [
|
|
i1 false, label %L1
|
|
i1 true, label %L2
|
|
]
|
|
|
|
L1:
|
|
ret void
|
|
L2:
|
|
call void @f(i1 %cmp)
|
|
ret void
|
|
L3:
|
|
ret void
|
|
}
|
|
|
|
; Make sure that we don't PRE a non-speculable load across a guard.
|
|
define void @unsafe_pre_across_guard(i8* %p, i1 %load.is.valid) {
|
|
|
|
; CHECK-LABEL: @unsafe_pre_across_guard(
|
|
; CHECK-NOT: loaded.pr
|
|
; CHECK: entry:
|
|
; CHECK-NEXT: br label %loop
|
|
; CHECK: loop:
|
|
; CHECK-NEXT: call void (i1, ...) @llvm.experimental.guard(i1 %load.is.valid) [ "deopt"() ]
|
|
; CHECK-NEXT: %loaded = load i8, i8* %p
|
|
; CHECK-NEXT: %continue = icmp eq i8 %loaded, 0
|
|
; CHECK-NEXT: br i1 %continue, label %exit, label %loop
|
|
entry:
|
|
br label %loop
|
|
|
|
loop: ; preds = %loop, %entry
|
|
call void (i1, ...) @llvm.experimental.guard(i1 %load.is.valid) [ "deopt"() ]
|
|
%loaded = load i8, i8* %p
|
|
%continue = icmp eq i8 %loaded, 0
|
|
br i1 %continue, label %exit, label %loop
|
|
|
|
exit: ; preds = %loop
|
|
ret void
|
|
}
|
|
|
|
; Make sure that we can safely PRE a speculable load across a guard.
|
|
define void @safe_pre_across_guard(i8* noalias nocapture readonly dereferenceable(8) %p, i1 %load.is.valid) {
|
|
|
|
; CHECK-LABEL: @safe_pre_across_guard(
|
|
; CHECK: entry:
|
|
; CHECK-NEXT: %loaded.pr = load i8, i8* %p
|
|
; CHECK-NEXT: br label %loop
|
|
; CHECK: loop:
|
|
; CHECK-NEXT: %loaded = phi i8 [ %loaded, %loop ], [ %loaded.pr, %entry ]
|
|
; CHECK-NEXT: call void (i1, ...) @llvm.experimental.guard(i1 %load.is.valid) [ "deopt"() ]
|
|
; CHECK-NEXT: %continue = icmp eq i8 %loaded, 0
|
|
; CHECK-NEXT: br i1 %continue, label %exit, label %loop
|
|
|
|
entry:
|
|
br label %loop
|
|
|
|
loop: ; preds = %loop, %entry
|
|
call void (i1, ...) @llvm.experimental.guard(i1 %load.is.valid) [ "deopt"() ]
|
|
%loaded = load i8, i8* %p
|
|
%continue = icmp eq i8 %loaded, 0
|
|
br i1 %continue, label %exit, label %loop
|
|
|
|
exit: ; preds = %loop
|
|
ret void
|
|
}
|
|
|
|
; Make sure that we don't PRE a non-speculable load across a call which may
|
|
; alias with the load.
|
|
define void @unsafe_pre_across_call(i8* %p) {
|
|
|
|
; CHECK-LABEL: @unsafe_pre_across_call(
|
|
; CHECK-NOT: loaded.pr
|
|
; CHECK: entry:
|
|
; CHECK-NEXT: br label %loop
|
|
; CHECK: loop:
|
|
; CHECK-NEXT: call i32 @f1()
|
|
; CHECK-NEXT: %loaded = load i8, i8* %p
|
|
; CHECK-NEXT: %continue = icmp eq i8 %loaded, 0
|
|
; CHECK-NEXT: br i1 %continue, label %exit, label %loop
|
|
entry:
|
|
br label %loop
|
|
|
|
loop: ; preds = %loop, %entry
|
|
call i32 @f1()
|
|
%loaded = load i8, i8* %p
|
|
%continue = icmp eq i8 %loaded, 0
|
|
br i1 %continue, label %exit, label %loop
|
|
|
|
exit: ; preds = %loop
|
|
ret void
|
|
}
|
|
|
|
; Make sure that we can safely PRE a speculable load across a call.
|
|
define void @safe_pre_across_call(i8* noalias nocapture readonly dereferenceable(8) %p) {
|
|
|
|
; CHECK-LABEL: @safe_pre_across_call(
|
|
; CHECK: entry:
|
|
; CHECK-NEXT: %loaded.pr = load i8, i8* %p
|
|
; CHECK-NEXT: br label %loop
|
|
; CHECK: loop:
|
|
; CHECK-NEXT: %loaded = phi i8 [ %loaded, %loop ], [ %loaded.pr, %entry ]
|
|
; CHECK-NEXT: call i32 @f1()
|
|
; CHECK-NEXT: %continue = icmp eq i8 %loaded, 0
|
|
; CHECK-NEXT: br i1 %continue, label %exit, label %loop
|
|
|
|
entry:
|
|
br label %loop
|
|
|
|
loop: ; preds = %loop, %entry
|
|
call i32 @f1()
|
|
%loaded = load i8, i8* %p
|
|
%continue = icmp eq i8 %loaded, 0
|
|
br i1 %continue, label %exit, label %loop
|
|
|
|
exit: ; preds = %loop
|
|
ret void
|
|
}
|