Skip to content

Commit 2dfb1c6

Browse files
authored
[VPlan] Try to hoist Previous (and operands), if sinking fails for FORs. (#108945)
In some cases, Previous (and its operands) can be hoisted. This allows supporting additional cases where sinking of all users of to FOR fails, e.g. due having to sink recipes with side-effects. This fixes a crash where we fail to create a scalar VPlan for a first-order recurrence, but can create a vector VPlan, because the trunc instruction of an IV which generates the previous value of the recurrence has been optimized to a truncated induction recipe, thus hoisting it to the beginning. Fixes #106523. PR: #108945
1 parent 2002533 commit 2dfb1c6

File tree

5 files changed

+331
-30
lines changed

5 files changed

+331
-30
lines changed

llvm/lib/Transforms/Vectorize/VPlanTransforms.cpp

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,105 @@ sinkRecurrenceUsersAfterPrevious(VPFirstOrderRecurrencePHIRecipe *FOR,
772772
return true;
773773
}
774774

775+
/// Try to hoist \p Previous and its operands before all users of \p FOR.
776+
static bool hoistPreviousBeforeFORUsers(VPFirstOrderRecurrencePHIRecipe *FOR,
777+
VPRecipeBase *Previous,
778+
VPDominatorTree &VPDT) {
779+
if (Previous->mayHaveSideEffects() || Previous->mayReadFromMemory())
780+
return false;
781+
782+
// Collect recipes that need hoisting.
783+
SmallVector<VPRecipeBase *> HoistCandidates;
784+
SmallPtrSet<VPRecipeBase *, 8> Visited;
785+
VPRecipeBase *HoistPoint = nullptr;
786+
// Find the closest hoist point by looking at all users of FOR and selecting
787+
// the recipe dominating all other users.
788+
for (VPUser *U : FOR->users()) {
789+
auto *R = dyn_cast<VPRecipeBase>(U);
790+
if (!R)
791+
continue;
792+
if (!HoistPoint || VPDT.properlyDominates(R, HoistPoint))
793+
HoistPoint = R;
794+
}
795+
assert(all_of(FOR->users(),
796+
[&VPDT, HoistPoint](VPUser *U) {
797+
auto *R = dyn_cast<VPRecipeBase>(U);
798+
return !R || HoistPoint == R ||
799+
VPDT.properlyDominates(HoistPoint, R);
800+
}) &&
801+
"HoistPoint must dominate all users of FOR");
802+
803+
auto NeedsHoisting = [HoistPoint, &VPDT,
804+
&Visited](VPValue *HoistCandidateV) -> VPRecipeBase * {
805+
VPRecipeBase *HoistCandidate = HoistCandidateV->getDefiningRecipe();
806+
if (!HoistCandidate)
807+
return nullptr;
808+
VPRegionBlock *EnclosingLoopRegion =
809+
HoistCandidate->getParent()->getEnclosingLoopRegion();
810+
assert((!HoistCandidate->getParent()->getParent() ||
811+
HoistCandidate->getParent()->getParent() == EnclosingLoopRegion) &&
812+
"CFG in VPlan should still be flat, without replicate regions");
813+
// Hoist candidate was already visited, no need to hoist.
814+
if (!Visited.insert(HoistCandidate).second)
815+
return nullptr;
816+
817+
// Candidate is outside loop region or a header phi, dominates FOR users w/o
818+
// hoisting.
819+
if (!EnclosingLoopRegion || isa<VPHeaderPHIRecipe>(HoistCandidate))
820+
return nullptr;
821+
822+
// If we reached a recipe that dominates HoistPoint, we don't need to
823+
// hoist the recipe.
824+
if (VPDT.properlyDominates(HoistCandidate, HoistPoint))
825+
return nullptr;
826+
return HoistCandidate;
827+
};
828+
auto CanHoist = [&](VPRecipeBase *HoistCandidate) {
829+
// Avoid hoisting candidates with side-effects, as we do not yet analyze
830+
// associated dependencies.
831+
return !HoistCandidate->mayHaveSideEffects();
832+
};
833+
834+
if (!NeedsHoisting(Previous->getVPSingleValue()))
835+
return true;
836+
837+
// Recursively try to hoist Previous and its operands before all users of FOR.
838+
HoistCandidates.push_back(Previous);
839+
840+
for (unsigned I = 0; I != HoistCandidates.size(); ++I) {
841+
VPRecipeBase *Current = HoistCandidates[I];
842+
assert(Current->getNumDefinedValues() == 1 &&
843+
"only recipes with a single defined value expected");
844+
if (!CanHoist(Current))
845+
return false;
846+
847+
for (VPValue *Op : Current->operands()) {
848+
// If we reach FOR, it means the original Previous depends on some other
849+
// recurrence that in turn depends on FOR. If that is the case, we would
850+
// also need to hoist recipes involving the other FOR, which may break
851+
// dependencies.
852+
if (Op == FOR)
853+
return false;
854+
855+
if (auto *R = NeedsHoisting(Op))
856+
HoistCandidates.push_back(R);
857+
}
858+
}
859+
860+
// Order recipes to hoist by dominance so earlier instructions are processed
861+
// first.
862+
sort(HoistCandidates, [&VPDT](const VPRecipeBase *A, const VPRecipeBase *B) {
863+
return VPDT.properlyDominates(A, B);
864+
});
865+
866+
for (VPRecipeBase *HoistCandidate : HoistCandidates) {
867+
HoistCandidate->moveBefore(*HoistPoint->getParent(),
868+
HoistPoint->getIterator());
869+
}
870+
871+
return true;
872+
}
873+
775874
bool VPlanTransforms::adjustFixedOrderRecurrences(VPlan &Plan,
776875
VPBuilder &LoopBuilder) {
777876
VPDominatorTree VPDT;
@@ -795,7 +894,8 @@ bool VPlanTransforms::adjustFixedOrderRecurrences(VPlan &Plan,
795894
Previous = PrevPhi->getBackedgeValue()->getDefiningRecipe();
796895
}
797896

798-
if (!sinkRecurrenceUsersAfterPrevious(FOR, Previous, VPDT))
897+
if (!sinkRecurrenceUsersAfterPrevious(FOR, Previous, VPDT) &&
898+
!hoistPreviousBeforeFORUsers(FOR, Previous, VPDT))
799899
return false;
800900

801901
// Introduce a recipe to combine the incoming and previous values of a

llvm/lib/Transforms/Vectorize/VPlanTransforms.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@ struct VPlanTransforms {
3636
GetIntOrFpInductionDescriptor,
3737
ScalarEvolution &SE, const TargetLibraryInfo &TLI);
3838

39-
/// Sink users of fixed-order recurrences after the recipe defining their
40-
/// previous value. Then introduce FirstOrderRecurrenceSplice VPInstructions
41-
/// to combine the value from the recurrence phis and previous values. The
42-
/// current implementation assumes all users can be sunk after the previous
43-
/// value, which is enforced by earlier legality checks.
39+
/// Try to have all users of fixed-order recurrences appear after the recipe
40+
/// defining their previous value, by either sinking users or hoisting recipes
41+
/// defining their previous value (and its operands). Then introduce
42+
/// FirstOrderRecurrenceSplice VPInstructions to combine the value from the
43+
/// recurrence phis and previous values.
4444
/// \returns true if all users of fixed-order recurrences could be re-arranged
4545
/// as needed or false if it is not possible. In the latter case, \p Plan is
4646
/// not valid.

llvm/test/Transforms/LoopVectorize/X86/fixed-order-recurrence.ll

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,3 +278,79 @@ exit:
278278
store double %.lcssa, ptr %C
279279
ret i64 %.in.lcssa
280280
}
281+
282+
; Test for https://github.com/llvm/llvm-project/issues/106523.
283+
; %for.2 requires no code motion, as its previous (%or) precedes its (first)
284+
; user (store). Furthermore, its user cannot sink, being a store.
285+
;
286+
; %for.1 requires code motion, as its previous (%trunc) follows its (first)
287+
; user (%or). Sinking %or past %trunc seems possible, as %or has no uses
288+
; (except for feeding %for.2; worth strengthening VPlan's dce?). However, %or
289+
; is both the user of %for.1 and the previous of %for.2, and we refrain from
290+
; sinking instructions that act as previous because they (may) serve points to
291+
; sink after.
292+
293+
; Instead, %for.1 can be reconciled by hoisting its previous above its user
294+
; %or, as this user %trunc depends only on %iv.
295+
define void @for_iv_trunc_optimized(ptr %dst) {
296+
; CHECK-LABEL: @for_iv_trunc_optimized(
297+
; CHECK-NEXT: bb:
298+
; CHECK-NEXT: br i1 false, label [[SCALAR_PH:%.*]], label [[VECTOR_PH:%.*]]
299+
; CHECK: vector.ph:
300+
; CHECK-NEXT: br label [[VECTOR_BODY:%.*]]
301+
; CHECK: vector.body:
302+
; CHECK-NEXT: [[INDEX:%.*]] = phi i64 [ 0, [[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], [[VECTOR_BODY]] ]
303+
; CHECK-NEXT: [[VECTOR_RECUR:%.*]] = phi <4 x i32> [ <i32 poison, i32 poison, i32 poison, i32 1>, [[VECTOR_PH]] ], [ [[STEP_ADD:%.*]], [[VECTOR_BODY]] ]
304+
; CHECK-NEXT: [[VECTOR_RECUR1:%.*]] = phi <4 x i32> [ <i32 poison, i32 poison, i32 poison, i32 0>, [[VECTOR_PH]] ], [ [[TMP3:%.*]], [[VECTOR_BODY]] ]
305+
; CHECK-NEXT: [[VEC_IND:%.*]] = phi <4 x i32> [ <i32 1, i32 2, i32 3, i32 4>, [[VECTOR_PH]] ], [ [[VEC_IND_NEXT:%.*]], [[VECTOR_BODY]] ]
306+
; CHECK-NEXT: [[STEP_ADD]] = add <4 x i32> [[VEC_IND]], <i32 4, i32 4, i32 4, i32 4>
307+
; CHECK-NEXT: [[TMP0:%.*]] = shufflevector <4 x i32> [[VECTOR_RECUR]], <4 x i32> [[VEC_IND]], <4 x i32> <i32 3, i32 4, i32 5, i32 6>
308+
; CHECK-NEXT: [[TMP1:%.*]] = shufflevector <4 x i32> [[VEC_IND]], <4 x i32> [[STEP_ADD]], <4 x i32> <i32 3, i32 4, i32 5, i32 6>
309+
; CHECK-NEXT: [[TMP2:%.*]] = or <4 x i32> [[TMP0]], <i32 3, i32 3, i32 3, i32 3>
310+
; CHECK-NEXT: [[TMP3]] = or <4 x i32> [[TMP1]], <i32 3, i32 3, i32 3, i32 3>
311+
; CHECK-NEXT: [[TMP5:%.*]] = shufflevector <4 x i32> [[TMP2]], <4 x i32> [[TMP3]], <4 x i32> <i32 3, i32 4, i32 5, i32 6>
312+
; CHECK-NEXT: [[TMP6:%.*]] = extractelement <4 x i32> [[TMP5]], i32 3
313+
; CHECK-NEXT: store i32 [[TMP6]], ptr [[DST:%.*]], align 4
314+
; CHECK-NEXT: [[INDEX_NEXT]] = add nuw i64 [[INDEX]], 8
315+
; CHECK-NEXT: [[VEC_IND_NEXT]] = add <4 x i32> [[STEP_ADD]], <i32 4, i32 4, i32 4, i32 4>
316+
; CHECK-NEXT: [[TMP7:%.*]] = icmp eq i64 [[INDEX_NEXT]], 336
317+
; CHECK-NEXT: br i1 [[TMP7]], label [[MIDDLE_BLOCK:%.*]], label [[VECTOR_BODY]], !llvm.loop [[LOOP8:![0-9]+]]
318+
; CHECK: middle.block:
319+
; CHECK-NEXT: [[VECTOR_RECUR_EXTRACT:%.*]] = extractelement <4 x i32> [[STEP_ADD]], i32 3
320+
; CHECK-NEXT: [[VECTOR_RECUR_EXTRACT3:%.*]] = extractelement <4 x i32> [[TMP3]], i32 3
321+
; CHECK-NEXT: br i1 false, label [[EXIT:%.*]], label [[SCALAR_PH]]
322+
; CHECK: scalar.ph:
323+
; CHECK-NEXT: [[BC_RESUME_VAL:%.*]] = phi i64 [ 337, [[MIDDLE_BLOCK]] ], [ 1, [[BB:%.*]] ]
324+
; CHECK-NEXT: [[SCALAR_RECUR_INIT:%.*]] = phi i32 [ [[VECTOR_RECUR_EXTRACT]], [[MIDDLE_BLOCK]] ], [ 1, [[BB]] ]
325+
; CHECK-NEXT: [[SCALAR_RECUR_INIT4:%.*]] = phi i32 [ [[VECTOR_RECUR_EXTRACT3]], [[MIDDLE_BLOCK]] ], [ 0, [[BB]] ]
326+
; CHECK-NEXT: br label [[LOOP:%.*]]
327+
; CHECK: loop:
328+
; CHECK-NEXT: [[IV:%.*]] = phi i64 [ [[BC_RESUME_VAL]], [[SCALAR_PH]] ], [ [[ADD:%.*]], [[LOOP]] ]
329+
; CHECK-NEXT: [[FOR_1:%.*]] = phi i32 [ [[SCALAR_RECUR_INIT]], [[SCALAR_PH]] ], [ [[TRUNC:%.*]], [[LOOP]] ]
330+
; CHECK-NEXT: [[FOR_2:%.*]] = phi i32 [ [[SCALAR_RECUR_INIT4]], [[SCALAR_PH]] ], [ [[OR:%.*]], [[LOOP]] ]
331+
; CHECK-NEXT: [[OR]] = or i32 [[FOR_1]], 3
332+
; CHECK-NEXT: [[ADD]] = add i64 [[IV]], 1
333+
; CHECK-NEXT: store i32 [[FOR_2]], ptr [[DST]], align 4
334+
; CHECK-NEXT: [[ICMP:%.*]] = icmp ult i64 [[IV]], 337
335+
; CHECK-NEXT: [[TRUNC]] = trunc i64 [[IV]] to i32
336+
; CHECK-NEXT: br i1 [[ICMP]], label [[LOOP]], label [[EXIT]], !llvm.loop [[LOOP9:![0-9]+]]
337+
; CHECK: exit:
338+
; CHECK-NEXT: ret void
339+
;
340+
bb:
341+
br label %loop
342+
343+
loop:
344+
%iv = phi i64 [ 1, %bb ], [ %add, %loop ]
345+
%for.1 = phi i32 [ 1, %bb ], [ %trunc, %loop ]
346+
%for.2 = phi i32 [ 0, %bb ], [ %or, %loop ]
347+
%or = or i32 %for.1, 3
348+
%add = add i64 %iv, 1
349+
store i32 %for.2, ptr %dst, align 4
350+
%icmp = icmp ult i64 %iv, 337
351+
%trunc = trunc i64 %iv to i32
352+
br i1 %icmp, label %loop, label %exit
353+
354+
exit:
355+
ret void
356+
}

llvm/test/Transforms/LoopVectorize/first-order-recurrence-chains-vplan.ll

Lines changed: 97 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -147,14 +147,57 @@ exit:
147147
}
148148

149149
; This test has two FORs (for.x and for.y) where incoming value from the previous
150-
; iteration (for.x.prev) of one FOR (for.y) depends on another FOR (for.x). Due to
151-
; this dependency all uses of the former FOR (for.y) should be sunk after
152-
; incoming value from the previous iteration (for.x.prev) of te latter FOR (for.y).
153-
; That means side-effecting user (store i64 %for.y.i64, ptr %gep) of the latter
154-
; FOR (for.y) should be moved which is not currently supported.
150+
; iteration (for.x.prev) of one FOR (for.y) depends on another FOR (for.x).
151+
; Sinking would require moving a recipe with side effects (store). Instead,
152+
; for.x.next can be hoisted.
155153
define i32 @test_chained_first_order_recurrences_4(ptr %base, i64 %x) {
156154
; CHECK-LABEL: 'test_chained_first_order_recurrences_4'
157-
; CHECK: No VPlans built.
155+
; CHECK: VPlan 'Initial VPlan for VF={4},UF>=1' {
156+
; CHECK-NEXT: Live-in vp<[[VFxUF:%.+]]> = VF * UF
157+
; CHECK-NEXT: Live-in vp<[[VTC:%.+]]> = vector-trip-count
158+
; CHECK-NEXT: Live-in ir<4098> = original trip-count
159+
; CHECK-EMPTY:
160+
; CHECK-NEXT: vector.ph:
161+
; CHECK-NEXT: WIDEN ir<%for.x.next> = mul ir<%x>, ir<2>
162+
; CHECK-NEXT: Successor(s): vector loop
163+
; CHECK-EMPTY:
164+
; CHECK-NEXT: <x1> vector loop: {
165+
; CHECK-NEXT: vector.body:
166+
; CHECK-NEXT: EMIT vp<[[CAN_IV:%.+]]> = CANONICAL-INDUCTION ir<0>, vp<[[CAN_IV_NEXT:%.+]]>
167+
; CHECK-NEXT: FIRST-ORDER-RECURRENCE-PHI ir<%for.x> = phi ir<0>, ir<%for.x.next>
168+
; CHECK-NEXT: FIRST-ORDER-RECURRENCE-PHI ir<%for.y> = phi ir<0>, ir<%for.x.prev>
169+
; CHECK-NEXT: vp<[[SCALAR_STEPS:%.+]]> = SCALAR-STEPS vp<[[CAN_IV]]>, ir<1>
170+
; CHECK-NEXT: CLONE ir<%gep> = getelementptr ir<%base>, vp<[[SCALAR_STEPS]]>
171+
; CHECK-NEXT: EMIT vp<[[SPLICE_X:%.]]> = first-order splice ir<%for.x>, ir<%for.x.next>
172+
; CHECK-NEXT: WIDEN-CAST ir<%for.x.prev> = trunc vp<[[SPLICE_X]]> to i32
173+
; CHECK-NEXT: EMIT vp<[[SPLICE_Y:%.+]]> = first-order splice ir<%for.y>, ir<%for.x.prev>
174+
; CHECK-NEXT: WIDEN-CAST ir<%for.y.i64> = sext vp<[[SPLICE_Y]]> to i64
175+
; CHECK-NEXT: vp<[[VEC_PTR:%.+]]> = vector-pointer ir<%gep>
176+
; CHECK-NEXT: WIDEN store vp<[[VEC_PTR]]>, ir<%for.y.i64>
177+
; CHECK-NEXT: EMIT vp<[[CAN_IV_NEXT]]> = add nuw vp<[[CAN_IV]]>, vp<[[VFxUF]]>
178+
; CHECK-NEXT: EMIT branch-on-count vp<[[CAN_IV_NEXT]]>, vp<[[VTC]]>
179+
; CHECK-NEXT: No successors
180+
; CHECK-NEXT: }
181+
; CHECK-NEXT: Successor(s): middle.block
182+
; CHECK-EMPTY:
183+
; CHECK-NEXT: middle.block:
184+
; CHECK-NEXT: EMIT vp<[[EXT_X:%.+]]> = extract-from-end ir<%for.x.next>, ir<1>
185+
; CHECK-NEXT: EMIT vp<[[EXT_Y:%.+]]>.1 = extract-from-end ir<%for.x.prev>, ir<1>
186+
; CHECK-NEXT: EMIT vp<[[MIDDLE_C:%.+]]> = icmp eq ir<4098>, vp<[[VTC]]>
187+
; CHECK-NEXT: EMIT branch-on-cond vp<[[MIDDLE_C]]>
188+
; CHECK-NEXT: Successor(s): ir-bb<ret>, scalar.ph
189+
; CHECK-EMPTY:
190+
; CHECK-NEXT: ir-bb<ret>:
191+
; CHECK-NEXT: No successors
192+
; CHECK-EMPTY:
193+
; CHECK-NEXT: scalar.ph:
194+
; CHECK-NEXT: EMIT vp<[[RESUME_X:%.+]]> = resume-phi vp<[[EXT_X]]>, ir<0>
195+
; CHECK-NEXT: EMIT vp<[[RESUME_Y:%.+]]>.1 = resume-phi vp<[[EXT_Y]]>.1, ir<0>
196+
; CHECK-NEXT: No successors
197+
; CHECK-EMPTY:
198+
; CHECK-NEXT: Live-out i64 %for.x = vp<[[RESUME_X]]>
199+
; CHECK-NEXT: Live-out i32 %for.y = vp<[[RESUME_Y]]>.1
200+
; CHECK-NEXT: }
158201
;
159202
entry:
160203
br label %loop
@@ -178,7 +221,54 @@ ret:
178221

179222
define i32 @test_chained_first_order_recurrences_5_hoist_to_load(ptr %base) {
180223
; CHECK-LABEL: 'test_chained_first_order_recurrences_5_hoist_to_load'
181-
; CHECK: No VPlans built.
224+
; CHECK: VPlan 'Initial VPlan for VF={4},UF>=1' {
225+
; CHECK-NEXT: Live-in vp<[[VFxUF:%.+]]> = VF * UF
226+
; CHECK-NEXT: Live-in vp<[[VTC:%.+]]> = vector-trip-count
227+
; CHECK-NEXT: Live-in ir<4098> = original trip-count
228+
; CHECK-EMPTY:
229+
; CHECK-NEXT: vector.ph:
230+
; CHECK-NEXT: Successor(s): vector loop
231+
; CHECK-EMPTY:
232+
; CHECK-NEXT: <x1> vector loop: {
233+
; CHECK-NEXT: vector.body:
234+
; CHECK-NEXT: EMIT vp<[[CAN_IV:%.+]]> = CANONICAL-INDUCTION ir<0>, vp<[[CAN_IV_NEXT:%.+]]>
235+
; CHECK-NEXT: FIRST-ORDER-RECURRENCE-PHI ir<%for.x> = phi ir<0>, ir<%for.x.next>
236+
; CHECK-NEXT: FIRST-ORDER-RECURRENCE-PHI ir<%for.y> = phi ir<0>, ir<%for.x.prev>
237+
; CHECK-NEXT: vp<[[SCALAR_STEPS:%.+]]> = SCALAR-STEPS vp<[[CAN_IV]]>, ir<1>
238+
; CHECK-NEXT: CLONE ir<%gep> = getelementptr ir<%base>, vp<[[SCALAR_STEPS]]>
239+
; CHECK-NEXT: vp<[[VEC_PTR:%.+]]> = vector-pointer ir<%gep>
240+
; CHECK-NEXT: WIDEN ir<%l> = load vp<[[VEC_PTR]]>
241+
; CHECK-NEXT: WIDEN ir<%for.x.next> = mul ir<%l>, ir<2>
242+
; CHECK-NEXT: EMIT vp<[[SPLICE_X:%.]]> = first-order splice ir<%for.x>, ir<%for.x.next>
243+
; CHECK-NEXT: WIDEN-CAST ir<%for.x.prev> = trunc vp<[[SPLICE_X]]> to i32
244+
; CHECK-NEXT: EMIT vp<[[SPLICE_Y:%.+]]> = first-order splice ir<%for.y>, ir<%for.x.prev>
245+
; CHECK-NEXT: WIDEN-CAST ir<%for.y.i64> = sext vp<[[SPLICE_Y]]> to i64
246+
; CHECK-NEXT: vp<[[VEC_PTR:%.+]]> = vector-pointer ir<%gep>
247+
; CHECK-NEXT: WIDEN store vp<[[VEC_PTR]]>, ir<%for.y.i64>
248+
; CHECK-NEXT: EMIT vp<[[CAN_IV_NEXT]]> = add nuw vp<[[CAN_IV]]>, vp<[[VFxUF]]>
249+
; CHECK-NEXT: EMIT branch-on-count vp<[[CAN_IV_NEXT]]>, vp<[[VTC]]>
250+
; CHECK-NEXT: No successors
251+
; CHECK-NEXT: }
252+
; CHECK-NEXT: Successor(s): middle.block
253+
; CHECK-EMPTY:
254+
; CHECK-NEXT: middle.block:
255+
; CHECK-NEXT: EMIT vp<[[EXT_X:%.+]]> = extract-from-end ir<%for.x.next>, ir<1>
256+
; CHECK-NEXT: EMIT vp<[[EXT_Y:%.+]]>.1 = extract-from-end ir<%for.x.prev>, ir<1>
257+
; CHECK-NEXT: EMIT vp<[[MIDDLE_C:%.+]]> = icmp eq ir<4098>, vp<[[VTC]]>
258+
; CHECK-NEXT: EMIT branch-on-cond vp<[[MIDDLE_C]]>
259+
; CHECK-NEXT: Successor(s): ir-bb<ret>, scalar.ph
260+
; CHECK-EMPTY:
261+
; CHECK-NEXT: ir-bb<ret>:
262+
; CHECK-NEXT: No successors
263+
; CHECK-EMPTY:
264+
; CHECK-NEXT: scalar.ph:
265+
; CHECK-NEXT: EMIT vp<[[RESUME_X:%.+]]> = resume-phi vp<[[EXT_X]]>, ir<0>
266+
; CHECK-NEXT: EMIT vp<[[RESUME_Y:%.+]]>.1 = resume-phi vp<[[EXT_Y]]>.1, ir<0>
267+
; CHECK-NEXT: No successors
268+
; CHECK-EMPTY:
269+
; CHECK-NEXT: Live-out i64 %for.x = vp<[[RESUME_X]]>
270+
; CHECK-NEXT: Live-out i32 %for.y = vp<[[RESUME_Y]]>.1
271+
; CHECK-NEXT: }
182272
;
183273
entry:
184274
br label %loop

0 commit comments

Comments
 (0)