; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
; RUN: opt -S -passes=guard-widening,dce < %s | FileCheck %s
declare void @llvm.experimental.guard(i1,...)
declare i1 @dummy()
; This tests shows the incorrect behavior of guard widening in terms of
; interaction with poison values.
; Let x incoming parameter is used for rane checks.
; Test generates 5 checks. One of them (c2) is used to get the corretness
; of nuw/nsw flags for x3 and x5. Others are used in guards and represent
; the checks x + 10 u< L, x + 15 u< L, x + 20 u< L and x + 3 u< L.
; The first two checks are in the first basic block and guard widening
; considers them as profitable to combine.
; When c4 and c3 are considered, number of check becomes more than two
; and combineRangeCheck consider them as profitable even if they are in
; different basic blocks.
; Accoding to algorithm of combineRangeCheck it detects that c3 and c4
; are enough to cover c1 and c5, so it ends up with guard of c3 && c4
; while both of them are poison at entry. This is a bug.
define void @combine_range_checks(i32 %x) {
; CHECK-LABEL: @combine_range_checks(
; CHECK-NEXT: entry:
; CHECK-NEXT: [[X_GW_FR:%.*]] = freeze i32 [[X:%.*]]
; CHECK-NEXT: [[X2:%.*]] = add i32 [[X_GW_FR]], 0
; CHECK-NEXT: [[C2:%.*]] = icmp ult i32 [[X2]], 200
; CHECK-NEXT: [[X3:%.*]] = add i32 [[X_GW_FR]], 3
; CHECK-NEXT: [[C3:%.*]] = icmp ult i32 [[X3]], 100
; CHECK-NEXT: [[X4:%.*]] = add i32 [[X_GW_FR]], 20
; CHECK-NEXT: [[C4:%.*]] = icmp ult i32 [[X4]], 100
; CHECK-NEXT: [[WIDE_CHK2:%.*]] = and i1 [[C4]], [[C3]]
; CHECK-NEXT: call void (i1, ...) @llvm.experimental.guard(i1 [[WIDE_CHK2]]) [ "deopt"(i64 1) ]
; CHECK-NEXT: br i1 [[C2]], label [[OK:%.*]], label [[OUT:%.*]]
; CHECK: ok:
; CHECK-NEXT: br label [[OUT]]
; CHECK: out:
; CHECK-NEXT: ret void
;
entry:
%x1 = add i32 %x, 10
%c1 = icmp ult i32 %x1, 100
%x2 = add i32 %x, 0
%c2 = icmp ult i32 %x2, 200
%x3 = add nuw nsw i32 %x, 3
%c3 = icmp ult i32 %x3, 100
%x4 = add nuw nsw i32 %x, 20
%c4 = icmp ult i32 %x4, 100
%x5 = add i32 %x, 15
%c5 = icmp ult i32 %x5, 100
call void(i1, ...) @llvm.experimental.guard(i1 %c1) [ "deopt"(i64 1) ]
call void(i1, ...) @llvm.experimental.guard(i1 %c5) [ "deopt"(i64 5) ]
br i1 %c2, label %ok, label %out
ok:
call void(i1, ...) @llvm.experimental.guard(i1 %c4) [ "deopt"(i64 4) ]
call void(i1, ...) @llvm.experimental.guard(i1 %c3) [ "deopt"(i64 3) ]
br label %out
out:
ret void
}
; This is similar to @combine_range_checks but shows that simple freeze
; over c3 and c4 will not help due to with X = SMAX_INT, guard with c1 will
; go to deoptimization. But after guard widening freeze of c3 and c4 may return
; true due to c3 and c4 are poisons and we pass guard executing side effect store
; which never been executed in original program.
define void @combine_range_checks_with_side_effect(i32 %x, ptr %p) {
; CHECK-LABEL: @combine_range_checks_with_side_effect(
; CHECK-NEXT: entry:
; CHECK-NEXT: [[X_GW_FR:%.*]] = freeze i32 [[X:%.*]]
; CHECK-NEXT: [[X2:%.*]] = add i32 [[X_GW_FR]], 0
; CHECK-NEXT: [[C2:%.*]] = icmp ult i32 [[X2]], 200
; CHECK-NEXT: [[X3:%.*]] = add i32 [[X_GW_FR]], 3
; CHECK-NEXT: [[C3:%.*]] = icmp ult i32 [[X3]], 100
; CHECK-NEXT: [[X4:%.*]] = add i32 [[X_GW_FR]], 20
; CHECK-NEXT: [[C4:%.*]] = icmp ult i32 [[X4]], 100
; CHECK-NEXT: [[WIDE_CHK2:%.*]] = and i1 [[C4]], [[C3]]
; CHECK-NEXT: call void (i1, ...) @llvm.experimental.guard(i1 [[WIDE_CHK2]]) [ "deopt"(i64 1) ]
; CHECK-NEXT: store i32 0, ptr [[P:%.*]], align 4
; CHECK-NEXT: br i1 [[C2]], label [[OK:%.*]], label [[OUT:%.*]]
; CHECK: ok:
; CHECK-NEXT: br label [[OUT]]
; CHECK: out:
; CHECK-NEXT: ret void
;
entry:
%x1 = add i32 %x, 10
%c1 = icmp ult i32 %x1, 100
%x2 = add i32 %x, 0
%c2 = icmp ult i32 %x2, 200
%x3 = add nuw nsw i32 %x, 3
%c3 = icmp ult i32 %x3, 100
%x4 = add nuw nsw i32 %x, 20
%c4 = icmp ult i32 %x4, 100
%x5 = add i32 %x, 15
%c5 = icmp ult i32 %x5, 100
call void(i1, ...) @llvm.experimental.guard(i1 %c1) [ "deopt"(i64 1) ]
call void(i1, ...) @llvm.experimental.guard(i1 %c5) [ "deopt"(i64 5) ]
store i32 0, ptr %p
br i1 %c2, label %ok, label %out
ok:
call void(i1, ...) @llvm.experimental.guard(i1 %c4) [ "deopt"(i64 4) ]
call void(i1, ...) @llvm.experimental.guard(i1 %c3) [ "deopt"(i64 3) ]
br label %out
out:
ret void
}
; The test shows the bug in guard widening. Critical pieces.
; There is a %cond_1 check which provides the correctness of nuw nsw in %b.shift.
; %b.shift and %cond_2 are poisons and after guard widening it leads to UB
; for both arithmetic and logcal and.
define void @simple_case(i32 %a, i32 %b, i1 %cnd) {
; CHECK-LABEL: @simple_case(
; CHECK-NEXT: entry:
; CHECK-NEXT: [[B_GW_FR:%.*]] = freeze i32 [[B:%.*]]
; CHECK-NEXT: [[COND_0:%.*]] = icmp ult i32 [[A:%.*]], 10
; CHECK-NEXT: [[B_SHIFT:%.*]] = add i32 [[B_GW_FR]], 5
; CHECK-NEXT: [[COND_2:%.*]] = icmp ult i32 [[B_SHIFT]], 10
; CHECK-NEXT: [[WIDE_CHK:%.*]] = and i1 [[COND_0]], [[COND_2]]
; CHECK-NEXT: call void (i1, ...) @llvm.experimental.guard(i1 [[WIDE_CHK]]) [ "deopt"() ]
; CHECK-NEXT: br label [[LOOP:%.*]]
; CHECK: loop:
; CHECK-NEXT: [[COND_1:%.*]] = icmp ult i32 [[B_GW_FR]], 10
; CHECK-NEXT: br i1 [[COND_1]], label [[OK:%.*]], label [[LEAVE_LOOPEXIT:%.*]]
; CHECK: ok:
; CHECK-NEXT: br i1 [[CND:%.*]], label [[LOOP]], label [[LEAVE_LOOPEXIT]]
; CHECK: leave.loopexit:
; CHECK-NEXT: br label [[LEAVE:%.*]]
; CHECK: leave:
; CHECK-NEXT: ret void
;
entry:
%cond_0 = icmp ult i32 %a, 10
%b.shift = add nuw nsw i32 %b, 5
%cond_2 = icmp ult i32 %b.shift, 10
call void (i1, ...) @llvm.experimental.guard(i1 %cond_0) [ "deopt"() ]
br label %loop
loop:
%cond_1 = icmp ult i32 %b, 10
br i1 %cond_1, label %ok, label %leave.loopexit
ok:
call void (i1, ...) @llvm.experimental.guard(i1 %cond_2) [ "deopt"() ]
br i1 %cnd, label %loop, label %leave.loopexit
leave.loopexit:
br label %leave
leave:
ret void
}
declare ptr @fake_personality_function()
define void @case_with_invoke(i1 %c, i1 %gc) personality ptr @fake_personality_function {
; CHECK-LABEL: @case_with_invoke(
; CHECK-NEXT: entry:
; CHECK-NEXT: br i1 [[C:%.*]], label [[NORMAL:%.*]], label [[INVOK:%.*]]
; CHECK: invok:
; CHECK-NEXT: [[INVOKE_RESULT:%.*]] = invoke i1 @dummy()
; CHECK-NEXT: to label [[NORMAL]] unwind label [[EXCEPTION:%.*]]
; CHECK: normal:
; CHECK-NEXT: [[PHI_C:%.*]] = phi i1 [ true, [[ENTRY:%.*]] ], [ [[INVOKE_RESULT]], [[INVOK]] ]
; CHECK-NEXT: [[PHI_C_GW_FR:%.*]] = freeze i1 [[PHI_C]]
; CHECK-NEXT: [[WIDE_CHK:%.*]] = and i1 [[GC:%.*]], [[PHI_C_GW_FR]]
; CHECK-NEXT: call void (i1, ...) @llvm.experimental.guard(i1 [[WIDE_CHK]]) [ "deopt"() ]
; CHECK-NEXT: ret void
; CHECK: exception:
; CHECK-NEXT: [[LANDING_PAD:%.*]] = landingpad { ptr, i32 }
; CHECK-NEXT: cleanup
; CHECK-NEXT: ret void
;
entry:
br i1 %c, label %normal, label %invok
invok:
%invoke.result = invoke i1 @dummy() to label %normal unwind label %exception
normal:
%phi.c = phi i1 [true, %entry], [%invoke.result, %invok]
call void (i1, ...) @llvm.experimental.guard(i1 %gc) [ "deopt"() ]
call void (i1, ...) @llvm.experimental.guard(i1 %phi.c) [ "deopt"() ]
ret void
exception:
%landing_pad = landingpad { ptr, i32 } cleanup
ret void
}
define void @case_with_invoke_in_latch(i1 %c, i1 %gc) personality ptr @fake_personality_function {
; CHECK-LABEL: @case_with_invoke_in_latch(
; CHECK-NEXT: entry:
; CHECK-NEXT: br label [[HEADER:%.*]]
; CHECK: header:
; CHECK-NEXT: [[PHI_C:%.*]] = phi i1 [ false, [[ENTRY:%.*]] ], [ [[INVOKE_RESULT:%.*]], [[HEADER]] ]
; CHECK-NEXT: [[PHI_C_GW_FR:%.*]] = freeze i1 [[PHI_C]]
; CHECK-NEXT: [[WIDE_CHK:%.*]] = and i1 [[GC:%.*]], [[PHI_C_GW_FR]]
; CHECK-NEXT: call void (i1, ...) @llvm.experimental.guard(i1 [[WIDE_CHK]]) [ "deopt"() ]
; CHECK-NEXT: [[INVOKE_RESULT]] = invoke i1 @dummy()
; CHECK-NEXT: to label [[HEADER]] unwind label [[EXCEPTION:%.*]]
; CHECK: exception:
; CHECK-NEXT: [[LANDING_PAD:%.*]] = landingpad { ptr, i32 }
; CHECK-NEXT: cleanup
; CHECK-NEXT: ret void
;
entry:
br label %header
header:
%phi.c = phi i1 [false, %entry], [%invoke.result, %header]
call void (i1, ...) @llvm.experimental.guard(i1 %gc) [ "deopt"() ]
call void (i1, ...) @llvm.experimental.guard(i1 %phi.c) [ "deopt"() ]
%invoke.result = invoke i1 @dummy() to label %header unwind label %exception
exception:
%landing_pad = landingpad { ptr, i32 } cleanup
ret void
}
declare void @dummy_vec(<4 x i1> %arg)
define void @freeze_poison(i1 %c, i1 %g) {
; CHECK-LABEL: @freeze_poison(
; CHECK-NEXT: entry:
; CHECK-NEXT: [[DOTGW_FR:%.*]] = freeze i1 poison
; CHECK-NEXT: br i1 [[C:%.*]], label [[LEFT:%.*]], label [[RIGHT:%.*]]
; CHECK: left:
; CHECK-NEXT: call void @dummy_vec(<4 x i1> <i1 false, i1 poison, i1 poison, i1 poison>)
; CHECK-NEXT: ret void
; CHECK: right:
; CHECK-NEXT: [[WIDE_CHK:%.*]] = and i1 [[G:%.*]], [[DOTGW_FR]]
; CHECK-NEXT: call void (i1, ...) @llvm.experimental.guard(i1 [[WIDE_CHK]]) [ "deopt"() ]
; CHECK-NEXT: ret void
;
entry:
br i1 %c, label %left, label %right
left:
call void @dummy_vec(<4 x i1> <i1 0, i1 poison, i1 poison, i1 poison>)
ret void
right:
call void (i1, ...) @llvm.experimental.guard(i1 %g) [ "deopt"() ]
call void (i1, ...) @llvm.experimental.guard(i1 poison) [ "deopt"() ]
ret void
}