; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
; RUN: opt < %s -passes=unify-loop-exits -max-booleans-in-control-flow-hub=1 -S | FileCheck %s
; RUN: opt < %s -passes=unify-loop-exits -S | FileCheck --check-prefix=BOOLEAN %s
; A loop with multiple exit blocks.
define void @loop_two_exits(i1 %PredEntry, i1 %PredA) {
; CHECK-LABEL: @loop_two_exits(
; CHECK-NEXT: entry:
; CHECK-NEXT: br i1 [[PREDENTRY:%.*]], label [[A:%.*]], label [[E:%.*]]
; CHECK: A:
; CHECK-NEXT: [[INC1:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[INC2:%.*]], [[C:%.*]] ]
; CHECK-NEXT: br i1 [[PREDA:%.*]], label [[LOOP_EXIT_GUARD:%.*]], label [[C]]
; CHECK: B:
; CHECK-NEXT: tail call fastcc void @check(i32 1) #[[ATTR0:[0-9]+]]
; CHECK-NEXT: br label [[D:%.*]]
; CHECK: C:
; CHECK-NEXT: [[INC2]] = add i32 [[INC1]], 1
; CHECK-NEXT: [[CMP:%.*]] = icmp ult i32 [[INC2]], 10
; CHECK-NEXT: br i1 [[CMP]], label [[A]], label [[LOOP_EXIT_GUARD]]
; CHECK: D:
; CHECK-NEXT: unreachable
; CHECK: E:
; CHECK-NEXT: ret void
; CHECK: loop.exit.guard:
; CHECK-NEXT: [[MERGED_BB_IDX:%.*]] = phi i32 [ 0, [[A]] ], [ 1, [[C]] ]
; CHECK-NEXT: [[B_PREDICATE:%.*]] = icmp eq i32 [[MERGED_BB_IDX]], 0
; CHECK-NEXT: br i1 [[B_PREDICATE]], label [[B:%.*]], label [[E]]
;
; BOOLEAN-LABEL: @loop_two_exits(
; BOOLEAN-NEXT: entry:
; BOOLEAN-NEXT: br i1 [[PREDENTRY:%.*]], label [[A:%.*]], label [[E:%.*]]
; BOOLEAN: A:
; BOOLEAN-NEXT: [[INC1:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[INC2:%.*]], [[C:%.*]] ]
; BOOLEAN-NEXT: br i1 [[PREDA:%.*]], label [[LOOP_EXIT_GUARD:%.*]], label [[C]]
; BOOLEAN: B:
; BOOLEAN-NEXT: tail call fastcc void @check(i32 1) #[[ATTR0:[0-9]+]]
; BOOLEAN-NEXT: br label [[D:%.*]]
; BOOLEAN: C:
; BOOLEAN-NEXT: [[INC2]] = add i32 [[INC1]], 1
; BOOLEAN-NEXT: [[CMP:%.*]] = icmp ult i32 [[INC2]], 10
; BOOLEAN-NEXT: br i1 [[CMP]], label [[A]], label [[LOOP_EXIT_GUARD]]
; BOOLEAN: D:
; BOOLEAN-NEXT: unreachable
; BOOLEAN: E:
; BOOLEAN-NEXT: ret void
; BOOLEAN: loop.exit.guard:
; BOOLEAN-NEXT: [[GUARD_B:%.*]] = phi i1 [ true, [[A]] ], [ false, [[C]] ]
; BOOLEAN-NEXT: br i1 [[GUARD_B]], label [[B:%.*]], label [[E]]
;
entry:
br i1 %PredEntry, label %A, label %E
A:
%inc1 = phi i32 [ 0, %entry ], [ %inc2, %C ]
br i1 %PredA, label %B, label %C
B:
tail call fastcc void @check(i32 1) #0
br label %D
C:
%inc2 = add i32 %inc1, 1
%cmp = icmp ult i32 %inc2, 10
br i1 %cmp, label %A, label %E
D:
unreachable
E:
ret void
}
; The loop exit blocks appear in an inner loop.
define void @inner_loop(i1 %PredEntry, i1 %PredA, i1 %PredB) {
; CHECK-LABEL: @inner_loop(
; CHECK-NEXT: entry:
; CHECK-NEXT: br i1 [[PREDENTRY:%.*]], label [[A:%.*]], label [[I:%.*]]
; CHECK: A:
; CHECK-NEXT: [[OUTER1:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[OUTER2:%.*]], [[G:%.*]] ]
; CHECK-NEXT: br label [[B:%.*]]
; CHECK: B:
; CHECK-NEXT: [[INNER1:%.*]] = phi i32 [ 0, [[A]] ], [ [[INNER2:%.*]], [[F:%.*]] ]
; CHECK-NEXT: br i1 [[PREDA:%.*]], label [[D:%.*]], label [[LOOP_EXIT_GUARD2:%.*]]
; CHECK: C:
; CHECK-NEXT: tail call fastcc void @check(i32 1) #[[ATTR0]]
; CHECK-NEXT: br label [[H:%.*]]
; CHECK: D:
; CHECK-NEXT: br i1 [[PREDB:%.*]], label [[LOOP_EXIT_GUARD2]], label [[F]]
; CHECK: E:
; CHECK-NEXT: tail call fastcc void @check(i32 2) #[[ATTR0]]
; CHECK-NEXT: br label [[H]]
; CHECK: F:
; CHECK-NEXT: [[INNER2]] = add i32 [[INNER1]], 1
; CHECK-NEXT: [[CMP1:%.*]] = icmp ult i32 [[INNER2]], 20
; CHECK-NEXT: br i1 [[CMP1]], label [[B]], label [[LOOP_EXIT_GUARD2]]
; CHECK: G:
; CHECK-NEXT: [[OUTER2]] = add i32 [[OUTER1]], 1
; CHECK-NEXT: [[CMP2:%.*]] = icmp ult i32 [[OUTER2]], 10
; CHECK-NEXT: br i1 [[CMP2]], label [[A]], label [[LOOP_EXIT_GUARD:%.*]]
; CHECK: H:
; CHECK-NEXT: unreachable
; CHECK: I:
; CHECK-NEXT: ret void
; CHECK: loop.exit.guard:
; CHECK-NEXT: [[MERGED_BB_IDX:%.*]] = phi i32 [ 2, [[G]] ], [ [[MERGED_BB_IDX_MOVED:%.*]], [[LOOP_EXIT_GUARD2]] ]
; CHECK-NEXT: [[C_PREDICATE:%.*]] = icmp eq i32 [[MERGED_BB_IDX]], 0
; CHECK-NEXT: br i1 [[C_PREDICATE]], label [[C:%.*]], label [[LOOP_EXIT_GUARD1:%.*]]
; CHECK: loop.exit.guard1:
; CHECK-NEXT: [[E_PREDICATE:%.*]] = icmp eq i32 [[MERGED_BB_IDX]], 1
; CHECK-NEXT: br i1 [[E_PREDICATE]], label [[E:%.*]], label [[I]]
; CHECK: loop.exit.guard2:
; CHECK-NEXT: [[MERGED_BB_IDX_MOVED]] = phi i32 [ 0, [[B]] ], [ 1, [[D]] ], [ poison, [[F]] ]
; CHECK-NEXT: [[MERGED_BB_IDX3:%.*]] = phi i32 [ 0, [[B]] ], [ 0, [[D]] ], [ 1, [[F]] ]
; CHECK-NEXT: [[LOOP_EXIT_GUARD_PREDICATE:%.*]] = icmp eq i32 [[MERGED_BB_IDX3]], 0
; CHECK-NEXT: br i1 [[LOOP_EXIT_GUARD_PREDICATE]], label [[LOOP_EXIT_GUARD]], label [[G]]
;
; BOOLEAN-LABEL: @inner_loop(
; BOOLEAN-NEXT: entry:
; BOOLEAN-NEXT: br i1 [[PREDENTRY:%.*]], label [[A:%.*]], label [[I:%.*]]
; BOOLEAN: A:
; BOOLEAN-NEXT: [[OUTER1:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[OUTER2:%.*]], [[G:%.*]] ]
; BOOLEAN-NEXT: br label [[B:%.*]]
; BOOLEAN: B:
; BOOLEAN-NEXT: [[INNER1:%.*]] = phi i32 [ 0, [[A]] ], [ [[INNER2:%.*]], [[F:%.*]] ]
; BOOLEAN-NEXT: br i1 [[PREDA:%.*]], label [[D:%.*]], label [[LOOP_EXIT_GUARD2:%.*]]
; BOOLEAN: C:
; BOOLEAN-NEXT: tail call fastcc void @check(i32 1) #[[ATTR0]]
; BOOLEAN-NEXT: br label [[H:%.*]]
; BOOLEAN: D:
; BOOLEAN-NEXT: br i1 [[PREDB:%.*]], label [[LOOP_EXIT_GUARD2]], label [[F]]
; BOOLEAN: E:
; BOOLEAN-NEXT: tail call fastcc void @check(i32 2) #[[ATTR0]]
; BOOLEAN-NEXT: br label [[H]]
; BOOLEAN: F:
; BOOLEAN-NEXT: [[INNER2]] = add i32 [[INNER1]], 1
; BOOLEAN-NEXT: [[CMP1:%.*]] = icmp ult i32 [[INNER2]], 20
; BOOLEAN-NEXT: br i1 [[CMP1]], label [[B]], label [[LOOP_EXIT_GUARD2]]
; BOOLEAN: G:
; BOOLEAN-NEXT: [[OUTER2]] = add i32 [[OUTER1]], 1
; BOOLEAN-NEXT: [[CMP2:%.*]] = icmp ult i32 [[OUTER2]], 10
; BOOLEAN-NEXT: br i1 [[CMP2]], label [[A]], label [[LOOP_EXIT_GUARD:%.*]]
; BOOLEAN: H:
; BOOLEAN-NEXT: unreachable
; BOOLEAN: I:
; BOOLEAN-NEXT: ret void
; BOOLEAN: loop.exit.guard:
; BOOLEAN-NEXT: [[GUARD_C:%.*]] = phi i1 [ false, [[G]] ], [ [[GUARD_C_MOVED:%.*]], [[LOOP_EXIT_GUARD2]] ]
; BOOLEAN-NEXT: [[GUARD_E:%.*]] = phi i1 [ false, [[G]] ], [ [[GUARD_E_MOVED:%.*]], [[LOOP_EXIT_GUARD2]] ]
; BOOLEAN-NEXT: br i1 [[GUARD_C]], label [[C:%.*]], label [[LOOP_EXIT_GUARD1:%.*]]
; BOOLEAN: loop.exit.guard1:
; BOOLEAN-NEXT: br i1 [[GUARD_E]], label [[E:%.*]], label [[I]]
; BOOLEAN: loop.exit.guard2:
; BOOLEAN-NEXT: [[GUARD_E_MOVED]] = phi i1 [ false, [[B]] ], [ true, [[D]] ], [ poison, [[F]] ]
; BOOLEAN-NEXT: [[GUARD_C_MOVED]] = phi i1 [ true, [[B]] ], [ false, [[D]] ], [ poison, [[F]] ]
; BOOLEAN-NEXT: [[GUARD_LOOP_EXIT_GUARD:%.*]] = phi i1 [ true, [[B]] ], [ true, [[D]] ], [ false, [[F]] ]
; BOOLEAN-NEXT: br i1 [[GUARD_LOOP_EXIT_GUARD]], label [[LOOP_EXIT_GUARD]], label [[G]]
;
entry:
br i1 %PredEntry, label %A, label %I
A:
%outer1 = phi i32 [ 0, %entry ], [ %outer2, %G ]
br label %B
B:
%inner1 = phi i32 [ 0, %A ], [ %inner2, %F ]
br i1 %PredA, label %D, label %C
C:
tail call fastcc void @check(i32 1) #0
br label %H
D:
br i1 %PredB, label %E, label %F
E:
tail call fastcc void @check(i32 2) #0
br label %H
F:
%inner2 = add i32 %inner1, 1
%cmp1 = icmp ult i32 %inner2, 20
br i1 %cmp1, label %B, label %G
G:
%outer2 = add i32 %outer1, 1
%cmp2 = icmp ult i32 %outer2, 10
br i1 %cmp2, label %A, label %I
H:
unreachable
I:
ret void
}
; A loop with more exit blocks.
define void @loop_five_exits(i1 %PredEntry, i1 %PredA, i1 %PredB, i1 %PredC, i1 %PredD) {
; CHECK-LABEL: @loop_five_exits(
; CHECK-NEXT: entry:
; CHECK-NEXT: br i1 [[PREDENTRY:%.*]], label [[A:%.*]], label [[L:%.*]]
; CHECK: A:
; CHECK-NEXT: [[INC1:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[INC2:%.*]], [[I:%.*]] ]
; CHECK-NEXT: br i1 [[PREDA:%.*]], label [[LOOP_EXIT_GUARD:%.*]], label [[C:%.*]]
; CHECK: B:
; CHECK-NEXT: tail call fastcc void @check(i32 1) #[[ATTR0]]
; CHECK-NEXT: br label [[J:%.*]]
; CHECK: C:
; CHECK-NEXT: br i1 [[PREDB:%.*]], label [[LOOP_EXIT_GUARD]], label [[E:%.*]]
; CHECK: D:
; CHECK-NEXT: tail call fastcc void @check(i32 2) #[[ATTR0]]
; CHECK-NEXT: br label [[J]]
; CHECK: E:
; CHECK-NEXT: br i1 [[PREDC:%.*]], label [[LOOP_EXIT_GUARD]], label [[G:%.*]]
; CHECK: F:
; CHECK-NEXT: tail call fastcc void @check(i32 3) #[[ATTR0]]
; CHECK-NEXT: br label [[K:%.*]]
; CHECK: G:
; CHECK-NEXT: br i1 [[PREDD:%.*]], label [[LOOP_EXIT_GUARD]], label [[I]]
; CHECK: H:
; CHECK-NEXT: tail call fastcc void @check(i32 4) #[[ATTR0]]
; CHECK-NEXT: br label [[K]]
; CHECK: I:
; CHECK-NEXT: [[INC2]] = add i32 [[INC1]], 1
; CHECK-NEXT: [[CMP:%.*]] = icmp ult i32 [[INC2]], 10
; CHECK-NEXT: br i1 [[CMP]], label [[A]], label [[LOOP_EXIT_GUARD]]
; CHECK: J:
; CHECK-NEXT: br label [[L]]
; CHECK: K:
; CHECK-NEXT: br label [[L]]
; CHECK: L:
; CHECK-NEXT: ret void
; CHECK: loop.exit.guard:
; CHECK-NEXT: [[MERGED_BB_IDX:%.*]] = phi i32 [ 0, [[A]] ], [ 1, [[C]] ], [ 2, [[E]] ], [ 3, [[G]] ], [ 4, [[I]] ]
; CHECK-NEXT: [[B_PREDICATE:%.*]] = icmp eq i32 [[MERGED_BB_IDX]], 0
; CHECK-NEXT: br i1 [[B_PREDICATE]], label [[B:%.*]], label [[LOOP_EXIT_GUARD1:%.*]]
; CHECK: loop.exit.guard1:
; CHECK-NEXT: [[D_PREDICATE:%.*]] = icmp eq i32 [[MERGED_BB_IDX]], 1
; CHECK-NEXT: br i1 [[D_PREDICATE]], label [[D:%.*]], label [[LOOP_EXIT_GUARD2:%.*]]
; CHECK: loop.exit.guard2:
; CHECK-NEXT: [[F_PREDICATE:%.*]] = icmp eq i32 [[MERGED_BB_IDX]], 2
; CHECK-NEXT: br i1 [[F_PREDICATE]], label [[F:%.*]], label [[LOOP_EXIT_GUARD3:%.*]]
; CHECK: loop.exit.guard3:
; CHECK-NEXT: [[H_PREDICATE:%.*]] = icmp eq i32 [[MERGED_BB_IDX]], 3
; CHECK-NEXT: br i1 [[H_PREDICATE]], label [[H:%.*]], label [[L]]
;
; BOOLEAN-LABEL: @loop_five_exits(
; BOOLEAN-NEXT: entry:
; BOOLEAN-NEXT: br i1 [[PREDENTRY:%.*]], label [[A:%.*]], label [[L:%.*]]
; BOOLEAN: A:
; BOOLEAN-NEXT: [[INC1:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[INC2:%.*]], [[I:%.*]] ]
; BOOLEAN-NEXT: br i1 [[PREDA:%.*]], label [[LOOP_EXIT_GUARD:%.*]], label [[C:%.*]]
; BOOLEAN: B:
; BOOLEAN-NEXT: tail call fastcc void @check(i32 1) #[[ATTR0]]
; BOOLEAN-NEXT: br label [[J:%.*]]
; BOOLEAN: C:
; BOOLEAN-NEXT: br i1 [[PREDB:%.*]], label [[LOOP_EXIT_GUARD]], label [[E:%.*]]
; BOOLEAN: D:
; BOOLEAN-NEXT: tail call fastcc void @check(i32 2) #[[ATTR0]]
; BOOLEAN-NEXT: br label [[J]]
; BOOLEAN: E:
; BOOLEAN-NEXT: br i1 [[PREDC:%.*]], label [[LOOP_EXIT_GUARD]], label [[G:%.*]]
; BOOLEAN: F:
; BOOLEAN-NEXT: tail call fastcc void @check(i32 3) #[[ATTR0]]
; BOOLEAN-NEXT: br label [[K:%.*]]
; BOOLEAN: G:
; BOOLEAN-NEXT: br i1 [[PREDD:%.*]], label [[LOOP_EXIT_GUARD]], label [[I]]
; BOOLEAN: H:
; BOOLEAN-NEXT: tail call fastcc void @check(i32 4) #[[ATTR0]]
; BOOLEAN-NEXT: br label [[K]]
; BOOLEAN: I:
; BOOLEAN-NEXT: [[INC2]] = add i32 [[INC1]], 1
; BOOLEAN-NEXT: [[CMP:%.*]] = icmp ult i32 [[INC2]], 10
; BOOLEAN-NEXT: br i1 [[CMP]], label [[A]], label [[LOOP_EXIT_GUARD]]
; BOOLEAN: J:
; BOOLEAN-NEXT: br label [[L]]
; BOOLEAN: K:
; BOOLEAN-NEXT: br label [[L]]
; BOOLEAN: L:
; BOOLEAN-NEXT: ret void
; BOOLEAN: loop.exit.guard:
; BOOLEAN-NEXT: [[GUARD_B:%.*]] = phi i1 [ true, [[A]] ], [ false, [[C]] ], [ false, [[E]] ], [ false, [[G]] ], [ false, [[I]] ]
; BOOLEAN-NEXT: [[GUARD_D:%.*]] = phi i1 [ false, [[A]] ], [ true, [[C]] ], [ false, [[E]] ], [ false, [[G]] ], [ false, [[I]] ]
; BOOLEAN-NEXT: [[GUARD_F:%.*]] = phi i1 [ false, [[A]] ], [ false, [[C]] ], [ true, [[E]] ], [ false, [[G]] ], [ false, [[I]] ]
; BOOLEAN-NEXT: [[GUARD_H:%.*]] = phi i1 [ false, [[A]] ], [ false, [[C]] ], [ false, [[E]] ], [ true, [[G]] ], [ false, [[I]] ]
; BOOLEAN-NEXT: br i1 [[GUARD_B]], label [[B:%.*]], label [[LOOP_EXIT_GUARD1:%.*]]
; BOOLEAN: loop.exit.guard1:
; BOOLEAN-NEXT: br i1 [[GUARD_D]], label [[D:%.*]], label [[LOOP_EXIT_GUARD2:%.*]]
; BOOLEAN: loop.exit.guard2:
; BOOLEAN-NEXT: br i1 [[GUARD_F]], label [[F:%.*]], label [[LOOP_EXIT_GUARD3:%.*]]
; BOOLEAN: loop.exit.guard3:
; BOOLEAN-NEXT: br i1 [[GUARD_H]], label [[H:%.*]], label [[L]]
;
entry:
br i1 %PredEntry, label %A, label %L
A:
%inc1 = phi i32 [ 0, %entry ], [ %inc2, %I ]
br i1 %PredA, label %B, label %C
B:
tail call fastcc void @check(i32 1) #0
br label %J
C:
br i1 %PredB, label %D, label %E
D:
tail call fastcc void @check(i32 2) #0
br label %J
E:
br i1 %PredC, label %F, label %G
F:
tail call fastcc void @check(i32 3) #0
br label %K
G:
br i1 %PredD, label %H, label %I
H:
tail call fastcc void @check(i32 4) #0
br label %K
I:
%inc2 = add i32 %inc1, 1
%cmp = icmp ult i32 %inc2, 10
br i1 %cmp, label %A, label %L
J:
br label %L
K:
br label %L
L:
ret void
}
declare void @check(i32 noundef %i) #0
attributes #0 = { noreturn nounwind }