Skip to content

Commit 55cb52b

Browse files
[Flang][OpenMP] Restructure recursive lowering in createBodyOfOp (#77761)
This brings `createBodyOfOp` to its final intended form. First, input privatization is performed, then the recursive lowering takes place, and finally the output privatization (lastprivate) is done. This enables fixing a known issue with infinite loops inside of an OpenMP region, and the fix is included in this patch. Fixes #74348. Recursive lowering [5/5] --------- Co-authored-by: Kiran Chandramohan <kiran.chandramohan@arm.com>
1 parent 90af11e commit 55cb52b

File tree

6 files changed

+198
-49
lines changed

6 files changed

+198
-49
lines changed

flang/include/flang/Lower/OpenMP.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ struct Variable;
5050
} // namespace pft
5151

5252
// Generate the OpenMP terminator for Operation at Location.
53-
void genOpenMPTerminator(fir::FirOpBuilder &, mlir::Operation *,
54-
mlir::Location);
53+
mlir::Operation *genOpenMPTerminator(fir::FirOpBuilder &, mlir::Operation *,
54+
mlir::Location);
5555

5656
void genOpenMPConstruct(AbstractConverter &, Fortran::lower::SymMap &,
5757
semantics::SemanticsContext &, pft::Evaluation &,

flang/lib/Lower/OpenMP.cpp

Lines changed: 103 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "flang/Parser/parse-tree.h"
2828
#include "flang/Semantics/openmp-directive-sets.h"
2929
#include "flang/Semantics/tools.h"
30+
#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
3031
#include "mlir/Dialect/OpenMP/OpenMPDialect.h"
3132
#include "mlir/Dialect/SCF/IR/SCF.h"
3233
#include "mlir/Transforms/RegionUtils.h"
@@ -385,7 +386,8 @@ void DataSharingProcessor::insertLastPrivateCompare(mlir::Operation *op) {
385386
// construct
386387
mlir::OpBuilder::InsertPoint unstructuredSectionsIP =
387388
firOpBuilder.saveInsertionPoint();
388-
firOpBuilder.setInsertionPointToStart(&op->getRegion(0).back());
389+
mlir::Operation *lastOper = op->getRegion(0).back().getTerminator();
390+
firOpBuilder.setInsertionPoint(lastOper);
389391
lastPrivIP = firOpBuilder.saveInsertionPoint();
390392
firOpBuilder.restoreInsertionPoint(unstructuredSectionsIP);
391393
}
@@ -2206,15 +2208,6 @@ static mlir::Type getLoopVarType(Fortran::lower::AbstractConverter &converter,
22062208
return converter.getFirOpBuilder().getIntegerType(loopVarTypeSize);
22072209
}
22082210

2209-
static void resetBeforeTerminator(fir::FirOpBuilder &firOpBuilder,
2210-
mlir::Operation *storeOp,
2211-
mlir::Block &block) {
2212-
if (storeOp)
2213-
firOpBuilder.setInsertionPointAfter(storeOp);
2214-
else
2215-
firOpBuilder.setInsertionPointToStart(&block);
2216-
}
2217-
22182211
static mlir::Operation *
22192212
createAndSetPrivatizedLoopVar(Fortran::lower::AbstractConverter &converter,
22202213
mlir::Location loc, mlir::Value indexVal,
@@ -2257,11 +2250,17 @@ static void createBodyOfOp(
22572250
const llvm::SmallVector<const Fortran::semantics::Symbol *> &args = {},
22582251
bool outerCombined = false, DataSharingProcessor *dsp = nullptr) {
22592252
fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
2253+
2254+
auto insertMarker = [](fir::FirOpBuilder &builder) {
2255+
mlir::Value undef = builder.create<fir::UndefOp>(builder.getUnknownLoc(),
2256+
builder.getIndexType());
2257+
return undef.getDefiningOp();
2258+
};
2259+
22602260
// If an argument for the region is provided then create the block with that
22612261
// argument. Also update the symbol's address with the mlir argument value.
22622262
// e.g. For loops the argument is the induction variable. And all further
22632263
// uses of the induction variable should use this mlir value.
2264-
mlir::Operation *storeOp = nullptr;
22652264
if (args.size()) {
22662265
std::size_t loopVarTypeSize = 0;
22672266
for (const Fortran::semantics::Symbol *arg : args)
@@ -2272,20 +2271,20 @@ static void createBodyOfOp(
22722271
firOpBuilder.createBlock(&op.getRegion(), {}, tiv, locs);
22732272
// The argument is not currently in memory, so make a temporary for the
22742273
// argument, and store it there, then bind that location to the argument.
2274+
mlir::Operation *storeOp = nullptr;
22752275
for (auto [argIndex, argSymbol] : llvm::enumerate(args)) {
22762276
mlir::Value indexVal =
22772277
fir::getBase(op.getRegion().front().getArgument(argIndex));
22782278
storeOp =
22792279
createAndSetPrivatizedLoopVar(converter, loc, indexVal, argSymbol);
22802280
}
2281+
firOpBuilder.setInsertionPointAfter(storeOp);
22812282
} else {
22822283
firOpBuilder.createBlock(&op.getRegion());
22832284
}
2284-
// Set the insert for the terminator operation to go at the end of the
2285-
// block - this is either empty or the block with the stores above,
2286-
// the end of the block works for both.
2287-
mlir::Block &block = op.getRegion().back();
2288-
firOpBuilder.setInsertionPointToEnd(&block);
2285+
2286+
// Mark the earliest insertion point.
2287+
mlir::Operation *marker = insertMarker(firOpBuilder);
22892288

22902289
// If it is an unstructured region and is not the outer region of a combined
22912290
// construct, create empty blocks for all evaluations.
@@ -2294,37 +2293,100 @@ static void createBodyOfOp(
22942293
mlir::omp::YieldOp>(
22952294
firOpBuilder, eval.getNestedEvaluations());
22962295

2297-
// Insert the terminator.
2298-
Fortran::lower::genOpenMPTerminator(firOpBuilder, op.getOperation(), loc);
2299-
// Reset the insert point to before the terminator.
2300-
resetBeforeTerminator(firOpBuilder, storeOp, block);
2296+
// Start with privatization, so that the lowering of the nested
2297+
// code will use the right symbols.
2298+
constexpr bool isLoop = std::is_same_v<Op, mlir::omp::WsLoopOp> ||
2299+
std::is_same_v<Op, mlir::omp::SimdLoopOp>;
2300+
bool privatize = clauses && !outerCombined;
23012301

2302-
// Handle privatization. Do not privatize if this is the outer operation.
2303-
if (clauses && !outerCombined) {
2304-
constexpr bool isLoop = std::is_same_v<Op, mlir::omp::WsLoopOp> ||
2305-
std::is_same_v<Op, mlir::omp::SimdLoopOp>;
2302+
firOpBuilder.setInsertionPoint(marker);
2303+
std::optional<DataSharingProcessor> tempDsp;
2304+
if (privatize) {
23062305
if (!dsp) {
2307-
DataSharingProcessor proc(converter, *clauses, eval);
2308-
proc.processStep1();
2309-
proc.processStep2(op, isLoop);
2310-
} else {
2311-
if (isLoop && args.size() > 0)
2312-
dsp->setLoopIV(converter.getSymbolAddress(*args[0]));
2313-
dsp->processStep2(op, isLoop);
2306+
tempDsp.emplace(converter, *clauses, eval);
2307+
tempDsp->processStep1();
23142308
}
2315-
2316-
if (storeOp)
2317-
firOpBuilder.setInsertionPointAfter(storeOp);
23182309
}
23192310

23202311
if constexpr (std::is_same_v<Op, mlir::omp::ParallelOp>) {
23212312
threadPrivatizeVars(converter, eval);
2322-
if (clauses)
2313+
if (clauses) {
2314+
firOpBuilder.setInsertionPoint(marker);
23232315
ClauseProcessor(converter, *clauses).processCopyin();
2316+
}
23242317
}
23252318

2326-
if (genNested)
2319+
if (genNested) {
2320+
// genFIR(Evaluation&) tries to patch up unterminated blocks, causing
2321+
// a lot of trouble if the terminator generation is delayed past this
2322+
// point. Insert a temporary terminator here, then delete it.
2323+
firOpBuilder.setInsertionPointToEnd(&op.getRegion().back());
2324+
auto *temp = Fortran::lower::genOpenMPTerminator(firOpBuilder,
2325+
op.getOperation(), loc);
2326+
firOpBuilder.setInsertionPointAfter(marker);
23272327
genNestedEvaluations(converter, eval);
2328+
temp->erase();
2329+
}
2330+
2331+
// Get or create a unique exiting block from the given region, or
2332+
// return nullptr if there is no exiting block.
2333+
auto getUniqueExit = [&](mlir::Region &region) -> mlir::Block * {
2334+
// Find the blocks where the OMP terminator should go. In simple cases
2335+
// it is the single block in the operation's region. When the region
2336+
// is more complicated, especially with unstructured control flow, there
2337+
// may be multiple blocks, and some of them may have non-OMP terminators
2338+
// resulting from lowering of the code contained within the operation.
2339+
// All the remaining blocks are potential exit points from the op's region.
2340+
//
2341+
// Explicit control flow cannot exit any OpenMP region (other than via
2342+
// STOP), and that is enforced by semantic checks prior to lowering. STOP
2343+
// statements are lowered to a function call.
2344+
2345+
// Collect unterminated blocks.
2346+
llvm::SmallVector<mlir::Block *> exits;
2347+
for (mlir::Block &b : region) {
2348+
if (b.empty() || !b.back().hasTrait<mlir::OpTrait::IsTerminator>())
2349+
exits.push_back(&b);
2350+
}
2351+
2352+
if (exits.empty())
2353+
return nullptr;
2354+
// If there already is a unique exiting block, do not create another one.
2355+
// Additionally, some ops (e.g. omp.sections) require only 1 block in
2356+
// its region.
2357+
if (exits.size() == 1)
2358+
return exits[0];
2359+
mlir::Block *exit = firOpBuilder.createBlock(&region);
2360+
for (mlir::Block *b : exits) {
2361+
firOpBuilder.setInsertionPointToEnd(b);
2362+
firOpBuilder.create<mlir::cf::BranchOp>(loc, exit);
2363+
}
2364+
return exit;
2365+
};
2366+
2367+
if (auto *exitBlock = getUniqueExit(op.getRegion())) {
2368+
firOpBuilder.setInsertionPointToEnd(exitBlock);
2369+
auto *term = Fortran::lower::genOpenMPTerminator(firOpBuilder,
2370+
op.getOperation(), loc);
2371+
// Only insert lastprivate code when there actually is an exit block.
2372+
// Such a block may not exist if the nested code produced an infinite
2373+
// loop (this may not make sense in production code, but a user could
2374+
// write that and we should handle it).
2375+
firOpBuilder.setInsertionPoint(term);
2376+
if (privatize) {
2377+
if (!dsp) {
2378+
assert(tempDsp.has_value());
2379+
tempDsp->processStep2(op, isLoop);
2380+
} else {
2381+
if (isLoop && args.size() > 0)
2382+
dsp->setLoopIV(converter.getSymbolAddress(*args[0]));
2383+
dsp->processStep2(op, isLoop);
2384+
}
2385+
}
2386+
}
2387+
2388+
firOpBuilder.setInsertionPointAfter(marker);
2389+
marker->erase();
23282390
}
23292391

23302392
static void genBodyOfTargetDataOp(
@@ -3756,14 +3818,14 @@ genOMP(Fortran::lower::AbstractConverter &converter,
37563818
// Public functions
37573819
//===----------------------------------------------------------------------===//
37583820

3759-
void Fortran::lower::genOpenMPTerminator(fir::FirOpBuilder &builder,
3760-
mlir::Operation *op,
3761-
mlir::Location loc) {
3821+
mlir::Operation *Fortran::lower::genOpenMPTerminator(fir::FirOpBuilder &builder,
3822+
mlir::Operation *op,
3823+
mlir::Location loc) {
37623824
if (mlir::isa<mlir::omp::WsLoopOp, mlir::omp::ReductionDeclareOp,
37633825
mlir::omp::AtomicUpdateOp, mlir::omp::SimdLoopOp>(op))
3764-
builder.create<mlir::omp::YieldOp>(loc);
3826+
return builder.create<mlir::omp::YieldOp>(loc);
37653827
else
3766-
builder.create<mlir::omp::TerminatorOp>(loc);
3828+
return builder.create<mlir::omp::TerminatorOp>(loc);
37673829
}
37683830

37693831
void Fortran::lower::genOpenMPConstruct(

flang/test/Lower/OpenMP/FIR/sections.f90

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,11 @@ subroutine lastprivate()
126126
x = x * 10
127127
!CHECK: omp.section {
128128
!CHECK: %[[PRIVATE_X:.*]] = fir.alloca i32 {bindc_name = "x", pinned, uniq_name = "_QFlastprivateEx"}
129-
!CHECK: %[[true:.*]] = arith.constant true
130129
!CHECK: %[[temp:.*]] = fir.load %[[PRIVATE_X]] : !fir.ref<i32>
131130
!CHECK: %[[const:.*]] = arith.constant 1 : i32
132131
!CHECK: %[[result:.*]] = arith.addi %[[temp]], %[[const]] : i32
133132
!CHECK: fir.store %[[result]] to %[[PRIVATE_X]] : !fir.ref<i32>
133+
!CHECK: %[[true:.*]] = arith.constant true
134134
!CHECK: fir.if %[[true]] {
135135
!CHECK: %[[temp:.*]] = fir.load %[[PRIVATE_X]] : !fir.ref<i32>
136136
!CHECK: fir.store %[[temp]] to %[[X]] : !fir.ref<i32>
@@ -163,11 +163,11 @@ subroutine lastprivate()
163163
!CHECK: %[[temp:.*]] = fir.load %[[X]] : !fir.ref<i32>
164164
!CHECK: fir.store %[[temp]] to %[[PRIVATE_X]] : !fir.ref<i32>
165165
!CHECK: omp.barrier
166-
!CHECK: %[[true:.*]] = arith.constant true
167166
!CHECK: %[[temp:.*]] = fir.load %[[PRIVATE_X]] : !fir.ref<i32>
168167
!CHECK: %[[const:.*]] = arith.constant 1 : i32
169168
!CHECK: %[[result:.*]] = arith.addi %[[temp]], %[[const]] : i32
170169
!CHECK: fir.store %[[result]] to %[[PRIVATE_X]] : !fir.ref<i32>
170+
!CHECK: %[[true:.*]] = arith.constant true
171171
!CHECK: fir.if %true {
172172
!CHECK: %[[temp:.*]] = fir.load %[[PRIVATE_X]] : !fir.ref<i32>
173173
!CHECK: fir.store %[[temp]] to %[[X]] : !fir.ref<i32>
@@ -200,11 +200,11 @@ subroutine lastprivate()
200200
!CHECK: %[[temp:.*]] = fir.load %[[X]] : !fir.ref<i32>
201201
!CHECK: fir.store %[[temp]] to %[[PRIVATE_X]] : !fir.ref<i32>
202202
!CHECK: omp.barrier
203-
!CHECK: %[[true:.*]] = arith.constant true
204203
!CHECK: %[[temp:.*]] = fir.load %[[PRIVATE_X]] : !fir.ref<i32>
205204
!CHECK: %[[const:.*]] = arith.constant 1 : i32
206205
!CHECK: %[[result:.*]] = arith.addi %[[temp]], %[[const]] : i32
207206
!CHECK: fir.store %[[result]] to %[[PRIVATE_X]] : !fir.ref<i32>
207+
!CHECK: %[[true:.*]] = arith.constant true
208208
!CHECK: fir.if %true {
209209
!CHECK: %[[temp:.*]] = fir.load %[[PRIVATE_X]] : !fir.ref<i32>
210210
!CHECK: fir.store %[[temp]] to %[[X]] : !fir.ref<i32>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
! RUN: bbc -fopenmp -o - %s | FileCheck %s
2+
3+
! Check that this test can be lowered successfully.
4+
! See https://github.com/llvm/llvm-project/issues/74348
5+
6+
! CHECK-LABEL: func.func @_QPsb
7+
! CHECK: omp.parallel
8+
! CHECK: cf.cond_br %{{[0-9]+}}, ^bb1, ^bb2
9+
! CHECK-NEXT: ^bb1: // pred: ^bb0
10+
! CHECK: cf.br ^bb2
11+
! CHECK-NEXT: ^bb2: // 3 preds: ^bb0, ^bb1, ^bb2
12+
! CHECK-NEXT: cf.br ^bb2
13+
! CHECK-NEXT: }
14+
15+
subroutine sb(ninter, numnod)
16+
integer :: ninter, numnod
17+
integer, dimension(:), allocatable :: indx_nm
18+
19+
!$omp parallel
20+
if (ninter>0) then
21+
allocate(indx_nm(numnod))
22+
endif
23+
220 continue
24+
goto 220
25+
!$omp end parallel
26+
end subroutine

flang/test/Lower/OpenMP/sections.f90

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,11 @@ subroutine lastprivate()
141141
!CHECK: omp.section {
142142
!CHECK: %[[PRIVATE_X:.*]] = fir.alloca i32 {bindc_name = "x", pinned, uniq_name = "_QFlastprivateEx"}
143143
!CHECK: %[[PRIVATE_X_DECL:.*]]:2 = hlfir.declare %[[PRIVATE_X]] {uniq_name = "_QFlastprivateEx"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
144-
!CHECK: %[[TRUE:.*]] = arith.constant true
145144
!CHECK: %[[TEMP:.*]] = fir.load %[[PRIVATE_X_DECL]]#0 : !fir.ref<i32>
146145
!CHECK: %[[CONST:.*]] = arith.constant 1 : i32
147146
!CHECK: %[[RESULT:.*]] = arith.addi %[[TEMP]], %[[CONST]] : i32
148147
!CHECK: hlfir.assign %[[RESULT]] to %[[PRIVATE_X_DECL]]#0 : i32, !fir.ref<i32>
148+
!CHECK: %[[TRUE:.*]] = arith.constant true
149149
!CHECK: fir.if %[[TRUE]] {
150150
!CHECK: %[[TEMP1:.*]] = fir.load %[[PRIVATE_X_DECL]]#0 : !fir.ref<i32>
151151
!CHECK: hlfir.assign %[[TEMP1]] to %[[X_DECL]]#0 temporary_lhs : i32, !fir.ref<i32>
@@ -180,11 +180,11 @@ subroutine lastprivate()
180180
!CHECK: %[[TEMP:.*]] = fir.load %[[X_DECL]]#0 : !fir.ref<i32>
181181
!CHECK: hlfir.assign %[[TEMP]] to %[[PRIVATE_X_DECL]]#0 temporary_lhs : i32, !fir.ref<i32>
182182
!CHECK: omp.barrier
183-
!CHECK: %[[TRUE:.*]] = arith.constant true
184183
!CHECK: %[[TEMP:.*]] = fir.load %[[PRIVATE_X_DECL]]#0 : !fir.ref<i32>
185184
!CHECK: %[[CONST:.*]] = arith.constant 1 : i32
186185
!CHECK: %[[RESULT:.*]] = arith.addi %[[TEMP]], %[[CONST]] : i32
187186
!CHECK: hlfir.assign %[[RESULT]] to %[[PRIVATE_X_DECL]]#0 : i32, !fir.ref<i32>
187+
!CHECK: %[[TRUE:.*]] = arith.constant true
188188
!CHECK: fir.if %[[TRUE]] {
189189
!CHECK: %[[TEMP:.*]] = fir.load %[[PRIVATE_X_DECL]]#0 : !fir.ref<i32>
190190
!CHECK: hlfir.assign %[[TEMP]] to %[[X_DECL]]#0 temporary_lhs : i32, !fir.ref<i32>
@@ -219,11 +219,11 @@ subroutine lastprivate()
219219
!CHECK: %[[TEMP:.*]] = fir.load %[[X_DECL]]#0 : !fir.ref<i32>
220220
!CHECK: hlfir.assign %[[TEMP]] to %[[PRIVATE_X_DECL]]#0 temporary_lhs : i32, !fir.ref<i32>
221221
!CHECK: omp.barrier
222-
!CHECK: %[[TRUE:.*]] = arith.constant true
223222
!CHECK: %[[TEMP:.*]] = fir.load %[[PRIVATE_X_DECL]]#0 : !fir.ref<i32>
224223
!CHECK: %[[CONST:.*]] = arith.constant 1 : i32
225224
!CHECK: %[[RESULT:.*]] = arith.addi %[[TEMP]], %[[CONST]] : i32
226225
!CHECK: hlfir.assign %[[RESULT]] to %[[PRIVATE_X_DECL]]#0 : i32, !fir.ref<i32>
226+
!CHECK: %[[TRUE:.*]] = arith.constant true
227227
!CHECK: fir.if %[[TRUE]] {
228228
!CHECK: %[[TEMP:.*]] = fir.load %[[PRIVATE_X_DECL]]#0 : !fir.ref<i32>
229229
!CHECK: hlfir.assign %[[TEMP]] to %[[X_DECL]]#0 temporary_lhs : i32, !fir.ref<i32>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
! RUN: bbc -emit-hlfir -fopenmp -o - %s | FileCheck %s
2+
3+
subroutine sub(imax, jmax, x, y)
4+
integer, intent(in) :: imax, jmax
5+
real, intent(in), dimension(1:imax, 1:jmax) :: x, y
6+
7+
integer :: i, j, ii
8+
9+
! collapse(2) is needed to reproduce the issue
10+
!$omp parallel do collapse(2)
11+
do j = 1, jmax
12+
do i = 1, imax
13+
do ii = 1, imax ! note that this loop is not collapsed
14+
if (x(i,j) < y(ii,j)) then
15+
! exit needed to force unstructured control flow
16+
exit
17+
endif
18+
enddo
19+
enddo
20+
enddo
21+
end subroutine sub
22+
23+
! this is testing that we don't crash generating code for this: in particular
24+
! that all blocks are terminated
25+
26+
! CHECK-LABEL: func.func @_QPsub(
27+
! CHECK-SAME: %[[VAL_0:.*]]: !fir.ref<i32> {fir.bindc_name = "imax"},
28+
! CHECK-SAME: %[[VAL_1:.*]]: !fir.ref<i32> {fir.bindc_name = "jmax"},
29+
! CHECK-SAME: %[[VAL_2:.*]]: !fir.ref<!fir.array<?x?xf32>> {fir.bindc_name = "x"},
30+
! CHECK-SAME: %[[VAL_3:.*]]: !fir.ref<!fir.array<?x?xf32>> {fir.bindc_name = "y"}) {
31+
! [...]
32+
! CHECK: omp.wsloop for (%[[VAL_53:.*]], %[[VAL_54:.*]]) : i32 = ({{.*}}) to ({{.*}}) inclusive step ({{.*}}) {
33+
! [...]
34+
! CHECK: cf.br ^bb1
35+
! CHECK: ^bb1:
36+
! CHECK: cf.br ^bb2
37+
! CHECK: ^bb2:
38+
! [...]
39+
! CHECK: cf.br ^bb3
40+
! CHECK: ^bb3:
41+
! [...]
42+
! CHECK: %[[VAL_63:.*]] = arith.cmpi sgt, %{{.*}}, %{{.*}} : i32
43+
! CHECK: cf.cond_br %[[VAL_63]], ^bb4, ^bb7
44+
! CHECK: ^bb4:
45+
! [...]
46+
! CHECK: %[[VAL_76:.*]] = arith.cmpf olt, %{{.*}}, %{{.*}} fastmath<contract> : f32
47+
! CHECK: cf.cond_br %[[VAL_76]], ^bb5, ^bb6
48+
! CHECK: ^bb5:
49+
! CHECK: cf.br ^bb7
50+
! CHECK: ^bb6:
51+
! [...]
52+
! CHECK: cf.br ^bb3
53+
! CHECK: ^bb7:
54+
! CHECK: omp.yield
55+
! CHECK: }
56+
! CHECK: omp.terminator
57+
! CHECK: }
58+
! CHECK: cf.br ^bb1
59+
! CHECK: ^bb1:
60+
! CHECK: return
61+
! CHECK: }

0 commit comments

Comments
 (0)