Skip to content

Commit 5116c4a

Browse files
Meinersburmahesh-attarde
authored andcommitted
[Flang] Implement !$omp unroll using omp.unroll_heuristic (llvm#144785)
Add support for `!$omp unroll` in Flang and basic MLIR `omp.canonical_loop` modeling. First step to add `omp.canonical_loop` modeling to the MLIR OpenMP dialect with the goal of being more general than the current `omp.loop_nest` approach: * Support for non-perfectly nested loops * Support for non-rectangular loops * Support for arbitrary compositions of loop transformations This patch is functional end-to-end and adds support for `!$omp unroll` to Flang. `!$omp unroll` is lowered to `omp.new_cli`, `omp.canonical_loop`, and `omp.unroll_heuristic` in MLIR, which are lowered to LLVM-IR using the OpenMPIRBuilder (https://reviews.llvm.org/D107764).
1 parent ad465d9 commit 5116c4a

File tree

7 files changed

+354
-8
lines changed

7 files changed

+354
-8
lines changed

flang/lib/Lower/OpenMP/OpenMP.cpp

Lines changed: 199 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,28 @@ static void processHostEvalClauses(lower::AbstractConverter &converter,
6363
lower::pft::Evaluation &eval,
6464
mlir::Location loc);
6565

66+
static llvm::omp::Directive
67+
getOpenMPDirectiveEnum(const parser::OmpLoopDirective &beginStatment) {
68+
return beginStatment.v;
69+
}
70+
71+
static llvm::omp::Directive getOpenMPDirectiveEnum(
72+
const parser::OmpBeginLoopDirective &beginLoopDirective) {
73+
return getOpenMPDirectiveEnum(
74+
std::get<parser::OmpLoopDirective>(beginLoopDirective.t));
75+
}
76+
77+
static llvm::omp::Directive
78+
getOpenMPDirectiveEnum(const parser::OpenMPLoopConstruct &ompLoopConstruct) {
79+
return getOpenMPDirectiveEnum(
80+
std::get<parser::OmpBeginLoopDirective>(ompLoopConstruct.t));
81+
}
82+
83+
static llvm::omp::Directive getOpenMPDirectiveEnum(
84+
const common::Indirection<parser::OpenMPLoopConstruct> &ompLoopConstruct) {
85+
return getOpenMPDirectiveEnum(ompLoopConstruct.value());
86+
}
87+
6688
namespace {
6789
/// Structure holding information that is needed to pass host-evaluated
6890
/// information to later lowering stages.
@@ -2069,6 +2091,163 @@ genLoopOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
20692091
return loopOp;
20702092
}
20712093

2094+
static mlir::omp::CanonicalLoopOp
2095+
genCanonicalLoopOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
2096+
semantics::SemanticsContext &semaCtx,
2097+
lower::pft::Evaluation &eval, mlir::Location loc,
2098+
const ConstructQueue &queue,
2099+
ConstructQueue::const_iterator item,
2100+
llvm::ArrayRef<const semantics::Symbol *> ivs,
2101+
llvm::omp::Directive directive, DataSharingProcessor &dsp) {
2102+
fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
2103+
2104+
assert(ivs.size() == 1 && "Nested loops not yet implemented");
2105+
const semantics::Symbol *iv = ivs[0];
2106+
2107+
auto &nestedEval = eval.getFirstNestedEvaluation();
2108+
if (nestedEval.getIf<parser::DoConstruct>()->IsDoConcurrent()) {
2109+
// OpenMP specifies DO CONCURRENT only with the `!omp loop` construct. Will
2110+
// need to add special cases for this combination.
2111+
TODO(loc, "DO CONCURRENT as canonical loop not supported");
2112+
}
2113+
2114+
// Get the loop bounds (and increment)
2115+
auto &doLoopEval = nestedEval.getFirstNestedEvaluation();
2116+
auto *doStmt = doLoopEval.getIf<parser::NonLabelDoStmt>();
2117+
assert(doStmt && "Expected do loop to be in the nested evaluation");
2118+
auto &loopControl = std::get<std::optional<parser::LoopControl>>(doStmt->t);
2119+
assert(loopControl.has_value());
2120+
auto *bounds = std::get_if<parser::LoopControl::Bounds>(&loopControl->u);
2121+
assert(bounds && "Expected bounds for canonical loop");
2122+
lower::StatementContext stmtCtx;
2123+
mlir::Value loopLBVar = fir::getBase(
2124+
converter.genExprValue(*semantics::GetExpr(bounds->lower), stmtCtx));
2125+
mlir::Value loopUBVar = fir::getBase(
2126+
converter.genExprValue(*semantics::GetExpr(bounds->upper), stmtCtx));
2127+
mlir::Value loopStepVar = [&]() {
2128+
if (bounds->step) {
2129+
return fir::getBase(
2130+
converter.genExprValue(*semantics::GetExpr(bounds->step), stmtCtx));
2131+
}
2132+
2133+
// If `step` is not present, assume it is `1`.
2134+
return firOpBuilder.createIntegerConstant(loc, firOpBuilder.getI32Type(),
2135+
1);
2136+
}();
2137+
2138+
// Get the integer kind for the loop variable and cast the loop bounds
2139+
size_t loopVarTypeSize = bounds->name.thing.symbol->GetUltimate().size();
2140+
mlir::Type loopVarType = getLoopVarType(converter, loopVarTypeSize);
2141+
loopLBVar = firOpBuilder.createConvert(loc, loopVarType, loopLBVar);
2142+
loopUBVar = firOpBuilder.createConvert(loc, loopVarType, loopUBVar);
2143+
loopStepVar = firOpBuilder.createConvert(loc, loopVarType, loopStepVar);
2144+
2145+
// Start lowering
2146+
mlir::Value zero = firOpBuilder.createIntegerConstant(loc, loopVarType, 0);
2147+
mlir::Value one = firOpBuilder.createIntegerConstant(loc, loopVarType, 1);
2148+
mlir::Value isDownwards = firOpBuilder.create<mlir::arith::CmpIOp>(
2149+
loc, mlir::arith::CmpIPredicate::slt, loopStepVar, zero);
2150+
2151+
// Ensure we are counting upwards. If not, negate step and swap lb and ub.
2152+
mlir::Value negStep =
2153+
firOpBuilder.create<mlir::arith::SubIOp>(loc, zero, loopStepVar);
2154+
mlir::Value incr = firOpBuilder.create<mlir::arith::SelectOp>(
2155+
loc, isDownwards, negStep, loopStepVar);
2156+
mlir::Value lb = firOpBuilder.create<mlir::arith::SelectOp>(
2157+
loc, isDownwards, loopUBVar, loopLBVar);
2158+
mlir::Value ub = firOpBuilder.create<mlir::arith::SelectOp>(
2159+
loc, isDownwards, loopLBVar, loopUBVar);
2160+
2161+
// Compute the trip count assuming lb <= ub. This guarantees that the result
2162+
// is non-negative and we can use unsigned arithmetic.
2163+
mlir::Value span = firOpBuilder.create<mlir::arith::SubIOp>(
2164+
loc, ub, lb, ::mlir::arith::IntegerOverflowFlags::nuw);
2165+
mlir::Value tcMinusOne =
2166+
firOpBuilder.create<mlir::arith::DivUIOp>(loc, span, incr);
2167+
mlir::Value tcIfLooping = firOpBuilder.create<mlir::arith::AddIOp>(
2168+
loc, tcMinusOne, one, ::mlir::arith::IntegerOverflowFlags::nuw);
2169+
2170+
// Fall back to 0 if lb > ub
2171+
mlir::Value isZeroTC = firOpBuilder.create<mlir::arith::CmpIOp>(
2172+
loc, mlir::arith::CmpIPredicate::slt, ub, lb);
2173+
mlir::Value tripcount = firOpBuilder.create<mlir::arith::SelectOp>(
2174+
loc, isZeroTC, zero, tcIfLooping);
2175+
2176+
// Create the CLI handle.
2177+
auto newcli = firOpBuilder.create<mlir::omp::NewCliOp>(loc);
2178+
mlir::Value cli = newcli.getResult();
2179+
2180+
auto ivCallback = [&](mlir::Operation *op)
2181+
-> llvm::SmallVector<const Fortran::semantics::Symbol *> {
2182+
mlir::Region &region = op->getRegion(0);
2183+
2184+
// Create the op's region skeleton (BB taking the iv as argument)
2185+
firOpBuilder.createBlock(&region, {}, {loopVarType}, {loc});
2186+
2187+
// Compute the value of the loop variable from the logical iteration number.
2188+
mlir::Value natIterNum = fir::getBase(region.front().getArgument(0));
2189+
mlir::Value scaled =
2190+
firOpBuilder.create<mlir::arith::MulIOp>(loc, natIterNum, loopStepVar);
2191+
mlir::Value userVal =
2192+
firOpBuilder.create<mlir::arith::AddIOp>(loc, loopLBVar, scaled);
2193+
2194+
// The argument is not currently in memory, so make a temporary for the
2195+
// argument, and store it there, then bind that location to the argument.
2196+
mlir::Operation *storeOp =
2197+
createAndSetPrivatizedLoopVar(converter, loc, userVal, iv);
2198+
2199+
firOpBuilder.setInsertionPointAfter(storeOp);
2200+
return {iv};
2201+
};
2202+
2203+
// Create the omp.canonical_loop operation
2204+
auto canonLoop = genOpWithBody<mlir::omp::CanonicalLoopOp>(
2205+
OpWithBodyGenInfo(converter, symTable, semaCtx, loc, nestedEval,
2206+
directive)
2207+
.setClauses(&item->clauses)
2208+
.setDataSharingProcessor(&dsp)
2209+
.setGenRegionEntryCb(ivCallback),
2210+
queue, item, tripcount, cli);
2211+
2212+
firOpBuilder.setInsertionPointAfter(canonLoop);
2213+
return canonLoop;
2214+
}
2215+
2216+
static void genUnrollOp(Fortran::lower::AbstractConverter &converter,
2217+
Fortran::lower::SymMap &symTable,
2218+
lower::StatementContext &stmtCtx,
2219+
Fortran::semantics::SemanticsContext &semaCtx,
2220+
Fortran::lower::pft::Evaluation &eval,
2221+
mlir::Location loc, const ConstructQueue &queue,
2222+
ConstructQueue::const_iterator item) {
2223+
fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
2224+
2225+
mlir::omp::LoopRelatedClauseOps loopInfo;
2226+
llvm::SmallVector<const semantics::Symbol *> iv;
2227+
collectLoopRelatedInfo(converter, loc, eval, item->clauses, loopInfo, iv);
2228+
2229+
// Clauses for unrolling not yet implemnted
2230+
ClauseProcessor cp(converter, semaCtx, item->clauses);
2231+
cp.processTODO<clause::Partial, clause::Full>(
2232+
loc, llvm::omp::Directive::OMPD_unroll);
2233+
2234+
// Even though unroll does not support data-sharing clauses, but this is
2235+
// required to fill the symbol table.
2236+
DataSharingProcessor dsp(converter, semaCtx, item->clauses, eval,
2237+
/*shouldCollectPreDeterminedSymbols=*/true,
2238+
/*useDelayedPrivatization=*/false, symTable);
2239+
dsp.processStep1();
2240+
2241+
// Emit the associated loop
2242+
auto canonLoop =
2243+
genCanonicalLoopOp(converter, symTable, semaCtx, eval, loc, queue, item,
2244+
iv, llvm::omp::Directive::OMPD_unroll, dsp);
2245+
2246+
// Apply unrolling to it
2247+
auto cli = canonLoop.getCli();
2248+
firOpBuilder.create<mlir::omp::UnrollHeuristicOp>(loc, cli);
2249+
}
2250+
20722251
static mlir::omp::MaskedOp
20732252
genMaskedOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
20742253
lower::StatementContext &stmtCtx,
@@ -3249,12 +3428,14 @@ static void genOMPDispatch(lower::AbstractConverter &converter,
32493428
newOp = genTeamsOp(converter, symTable, stmtCtx, semaCtx, eval, loc, queue,
32503429
item);
32513430
break;
3252-
case llvm::omp::Directive::OMPD_tile:
3253-
case llvm::omp::Directive::OMPD_unroll: {
3431+
case llvm::omp::Directive::OMPD_tile: {
32543432
unsigned version = semaCtx.langOptions().OpenMPVersion;
32553433
TODO(loc, "Unhandled loop directive (" +
32563434
llvm::omp::getOpenMPDirectiveName(dir, version) + ")");
32573435
}
3436+
case llvm::omp::Directive::OMPD_unroll:
3437+
genUnrollOp(converter, symTable, stmtCtx, semaCtx, eval, loc, queue, item);
3438+
break;
32583439
// case llvm::omp::Directive::OMPD_workdistribute:
32593440
case llvm::omp::Directive::OMPD_workshare:
32603441
newOp = genWorkshareOp(converter, symTable, stmtCtx, semaCtx, eval, loc,
@@ -3690,12 +3871,25 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
36903871
if (auto *ompNestedLoopCons{
36913872
std::get_if<common::Indirection<parser::OpenMPLoopConstruct>>(
36923873
&*optLoopCons)}) {
3693-
genOMP(converter, symTable, semaCtx, eval, ompNestedLoopCons->value());
3874+
llvm::omp::Directive nestedDirective =
3875+
getOpenMPDirectiveEnum(*ompNestedLoopCons);
3876+
switch (nestedDirective) {
3877+
case llvm::omp::Directive::OMPD_tile:
3878+
// Emit the omp.loop_nest with annotation for tiling
3879+
genOMP(converter, symTable, semaCtx, eval, ompNestedLoopCons->value());
3880+
break;
3881+
default: {
3882+
unsigned version = semaCtx.langOptions().OpenMPVersion;
3883+
TODO(currentLocation,
3884+
"Applying a loop-associated on the loop generated by the " +
3885+
llvm::omp::getOpenMPDirectiveName(nestedDirective, version) +
3886+
" construct");
3887+
}
3888+
}
36943889
}
36953890
}
36963891

3697-
llvm::omp::Directive directive =
3698-
std::get<parser::OmpLoopDirective>(beginLoopDirective.t).v;
3892+
llvm::omp::Directive directive = getOpenMPDirectiveEnum(beginLoopDirective);
36993893
const parser::CharBlock &source =
37003894
std::get<parser::OmpLoopDirective>(beginLoopDirective.t).source;
37013895
ConstructQueue queue{

flang/test/Lower/OpenMP/nested-loop-transformation-construct01.f90

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
! Test to ensure TODO message is emitted for tile OpenMP 5.1 Directives when they are nested.
22

3-
!RUN: not %flang -fopenmp -fopenmp-version=51 %s 2>&1 | FileCheck %s
3+
!RUN: not %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=51 -o - %s 2>&1 | FileCheck %s
44

55
subroutine loop_transformation_construct
66
implicit none

flang/test/Lower/OpenMP/nested-loop-transformation-construct02.f90

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
! Test to ensure TODO message is emitted for unroll OpenMP 5.1 Directives when they are nested.
22

3-
!RUN: not %flang -fopenmp -fopenmp-version=51 %s 2>&1 | FileCheck %s
3+
!RUN: not %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=51 -o - %s 2>&1 | FileCheck %s
44

55
program loop_transformation_construct
66
implicit none
@@ -17,4 +17,4 @@ program loop_transformation_construct
1717
!$omp end do
1818
end program loop_transformation_construct
1919

20-
!CHECK: not yet implemented: Unhandled loop directive (unroll)
20+
!CHECK: not yet implemented: Applying a loop-associated on the loop generated by the unroll construct
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=51 -o - %s 2>&1 | FileCheck %s
2+
3+
4+
subroutine omp_unroll_heuristic01(lb, ub, inc)
5+
integer res, i, lb, ub, inc
6+
7+
!$omp unroll
8+
do i = lb, ub, inc
9+
res = i
10+
end do
11+
!$omp end unroll
12+
13+
end subroutine omp_unroll_heuristic01
14+
15+
16+
!CHECK-LABEL: func.func @_QPomp_unroll_heuristic01(
17+
!CHECK: %c0_i32 = arith.constant 0 : i32
18+
!CHECK-NEXT: %c1_i32 = arith.constant 1 : i32
19+
!CHECK-NEXT: %13 = arith.cmpi slt, %12, %c0_i32 : i32
20+
!CHECK-NEXT: %14 = arith.subi %c0_i32, %12 : i32
21+
!CHECK-NEXT: %15 = arith.select %13, %14, %12 : i32
22+
!CHECK-NEXT: %16 = arith.select %13, %11, %10 : i32
23+
!CHECK-NEXT: %17 = arith.select %13, %10, %11 : i32
24+
!CHECK-NEXT: %18 = arith.subi %17, %16 overflow<nuw> : i32
25+
!CHECK-NEXT: %19 = arith.divui %18, %15 : i32
26+
!CHECK-NEXT: %20 = arith.addi %19, %c1_i32 overflow<nuw> : i32
27+
!CHECK-NEXT: %21 = arith.cmpi slt, %17, %16 : i32
28+
!CHECK-NEXT: %22 = arith.select %21, %c0_i32, %20 : i32
29+
!CHECK-NEXT: %canonloop_s0 = omp.new_cli
30+
!CHECK-NEXT: omp.canonical_loop(%canonloop_s0) %iv : i32 in range(%22) {
31+
!CHECK-NEXT: %23 = arith.muli %iv, %12 : i32
32+
!CHECK-NEXT: %24 = arith.addi %10, %23 : i32
33+
!CHECK-NEXT: hlfir.assign %24 to %9#0 : i32, !fir.ref<i32>
34+
!CHECK-NEXT: %25 = fir.load %9#0 : !fir.ref<i32>
35+
!CHECK-NEXT: hlfir.assign %25 to %6#0 : i32, !fir.ref<i32>
36+
!CHECK-NEXT: omp.terminator
37+
!CHECK-NEXT: }
38+
!CHECK-NEXT: omp.unroll_heuristic(%canonloop_s0)
39+
!CHECK-NEXT: return
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=51 -o - %s 2>&1 | FileCheck %s
2+
3+
4+
subroutine omp_unroll_heuristic_nested02(outer_lb, outer_ub, outer_inc, inner_lb, inner_ub, inner_inc)
5+
integer res, i, j, inner_lb, inner_ub, inner_inc, outer_lb, outer_ub, outer_inc
6+
7+
!$omp unroll
8+
do i = outer_lb, outer_ub, outer_inc
9+
!$omp unroll
10+
do j = inner_lb, inner_ub, inner_inc
11+
res = i + j
12+
end do
13+
!$omp end unroll
14+
end do
15+
!$omp end unroll
16+
17+
end subroutine omp_unroll_heuristic_nested02
18+
19+
20+
!CHECK-LABEL: func.func @_QPomp_unroll_heuristic_nested02(%arg0: !fir.ref<i32> {fir.bindc_name = "outer_lb"}, %arg1: !fir.ref<i32> {fir.bindc_name = "outer_ub"}, %arg2: !fir.ref<i32> {fir.bindc_name = "outer_inc"}, %arg3: !fir.ref<i32> {fir.bindc_name = "inner_lb"}, %arg4: !fir.ref<i32> {fir.bindc_name = "inner_ub"}, %arg5: !fir.ref<i32> {fir.bindc_name = "inner_inc"}) {
21+
!CHECK: %c0_i32 = arith.constant 0 : i32
22+
!CHECK-NEXT: %c1_i32 = arith.constant 1 : i32
23+
!CHECK-NEXT: %18 = arith.cmpi slt, %17, %c0_i32 : i32
24+
!CHECK-NEXT: %19 = arith.subi %c0_i32, %17 : i32
25+
!CHECK-NEXT: %20 = arith.select %18, %19, %17 : i32
26+
!CHECK-NEXT: %21 = arith.select %18, %16, %15 : i32
27+
!CHECK-NEXT: %22 = arith.select %18, %15, %16 : i32
28+
!CHECK-NEXT: %23 = arith.subi %22, %21 overflow<nuw> : i32
29+
!CHECK-NEXT: %24 = arith.divui %23, %20 : i32
30+
!CHECK-NEXT: %25 = arith.addi %24, %c1_i32 overflow<nuw> : i32
31+
!CHECK-NEXT: %26 = arith.cmpi slt, %22, %21 : i32
32+
!CHECK-NEXT: %27 = arith.select %26, %c0_i32, %25 : i32
33+
!CHECK-NEXT: %canonloop_s0 = omp.new_cli
34+
!CHECK-NEXT: omp.canonical_loop(%canonloop_s0) %iv : i32 in range(%27) {
35+
!CHECK-NEXT: %28 = arith.muli %iv, %17 : i32
36+
!CHECK-NEXT: %29 = arith.addi %15, %28 : i32
37+
!CHECK-NEXT: hlfir.assign %29 to %14#0 : i32, !fir.ref<i32>
38+
!CHECK-NEXT: %30 = fir.alloca i32 {bindc_name = "j", pinned, uniq_name = "_QFomp_unroll_heuristic_nested02Ej"}
39+
!CHECK-NEXT: %31:2 = hlfir.declare %30 {uniq_name = "_QFomp_unroll_heuristic_nested02Ej"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
40+
!CHECK-NEXT: %32 = fir.load %4#0 : !fir.ref<i32>
41+
!CHECK-NEXT: %33 = fir.load %5#0 : !fir.ref<i32>
42+
!CHECK-NEXT: %34 = fir.load %3#0 : !fir.ref<i32>
43+
!CHECK-NEXT: %c0_i32_0 = arith.constant 0 : i32
44+
!CHECK-NEXT: %c1_i32_1 = arith.constant 1 : i32
45+
!CHECK-NEXT: %35 = arith.cmpi slt, %34, %c0_i32_0 : i32
46+
!CHECK-NEXT: %36 = arith.subi %c0_i32_0, %34 : i32
47+
!CHECK-NEXT: %37 = arith.select %35, %36, %34 : i32
48+
!CHECK-NEXT: %38 = arith.select %35, %33, %32 : i32
49+
!CHECK-NEXT: %39 = arith.select %35, %32, %33 : i32
50+
!CHECK-NEXT: %40 = arith.subi %39, %38 overflow<nuw> : i32
51+
!CHECK-NEXT: %41 = arith.divui %40, %37 : i32
52+
!CHECK-NEXT: %42 = arith.addi %41, %c1_i32_1 overflow<nuw> : i32
53+
!CHECK-NEXT: %43 = arith.cmpi slt, %39, %38 : i32
54+
!CHECK-NEXT: %44 = arith.select %43, %c0_i32_0, %42 : i32
55+
!CHECK-NEXT: %canonloop_s0_s0 = omp.new_cli
56+
!CHECK-NEXT: omp.canonical_loop(%canonloop_s0_s0) %iv_2 : i32 in range(%44) {
57+
!CHECK-NEXT: %45 = arith.muli %iv_2, %34 : i32
58+
!CHECK-NEXT: %46 = arith.addi %32, %45 : i32
59+
!CHECK-NEXT: hlfir.assign %46 to %31#0 : i32, !fir.ref<i32>
60+
!CHECK-NEXT: %47 = fir.load %14#0 : !fir.ref<i32>
61+
!CHECK-NEXT: %48 = fir.load %31#0 : !fir.ref<i32>
62+
!CHECK-NEXT: %49 = arith.addi %47, %48 : i32
63+
!CHECK-NEXT: hlfir.assign %49 to %12#0 : i32, !fir.ref<i32>
64+
!CHECK-NEXT: omp.terminator
65+
!CHECK-NEXT: }
66+
!CHECK-NEXT: omp.unroll_heuristic(%canonloop_s0_s0)
67+
!CHECK-NEXT: omp.terminator
68+
!CHECK-NEXT: }
69+
!CHECK-NEXT: omp.unroll_heuristic(%canonloop_s0)
70+
!CHECK-NEXT: return

0 commit comments

Comments
 (0)