implicit-null-checks.ll 11.5 KB
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
; RUN: opt -enable-nontrivial-unswitch=true -simple-loop-unswitch -S < %s | FileCheck %s
; RUN: opt -enable-nontrivial-unswitch=true -passes='loop(unswitch),verify<loops>' -S < %s | FileCheck %s

declare void @may_exit()
declare void @throw_npe()

; It is illegal to preserve make_implicit notion of the condition being
; unswitched because we may exit loop before we reach the condition, so
; there is no guarantee that following implicit branch always means getting
; to throw_npe block.
define i32 @test_should_drop_make_implicit(i32* %p1, i32* %p2) {
; CHECK-LABEL: @test_should_drop_make_implicit(
; CHECK-NEXT:  entry:
; CHECK-NEXT:    [[NULL_CHECK:%.*]] = icmp eq i32* [[P2:%.*]], null
; CHECK-NOT:     !make.implicit
; CHECK-NEXT:    br i1 [[NULL_CHECK]], label [[ENTRY_SPLIT_US:%.*]], label [[ENTRY_SPLIT:%.*]]
; CHECK:       entry.split.us:
; CHECK-NEXT:    br label [[LOOP_US:%.*]]
; CHECK:       loop.us:
; CHECK-NEXT:    [[IV_US:%.*]] = phi i32 [ 0, [[ENTRY_SPLIT_US]] ]
; CHECK-NEXT:    [[X_US:%.*]] = load i32, i32* [[P1:%.*]], align 4
; CHECK-NEXT:    [[SIDE_EXIT_COND_US:%.*]] = icmp eq i32 [[X_US]], 0
; CHECK-NEXT:    br i1 [[SIDE_EXIT_COND_US]], label [[SIDE_EXIT_SPLIT_US:%.*]], label [[NULL_CHECK_BLOCK_US:%.*]]
; CHECK:       null_check_block.us:
; CHECK-NEXT:    br label [[THROW_NPE_SPLIT_US:%.*]]
; CHECK:       side_exit.split.us:
; CHECK-NEXT:    br label [[SIDE_EXIT:%.*]]
; CHECK:       throw_npe.split.us:
; CHECK-NEXT:    br label [[THROW_NPE:%.*]]
; CHECK:       entry.split:
; CHECK-NEXT:    br label [[LOOP:%.*]]
; CHECK:       loop:
; CHECK-NEXT:    [[IV:%.*]] = phi i32 [ 0, [[ENTRY_SPLIT]] ], [ [[IV_NEXT:%.*]], [[BACKEDGE:%.*]] ]
; CHECK-NEXT:    [[X:%.*]] = load i32, i32* [[P1]], align 4
; CHECK-NEXT:    [[SIDE_EXIT_COND:%.*]] = icmp eq i32 [[X]], 0
; CHECK-NEXT:    br i1 [[SIDE_EXIT_COND]], label [[SIDE_EXIT_SPLIT:%.*]], label [[NULL_CHECK_BLOCK:%.*]]
; CHECK:       null_check_block:
; CHECK-NEXT:    br label [[BACKEDGE]]
; CHECK:       backedge:
; CHECK-NEXT:    [[IV_NEXT]] = add i32 [[IV]], 1
; CHECK-NEXT:    [[LOOP_COND:%.*]] = icmp slt i32 [[IV_NEXT]], 10000
; CHECK-NEXT:    br i1 [[LOOP_COND]], label [[LOOP]], label [[EXIT:%.*]]
; CHECK:       side_exit.split:
; CHECK-NEXT:    br label [[SIDE_EXIT]]
; CHECK:       side_exit:
; CHECK-NEXT:    ret i32 0
; CHECK:       throw_npe:
; CHECK-NEXT:    call void @throw_npe()
; CHECK-NEXT:    unreachable
; CHECK:       exit:
; CHECK-NEXT:    [[X_LCSSA2:%.*]] = phi i32 [ [[X]], [[BACKEDGE]] ]
; CHECK-NEXT:    ret i32 [[X_LCSSA2]]
;
entry:
  %null_check = icmp eq i32* %p2, null
  br label %loop
loop:
  %iv = phi i32 [0, %entry], [%iv.next, %backedge]
  %x = load i32, i32* %p1
  %side_exit_cond = icmp eq i32 %x, 0
  br i1 %side_exit_cond, label %side_exit, label %null_check_block

null_check_block:
  br i1 %null_check, label %throw_npe, label %backedge, !make.implicit !0

backedge:
  %iv.next = add i32 %iv,1
  %loop_cond = icmp slt i32 %iv.next, 10000
  br i1 %loop_cond, label %loop, label %exit

side_exit:
  ret i32 0

throw_npe:
  call void @throw_npe()
  unreachable

exit:
  ret i32 %x
}

; Here make.implicit notion may be preserved because we always get to throw_npe
; after following true branch. This is a trivial unswitch.
define i32 @test_may_keep_make_implicit_trivial(i32* %p1, i32* %p2) {
; CHECK-LABEL: @test_may_keep_make_implicit_trivial(
; CHECK-NEXT:  entry:
; CHECK-NEXT:    [[NULL_CHECK:%.*]] = icmp eq i32* [[P2:%.*]], null
; CHECK-NEXT:    br i1 [[NULL_CHECK]], label [[THROW_NPE:%.*]], label [[ENTRY_SPLIT:%.*]], !make.implicit !0
; CHECK:       entry.split:
; CHECK-NEXT:    br label [[LOOP:%.*]]
; CHECK:       loop:
; CHECK-NEXT:    [[IV:%.*]] = phi i32 [ 0, [[ENTRY_SPLIT]] ], [ [[IV_NEXT:%.*]], [[BACKEDGE:%.*]] ]
; CHECK-NEXT:    [[X:%.*]] = load i32, i32* [[P1:%.*]], align 4
; CHECK-NEXT:    [[SIDE_EXIT_COND:%.*]] = icmp eq i32 [[X]], 0
; CHECK-NEXT:    br label [[SIDE_EXIT_BLOCK:%.*]]
; CHECK:       side_exit_block:
; CHECK-NEXT:    br i1 [[SIDE_EXIT_COND]], label [[SIDE_EXIT:%.*]], label [[BACKEDGE]]
; CHECK:       backedge:
; CHECK-NEXT:    [[IV_NEXT]] = add i32 [[IV]], 1
; CHECK-NEXT:    [[LOOP_COND:%.*]] = icmp slt i32 [[IV_NEXT]], 10000
; CHECK-NEXT:    br i1 [[LOOP_COND]], label [[LOOP]], label [[EXIT:%.*]]
; CHECK:       side_exit:
; CHECK-NEXT:    ret i32 0
; CHECK:       throw_npe:
; CHECK-NEXT:    call void @throw_npe()
; CHECK-NEXT:    unreachable
; CHECK:       exit:
; CHECK-NEXT:    [[X_LCSSA2:%.*]] = phi i32 [ [[X]], [[BACKEDGE]] ]
; CHECK-NEXT:    ret i32 [[X_LCSSA2]]
;
entry:
  %null_check = icmp eq i32* %p2, null
  br label %loop
loop:
  %iv = phi i32 [0, %entry], [%iv.next, %backedge]
  %x = load i32, i32* %p1
  %side_exit_cond = icmp eq i32 %x, 0
  br i1 %null_check, label %throw_npe, label %side_exit_block, !make.implicit !0

side_exit_block:
  br i1 %side_exit_cond, label %side_exit, label %backedge

backedge:
  %iv.next = add i32 %iv,1
  %loop_cond = icmp slt i32 %iv.next, 10000
  br i1 %loop_cond, label %loop, label %exit

side_exit:
  ret i32 0

throw_npe:
  call void @throw_npe()
  unreachable

exit:
  ret i32 %x
}

define i32 @test_may_keep_make_implicit_non_trivial(i32* %p1, i32* %p2) {
; CHECK-LABEL: @test_may_keep_make_implicit_non_trivial(
; CHECK-NEXT:  entry:
; CHECK-NEXT:    [[NULL_CHECK:%.*]] = icmp eq i32* [[P2:%.*]], null
; CHECK-NEXT:    br i1 [[NULL_CHECK]], label [[ENTRY_SPLIT_US:%.*]], label [[ENTRY_SPLIT:%.*]], !make.implicit !0
; CHECK:       entry.split.us:
; CHECK-NEXT:    br label [[LOOP_US:%.*]]
; CHECK:       loop.us:
; CHECK-NEXT:    [[IV_US:%.*]] = phi i32 [ 0, [[ENTRY_SPLIT_US]] ]
; CHECK-NEXT:    [[X_US:%.*]] = load i32, i32* [[P1:%.*]], align 4
; CHECK-NEXT:    [[INNER_BLOCK_COND_US:%.*]] = icmp eq i32 [[X_US]], 0
; CHECK-NEXT:    br i1 [[INNER_BLOCK_COND_US]], label [[INNER_BLOCK_US:%.*]], label [[NULL_CHECK_BLOCK_US:%.*]]
; CHECK:       inner_block.us:
; CHECK-NEXT:    br label [[NULL_CHECK_BLOCK_US]]
; CHECK:       null_check_block.us:
; CHECK-NEXT:    br label [[THROW_NPE_SPLIT_US:%.*]]
; CHECK:       throw_npe.split.us:
; CHECK-NEXT:    br label [[THROW_NPE:%.*]]
; CHECK:       entry.split:
; CHECK-NEXT:    br label [[LOOP:%.*]]
; CHECK:       loop:
; CHECK-NEXT:    [[IV:%.*]] = phi i32 [ 0, [[ENTRY_SPLIT]] ], [ [[IV_NEXT:%.*]], [[BACKEDGE:%.*]] ]
; CHECK-NEXT:    [[X:%.*]] = load i32, i32* [[P1]], align 4
; CHECK-NEXT:    [[INNER_BLOCK_COND:%.*]] = icmp eq i32 [[X]], 0
; CHECK-NEXT:    br i1 [[INNER_BLOCK_COND]], label [[INNER_BLOCK:%.*]], label [[NULL_CHECK_BLOCK:%.*]]
; CHECK:       inner_block:
; CHECK-NEXT:    br label [[NULL_CHECK_BLOCK]]
; CHECK:       null_check_block:
; CHECK-NEXT:    br label [[BACKEDGE]]
; CHECK:       backedge:
; CHECK-NEXT:    [[IV_NEXT]] = add i32 [[IV]], 1
; CHECK-NEXT:    [[LOOP_COND:%.*]] = icmp slt i32 [[IV_NEXT]], 10000
; CHECK-NEXT:    br i1 [[LOOP_COND]], label [[LOOP]], label [[EXIT:%.*]]
; CHECK:       throw_npe:
; CHECK-NEXT:    call void @throw_npe()
; CHECK-NEXT:    unreachable
; CHECK:       exit:
; CHECK-NEXT:    [[X_LCSSA1:%.*]] = phi i32 [ [[X]], [[BACKEDGE]] ]
; CHECK-NEXT:    ret i32 [[X_LCSSA1]]
;
entry:
  %null_check = icmp eq i32* %p2, null
  br label %loop
loop:
  %iv = phi i32 [0, %entry], [%iv.next, %backedge]
  %x = load i32, i32* %p1
  %inner_block_cond = icmp eq i32 %x, 0
  br i1 %inner_block_cond, label %inner_block, label %null_check_block

inner_block:
  br label %null_check_block

null_check_block:
  br i1 %null_check, label %throw_npe, label %backedge, !make.implicit !0

backedge:
  %iv.next = add i32 %iv,1
  %loop_cond = icmp slt i32 %iv.next, 10000
  br i1 %loop_cond, label %loop, label %exit

throw_npe:
  call void @throw_npe()
  unreachable

exit:
  ret i32 %x
}

; Here make.implicit notion should be dropped because of exiting call.
define i32 @test_should_drop_make_implicit_exiting_call(i32* %p1, i32* %p2) {
; CHECK-LABEL: @test_should_drop_make_implicit_exiting_call(
; CHECK-NEXT:  entry:
; CHECK-NEXT:    [[NULL_CHECK:%.*]] = icmp eq i32* [[P2:%.*]], null
; CHECK-NOT:     !make.implicit
; CHECK-NEXT:    br i1 [[NULL_CHECK]], label [[ENTRY_SPLIT_US:%.*]], label [[ENTRY_SPLIT:%.*]]
; CHECK:       entry.split.us:
; CHECK-NEXT:    br label [[LOOP_US:%.*]]
; CHECK:       loop.us:
; CHECK-NEXT:    [[IV_US:%.*]] = phi i32 [ 0, [[ENTRY_SPLIT_US]] ]
; CHECK-NEXT:    call void @may_exit()
; CHECK-NEXT:    [[X_US:%.*]] = load i32, i32* [[P1:%.*]], align 4
; CHECK-NEXT:    [[SIDE_EXIT_COND_US:%.*]] = icmp eq i32 [[X_US]], 0
; CHECK-NEXT:    br label [[THROW_NPE_SPLIT_US:%.*]]
; CHECK:       throw_npe.split.us:
; CHECK-NEXT:    br label [[THROW_NPE:%.*]]
; CHECK:       entry.split:
; CHECK-NEXT:    br label [[LOOP:%.*]]
; CHECK:       loop:
; CHECK-NEXT:    [[IV:%.*]] = phi i32 [ 0, [[ENTRY_SPLIT]] ], [ [[IV_NEXT:%.*]], [[BACKEDGE:%.*]] ]
; CHECK-NEXT:    call void @may_exit()
; CHECK-NEXT:    [[X:%.*]] = load i32, i32* [[P1]], align 4
; CHECK-NEXT:    [[SIDE_EXIT_COND:%.*]] = icmp eq i32 [[X]], 0
; CHECK-NEXT:    br label [[BACKEDGE]]
; CHECK:       backedge:
; CHECK-NEXT:    [[IV_NEXT]] = add i32 [[IV]], 1
; CHECK-NEXT:    [[LOOP_COND:%.*]] = icmp slt i32 [[IV_NEXT]], 10000
; CHECK-NEXT:    br i1 [[LOOP_COND]], label [[LOOP]], label [[EXIT:%.*]]
; CHECK:       throw_npe:
; CHECK-NEXT:    call void @throw_npe()
; CHECK-NEXT:    unreachable
; CHECK:       exit:
; CHECK-NEXT:    [[X_LCSSA1:%.*]] = phi i32 [ [[X]], [[BACKEDGE]] ]
; CHECK-NEXT:    ret i32 [[X_LCSSA1]]
;
entry:
  %null_check = icmp eq i32* %p2, null
  br label %loop
loop:
  %iv = phi i32 [0, %entry], [%iv.next, %backedge]
  call void @may_exit()
  %x = load i32, i32* %p1
  %side_exit_cond = icmp eq i32 %x, 0
  br i1 %null_check, label %throw_npe, label %backedge, !make.implicit !0

backedge:
  %iv.next = add i32 %iv,1
  %loop_cond = icmp slt i32 %iv.next, 10000
  br i1 %loop_cond, label %loop, label %exit

throw_npe:
  call void @throw_npe()
  unreachable

exit:
  ret i32 %x
}

; Here exiting call goes after the null check, so make.implicit may be preserved.
define i32 @test_may_keep_make_implicit_exiting_call(i32* %p1, i32* %p2) {
; CHECK-LABEL: @test_may_keep_make_implicit_exiting_call(
; CHECK-NEXT:  entry:
; CHECK-NEXT:    [[NULL_CHECK:%.*]] = icmp eq i32* [[P2:%.*]], null
; CHECK-NEXT:    br i1 [[NULL_CHECK]], label [[THROW_NPE:%.*]], label [[ENTRY_SPLIT:%.*]], !make.implicit !0
; CHECK:       entry.split:
; CHECK-NEXT:    br label [[LOOP:%.*]]
; CHECK:       loop:
; CHECK-NEXT:    [[IV:%.*]] = phi i32 [ 0, [[ENTRY_SPLIT]] ], [ [[IV_NEXT:%.*]], [[BACKEDGE:%.*]] ]
; CHECK-NEXT:    [[X:%.*]] = load i32, i32* [[P1:%.*]], align 4
; CHECK-NEXT:    [[SIDE_EXIT_COND:%.*]] = icmp eq i32 [[X]], 0
; CHECK-NEXT:    br label [[BACKEDGE]]
; CHECK:       backedge:
; CHECK-NEXT:    [[IV_NEXT]] = add i32 [[IV]], 1
; CHECK-NEXT:    [[LOOP_COND:%.*]] = icmp slt i32 [[IV_NEXT]], 10000
; CHECK-NEXT:    call void @may_exit()
; CHECK-NEXT:    br i1 [[LOOP_COND]], label [[LOOP]], label [[EXIT:%.*]]
; CHECK:       throw_npe:
; CHECK-NEXT:    call void @throw_npe()
; CHECK-NEXT:    unreachable
; CHECK:       exit:
; CHECK-NEXT:    [[X_LCSSA1:%.*]] = phi i32 [ [[X]], [[BACKEDGE]] ]
; CHECK-NEXT:    ret i32 [[X_LCSSA1]]
;
entry:
  %null_check = icmp eq i32* %p2, null
  br label %loop
loop:
  %iv = phi i32 [0, %entry], [%iv.next, %backedge]
  %x = load i32, i32* %p1
  %side_exit_cond = icmp eq i32 %x, 0
  br i1 %null_check, label %throw_npe, label %backedge, !make.implicit !0

backedge:
  %iv.next = add i32 %iv,1
  %loop_cond = icmp slt i32 %iv.next, 10000
  call void @may_exit()
  br i1 %loop_cond, label %loop, label %exit

throw_npe:
  call void @throw_npe()
  unreachable

exit:
  ret i32 %x
}

!0 = !{}