[flang-commits] [flang] [llvm] [mlir] [flang][OpenMP] Support the FULL clause on the UNROLL construct (PR #214115)
via flang-commits
flang-commits at lists.llvm.org
Tue Aug 4 18:51:47 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-flang-openmp
Author: Spencer Bryngelson (sbryngelson)
<details>
<summary>Changes</summary>
`!$omp unroll full` is parsed and then aborts in lowering with
`not yet implemented: Unhandled clause FULL in UNROLL construct`. This implements it. Fixes #<!-- -->214114.
`partial` landed in #<!-- -->206642 and bare `unroll` in #<!-- -->144785, so `full` is the remaining clause on the
construct. clang already supports `#pragma omp unroll full`, and
`OpenMPIRBuilder::unrollLoopFull` already exists, so the work is the MLIR op, its translation, and
the flang lowering.
### MLIR
Adds `omp.unroll_full`, mirroring `omp.unroll_heuristic`: one applyee, no generatee, since no loop
remains after full unrolling. The name is the one already used as an example in `CanonicalLoopOp`'s
description. Translation calls `OpenMPIRBuilder::unrollLoopFull`, which attaches
`llvm.loop.unroll.enable` and `llvm.loop.unroll.full`.
### Flang
`genUnrollOp` emits the new op instead of the TODO. Two clause rules that clang enforces and flang
did not are also added:
* `full` and `partial` are now mutually exclusive. `OMP_Unroll` listed both in
`allowedOnceClauses`; moving them to `allowedExclusiveClauses` matches how `OMP_TaskLoop` handles
`grainsize`/`num_tasks`, and keeps the at-most-once check because the checker uses
`allowedOnce | allowedExclusive` for that. Only flang reads this field, so clang is unaffected;
`clang/test/OpenMP` is clean.
* A fully unrolled loop must have a compile-time constant trip count, so its bounds and step are
required to be constant. clang has the same restriction
(`err_omp_unroll_full_variable_trip_count`). `partial` is deliberately not constrained this way.
### Testing
* `mlir/test/Dialect/OpenMP/cli-unroll-full.mlir` — round-trip, raw and pretty forms
* `mlir/test/Target/LLVMIR/openmp-unroll-full01.mlir` — translation, single loop
* `mlir/test/Target/LLVMIR/openmp-unroll-full02.mlir` — translation, inner loop of a nest
* `flang/test/Lower/OpenMP/unroll-full01.f90` — lowering emits `omp.unroll_full`
* `flang/test/Semantics/OpenMP/unroll-clauses.f90` — exclusivity, at-most-once, trip count, and the
forms that must keep working
`check-flang`, `mlir/test/Dialect/OpenMP`, `mlir/test/Target/LLVMIR`, `llvm/test/Frontend` and
`clang/test/OpenMP` all pass.
### On motivation
I came to this from a performance question and want to be accurate about what I measured, since it
does not support a codegen-speed argument upstream. On an MFC-style WENO+HLLC kernel on gfx90a,
upstream flang's existing heuristics already unroll these loops well and the directive changes
nothing measurable (0.274 ms baseline vs 0.292 ms, interleaved best-of-5). On AMD's downstream
compiler the same source is 1.28x faster with unrolling forced, because its default threshold
differs. That divergence is the argument for having the directive: it is the portable, per-loop way
to say what you mean, rather than depending on a heuristic that varies between implementations. It
is not a claim that this speeds anything up upstream.
---
Parts of this change were written or audited with Claude Code. I have reviewed all of it and take
full responsibility for the contribution. See `llvm/docs/AIToolPolicy.md`.
---
Patch is 21.00 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/214115.diff
15 Files Affected:
- (modified) flang/docs/OpenMPSupport.md (+1-1)
- (modified) flang/lib/Lower/OpenMP/ClauseProcessor.cpp (+4)
- (modified) flang/lib/Lower/OpenMP/ClauseProcessor.h (+1)
- (modified) flang/lib/Lower/OpenMP/OpenMP.cpp (+6-3)
- (modified) flang/lib/Semantics/check-omp-loop.cpp (+40)
- (modified) flang/lib/Semantics/check-omp-structure.h (+1)
- (added) flang/test/Lower/OpenMP/unroll-full01.f90 (+17)
- (added) flang/test/Semantics/OpenMP/unroll-clauses.f90 (+73)
- (modified) llvm/include/llvm/Frontend/OpenMP/OMP.td (+1-1)
- (modified) mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td (+30)
- (modified) mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp (+53)
- (modified) mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp (+22)
- (added) mlir/test/Dialect/OpenMP/cli-unroll-full.mlir (+33)
- (added) mlir/test/Target/LLVMIR/openmp-unroll-full01.mlir (+21)
- (added) mlir/test/Target/LLVMIR/openmp-unroll-full02.mlir (+26)
``````````diff
diff --git a/flang/docs/OpenMPSupport.md b/flang/docs/OpenMPSupport.md
index fd6731fd28a2b..bf5e8f519bfbd 100644
--- a/flang/docs/OpenMPSupport.md
+++ b/flang/docs/OpenMPSupport.md
@@ -179,7 +179,7 @@ Parser/Semantics, MLIR, Lowering, or the OpenMPIRBuilder.
| indirect clause on declare target | <span class="part">partial</span> | | Parser coverage exists (`flang/test/Parser/OpenMP/declare-target-indirect-tree.f90`), while lowering remains TODO-tracked (`flang/test/Lower/OpenMP/Todo/omp-clause-indirect.f90`). | [llvm/llvm-project#143505](https://github.com/llvm/llvm-project/pull/143505) |
| error directive | <span class="part">partial</span> | | Semantics coverage exists (`flang/test/Semantics/OpenMP/error.f90`), but lowering still has explicit TODO coverage (`flang/test/Lower/OpenMP/Todo/error.f90`). | [llvm/llvm-project#121509](https://github.com/llvm/llvm-project/pull/121509), [llvm/llvm-project#206175](https://github.com/llvm/llvm-project/pull/206175) |
| nothing directive | <span class="good">done</span> | | Parser and lowering coverage exists for standalone and metadirective-selected forms (`flang/test/Parser/OpenMP/nothing.f90`, `flang/test/Lower/OpenMP/nothing.f90`, `flang/test/Lower/OpenMP/metadirective-nothing.f90`). | [llvm/llvm-project#193664](https://github.com/llvm/llvm-project/pull/193664), [llvm/llvm-project#202679](https://github.com/llvm/llvm-project/pull/202679) |
-| tile and unroll constructs | <span class="part">partial</span> | | Semantics coverage exists across tile/unroll and loop-transformation tests (`flang/test/Semantics/OpenMP/tile01.f90`, `flang/test/Semantics/OpenMP/tile09.f90`, `flang/test/Semantics/OpenMP/loop-transformation-construct01.f90`), with additional lowering/transform completeness work ongoing. | [llvm/llvm-project#160298](https://github.com/llvm/llvm-project/pull/160298), [llvm/llvm-project#185296](https://github.com/llvm/llvm-project/pull/185296), [llvm/llvm-project#188025](https://github.com/llvm/llvm-project/pull/188025) |
+| tile and unroll constructs | <span class="part">partial</span> | | Semantics coverage exists across tile/unroll and loop-transformation tests (`flang/test/Semantics/OpenMP/tile01.f90`, `flang/test/Semantics/OpenMP/tile09.f90`, `flang/test/Semantics/OpenMP/loop-transformation-construct01.f90`), with additional lowering/transform completeness work ongoing. The `full` and `partial` clauses on `unroll` are lowered (`flang/test/Lower/OpenMP/unroll-full01.f90`, `flang/test/Lower/OpenMP/unroll-partial01.f90`). | [llvm/llvm-project#160298](https://github.com/llvm/llvm-project/pull/160298), [llvm/llvm-project#185296](https://github.com/llvm/llvm-project/pull/185296), [llvm/llvm-project#188025](https://github.com/llvm/llvm-project/pull/188025) |
| scope construct | <span class="part">partial</span> | | Scope construct support is available, with follow-on completeness work still in progress for some combinations (see also OpenMP 5.2 scope-related rows). | [llvm/llvm-project#113700](https://github.com/llvm/llvm-project/pull/113700), [llvm/llvm-project#193098](https://github.com/llvm/llvm-project/pull/193098) |
| assumes directives | <span class="none">unclaimed</span> | | Lowering still has explicit TODO (`flang/lib/Lower/OpenMP/OpenMP.cpp` TODO: `OpenMP ASSUMES declaration`; `flang/test/Lower/OpenMP/Todo/assumes.f90`). | [llvm/llvm-project#102008](https://github.com/llvm/llvm-project/pull/102008) |
| assume directive | <span class="none">unclaimed</span> | | Lowering still has explicit TODO (`flang/lib/Lower/OpenMP/OpenMP.cpp` TODO: `OpenMP ASSUME construct`; `flang/test/Lower/OpenMP/Todo/assume.f90`). | [llvm/llvm-project#102008](https://github.com/llvm/llvm-project/pull/102008) |
diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index 5a554def351ea..bc19844e7988b 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -842,6 +842,10 @@ bool ClauseProcessor::processOrdered(
return false;
}
+bool ClauseProcessor::processFull() const {
+ return findUniqueClause<omp::clause::Full>() != nullptr;
+}
+
bool ClauseProcessor::processPartial(std::optional<int64_t> &result) const {
if (auto *clause = findUniqueClause<omp::clause::Partial>()) {
if (clause->v.has_value())
diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.h b/flang/lib/Lower/OpenMP/ClauseProcessor.h
index cb42b6524e2e7..c542f61adb7eb 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.h
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.h
@@ -111,6 +111,7 @@ class ClauseProcessor {
mlir::omp::NumThreadsClauseOps &result) const;
bool processOrder(mlir::omp::OrderClauseOps &result) const;
bool processOrdered(mlir::omp::OrderedClauseOps &result) const;
+ bool processFull() const;
bool processPartial(std::optional<int64_t> &result) const;
bool processPriority(lower::StatementContext &stmtCtx,
mlir::omp::PriorityClauseOps &result) const;
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index ae1eb897c9348..12fbeb6b0c555 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -3409,8 +3409,8 @@ static void genUnrollOp(Fortran::lower::AbstractConverter &converter,
ClauseProcessor cp(converter, semaCtx, item->clauses);
- // The `full` clause is not yet implemented.
- cp.processTODO<clause::Full>(loc, llvm::omp::Directive::OMPD_unroll);
+ // Process the `full` clause, which requests complete unrolling.
+ bool hasFull = cp.processFull();
// Process the `partial` clause. If present, it may carry a constant unroll
// factor.
@@ -3431,7 +3431,10 @@ static void genUnrollOp(Fortran::lower::AbstractConverter &converter,
auto cli = llvm::getSingleElement(canonLoops).getCli();
- if (partialFactor.has_value()) {
+ if (hasFull) {
+ // Fully unroll the loop.
+ mlir::omp::UnrollFullOp::create(firOpBuilder, loc, cli);
+ } else if (partialFactor.has_value()) {
// Partially unroll the loop by the given constant factor.
mlir::omp::UnrollPartialOp::create(firOpBuilder, loc, cli,
static_cast<uint64_t>(*partialFactor));
diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp
index 65d097b5a31f7..ad7e088c0641c 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -443,6 +443,46 @@ void OmpStructureChecker::Enter(const parser::OpenMPLoopConstruct &x) {
beginName.v == llvm::omp::Directive::OMPD_distribute_simd) {
CheckDistLinear(x);
}
+ if (beginName.v == llvm::omp::Directive::OMPD_unroll) {
+ CheckUnrollFullTripCount(x);
+ }
+}
+
+// A loop that is fully unrolled must have a trip count that is known at compile
+// time, so its bounds and step have to be constant expressions.
+void OmpStructureChecker::CheckUnrollFullTripCount(
+ const parser::OpenMPLoopConstruct &x) {
+ const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()};
+ const parser::OmpClause *full{
+ parser::omp::FindClause(beginSpec, llvm::omp::Clause::OMPC_full)};
+ if (!full) {
+ return;
+ }
+
+ for (const parser::ExecutionPartConstruct &construct :
+ std::get<parser::Block>(x.t)) {
+ const parser::DoConstruct *doConstruct{
+ parser::omp::GetDoConstruct(construct)};
+ if (!doConstruct) {
+ continue;
+ }
+ const auto &control{doConstruct->GetLoopControl()};
+ if (!control) {
+ continue;
+ }
+ const auto *bounds{std::get_if<parser::LoopControl::Bounds>(&control->u)};
+ if (!bounds) {
+ continue;
+ }
+ bool isConstant{GetIntValue(bounds->Lower()).has_value() &&
+ GetIntValue(bounds->Upper()).has_value() &&
+ (!bounds->Step() || GetIntValue(*bounds->Step()).has_value())};
+ if (!isConstant) {
+ context_.Say(full->source,
+ "The loop associated with an UNROLL directive with a FULL clause must have a constant trip count"_err_en_US);
+ }
+ break;
+ }
}
const parser::Name OmpStructureChecker::GetLoopIndex(
diff --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h
index 4acec2ad05ba1..c732463b67742 100644
--- a/flang/lib/Semantics/check-omp-structure.h
+++ b/flang/lib/Semantics/check-omp-structure.h
@@ -303,6 +303,7 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
void CheckAssociatedLoopConstraints(const parser::OpenMPLoopConstruct &x);
void CheckScanModifier(const parser::OmpClause::Reduction &x);
void CheckDistLinear(const parser::OpenMPLoopConstruct &x);
+ void CheckUnrollFullTripCount(const parser::OpenMPLoopConstruct &x);
void BeginMetadirectiveVariantScope();
void EndMetadirectiveVariantScope();
diff --git a/flang/test/Lower/OpenMP/unroll-full01.f90 b/flang/test/Lower/OpenMP/unroll-full01.f90
new file mode 100644
index 0000000000000..c8741bf7314cf
--- /dev/null
+++ b/flang/test/Lower/OpenMP/unroll-full01.f90
@@ -0,0 +1,17 @@
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=51 -o - %s | FileCheck %s
+
+subroutine test_unroll_full
+ integer res, i
+
+ !$omp unroll full
+ do i = 1, 100
+ res = i
+ end do
+ !$omp end unroll
+end subroutine test_unroll_full
+
+! CHECK-LABEL: func.func @_QPtest_unroll_full() {
+! CHECK: %[[CLI:.+]] = omp.new_cli
+! CHECK: omp.canonical_loop(%[[CLI]]) %{{.*}} : i32 in range(%{{.*}}) {
+! CHECK: omp.unroll_full(%[[CLI]])
+! CHECK: }
diff --git a/flang/test/Semantics/OpenMP/unroll-clauses.f90 b/flang/test/Semantics/OpenMP/unroll-clauses.f90
new file mode 100644
index 0000000000000..68dd47ce29b92
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/unroll-clauses.f90
@@ -0,0 +1,73 @@
+! Testing the Semantics of the FULL and PARTIAL clauses on the UNROLL directive
+
+!RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=51
+
+subroutine unroll_clauses
+ implicit none
+ integer, parameter :: n = 8
+ integer :: i
+ integer :: v(n)
+
+ !ERROR: FULL and PARTIAL clauses are mutually exclusive and may not appear on the same UNROLL directive
+ !$omp unroll full partial(2)
+ do i = 1, n
+ v(i) = i
+ end do
+ !$omp end unroll
+
+ !ERROR: At most one FULL clause can appear on UNROLL directive
+ !$omp unroll full full
+ do i = 1, n
+ v(i) = i
+ end do
+ !$omp end unroll
+
+ ! Each clause on its own is accepted.
+ !$omp unroll full
+ do i = 1, n
+ v(i) = i
+ end do
+ !$omp end unroll
+
+ !$omp unroll partial(2)
+ do i = 1, n
+ v(i) = i
+ end do
+ !$omp end unroll
+end subroutine
+
+subroutine unroll_full_trip_count(m, s)
+ implicit none
+ integer, parameter :: n = 8
+ integer :: m, s, i
+ integer :: v(n)
+
+ ! A fully unrolled loop must have a trip count known at compile time.
+ !ERROR: The loop associated with an UNROLL directive with a FULL clause must have a constant trip count
+ !$omp unroll full
+ do i = 1, m
+ v(1) = i
+ end do
+ !$omp end unroll
+
+ !ERROR: The loop associated with an UNROLL directive with a FULL clause must have a constant trip count
+ !$omp unroll full
+ do i = 1, n, s
+ v(1) = i
+ end do
+ !$omp end unroll
+
+ ! Constant bounds are fine, including named constants.
+ !$omp unroll full
+ do i = 1, n
+ v(i) = i
+ end do
+ !$omp end unroll
+
+ ! PARTIAL places no such requirement on the loop.
+ !$omp unroll partial(2)
+ do i = 1, m
+ v(1) = i
+ end do
+ !$omp end unroll
+end subroutine
diff --git a/llvm/include/llvm/Frontend/OpenMP/OMP.td b/llvm/include/llvm/Frontend/OpenMP/OMP.td
index a119dff67c30e..db20e5a840caf 100644
--- a/llvm/include/llvm/Frontend/OpenMP/OMP.td
+++ b/llvm/include/llvm/Frontend/OpenMP/OMP.td
@@ -1477,7 +1477,7 @@ def OMP_Unroll : Directive<[Spelling<"unroll">]> {
let allowedClauses = [
VersionedClause<OMPC_Apply, 60>,
];
- let allowedOnceClauses = [
+ let allowedExclusiveClauses = [
VersionedClause<OMPC_Full, 51>,
VersionedClause<OMPC_Partial, 51>,
];
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
index 70597b85902c5..5b7f38eb6771a 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -557,6 +557,36 @@ def UnrollHeuristicOp : OpenMPTransform_Op<"unroll_heuristic", []> {
let hasCustomAssemblyFormat = 1;
}
+//===----------------------------------------------------------------------===//
+// OpenMP unroll_full operation
+//===----------------------------------------------------------------------===//
+
+def UnrollFullOp : OpenMPTransform_Op<"unroll_full", []> {
+ let summary = "OpenMP full unroll operation";
+ let description = [{
+ Represents a `#pragma omp unroll full` construct introduced in OpenMP 5.1.
+
+ The operation has one applyee and no generatees. The applyee is fully
+ unrolled, so it must have a compile-time constant trip count. Because no
+ loop remains afterwards, there is no generatee and no further
+ loop-associated transformation can be chained onto it.
+
+ The lowering is implemented using `OpenMPIRBuilder::unrollLoopFull`.
+
+ Assembly formats:
+ omp.unroll_full(%cli)
+ omp.unroll_full(%cli) -> ()
+ }];
+
+ let arguments = (ins CanonicalLoopInfoType:$applyee);
+
+ let builders = [
+ OpBuilder<(ins "::mlir::Value":$cli)>,
+ ];
+
+ let hasCustomAssemblyFormat = 1;
+}
+
//===----------------------------------------------------------------------===//
// OpenMP unroll_partial operation
//===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
index 8605b39f17c16..d470627335e1a 100644
--- a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
+++ b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
@@ -4347,6 +4347,59 @@ UnrollHeuristicOp::getGenerateesODSOperandIndexAndLength() {
return {0, 0};
}
+//===----------------------------------------------------------------------===//
+// UnrollFullOp
+//===----------------------------------------------------------------------===//
+
+void UnrollFullOp::build(::mlir::OpBuilder &odsBuilder,
+ ::mlir::OperationState &odsState, ::mlir::Value cli) {
+ odsState.addOperands(cli);
+}
+
+void UnrollFullOp::print(OpAsmPrinter &p) {
+ p << '(' << getApplyee() << ')';
+
+ p.printOptionalAttrDict((*this)->getAttrs());
+}
+
+mlir::ParseResult UnrollFullOp::parse(::mlir::OpAsmParser &parser,
+ ::mlir::OperationState &result) {
+ auto cliType = CanonicalLoopInfoType::get(parser.getContext());
+
+ if (parser.parseLParen())
+ return failure();
+
+ OpAsmParser::UnresolvedOperand applyee;
+ if (parser.parseOperand(applyee) ||
+ parser.resolveOperand(applyee, cliType, result.operands))
+ return failure();
+
+ if (parser.parseRParen())
+ return failure();
+
+ // Optional output loop; full unrolling has none.
+ if (!parser.parseOptionalArrow()) {
+ if (parser.parseLParen() || parser.parseRParen())
+ return failure();
+ }
+
+ // Parse the optional attribute list.
+ if (parser.parseOptionalAttrDict(result.attributes))
+ return failure();
+
+ return mlir::success();
+}
+
+std::pair<unsigned, unsigned>
+UnrollFullOp::getApplyeesODSOperandIndexAndLength() {
+ return getODSOperandIndexAndLength(odsIndex_applyee);
+}
+
+std::pair<unsigned, unsigned>
+UnrollFullOp::getGenerateesODSOperandIndexAndLength() {
+ return {0, 0};
+}
+
//===----------------------------------------------------------------------===//
// UnrollPartialOp
//===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 1c50ff192c3d5..b817079072965 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -5379,6 +5379,25 @@ applyUnrollHeuristic(omp::UnrollHeuristicOp op, llvm::IRBuilderBase &builder,
return success();
}
+/// Apply a `#pragma omp unroll full` / `!$omp unroll full` transformation
+/// using the OpenMPIRBuilder.
+static LogicalResult
+applyUnrollFull(omp::UnrollFullOp op, llvm::IRBuilderBase &builder,
+ LLVM::ModuleTranslation &moduleTranslation) {
+ llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
+
+ Value applyee = op.getApplyee();
+ assert(applyee && "Loop to apply unrolling on required");
+
+ llvm::CanonicalLoopInfo *consBuilderCLI =
+ moduleTranslation.lookupOMPLoop(applyee);
+ llvm::OpenMPIRBuilder::LocationDescription loc(builder);
+ ompBuilder->unrollLoopFull(loc.DL, consBuilderCLI);
+
+ moduleTranslation.invalidateOmpLoop(applyee);
+ return success();
+}
+
/// Apply a `#pragma omp unroll partial` / `!$omp unroll partial`
/// transformation using the OpenMPIRBuilder.
static LogicalResult
@@ -10040,6 +10059,9 @@ LogicalResult OpenMPDialectLLVMIRTranslationInterface::convertOperation(
// the omp.canonical_loop.
return applyUnrollHeuristic(op, builder, moduleTranslation);
})
+ .Case([&](omp::UnrollFullOp op) {
+ return applyUnrollFull(op, builder, moduleTranslation);
+ })
.Case([&](omp::UnrollPartialOp op) {
return applyUnrollPartial(op, builder, moduleTranslation);
})
diff --git a/mlir/test/Dialect/OpenMP/cli-unroll-full.mlir b/mlir/test/Dialect/OpenMP/cli-unroll-full.mlir
new file mode 100644
index 0000000000000..b52635b1f194d
--- /dev/null
+++ b/mlir/test/Dialect/OpenMP/cli-unroll-full.mlir
@@ -0,0 +1,33 @@
+// RUN: mlir-opt %s | FileCheck %s --enable-var-scope
+// RUN: mlir-opt %s | mlir-opt | FileCheck %s --enable-var-scope
+
+
+// CHECK-LABEL: @omp_unroll_full_raw(
+// CHECK-SAME: %[[tc:.+]]: i32) {
+func.func @omp_unroll_full_raw(%tc : i32) -> () {
+ // CHECK-NEXT: %canonloop = omp.new_cli
+ %canonloop = "omp.new_cli" () : () -> (!omp.cli)
+ // CHECK-NEXT: omp.canonical_loop(%canonloop) %iv : i32 in range(%[[tc]]) {
+ "omp.canonical_loop" (%tc, %canonloop) ({
+ ^bb0(%iv: i32):
+ omp.terminator
+ }) : (i32, !omp.cli) -> ()
+ // CHECK: omp.unroll_full(%canonloop)
+ "omp.unroll_full" (%canonloop) : (!omp.cli) -> ()
+ return
+}
+
+
+// CHECK-LABEL: @omp_unroll_full_pretty(
+// CHECK-SAME: %[[tc:.+]]: i32) {
+func.func @omp_unroll_full_pretty(%tc : i32) -> () {
+ // CHECK-NEXT: %[[CANONLOOP:.+]] = omp.new_cli
+ %canonloop = omp.new_cli
+ // CHECK-NEXT: omp.canonical_loop(%canonloop) %iv : i32 in range(%[[tc]]) {
+ omp.canonical_loop(%canonloop) %iv : i32 in range(%tc) {
+ omp.terminator
+ }
+ // CHECK: omp.unroll_full(%canonloop)
+ omp.unroll_full(%canonloop)
+ return
+}
diff --git a/mlir/test/Target/LLVMIR/openmp-unroll-full01.mlir b/mlir/test/Target/LLVMIR/openmp-unroll-full01.mlir
new file mode 100644
index 0000000000000..97ae7a12e9861
--- /dev/null
+++ b/mlir/test/Target/LLVMIR/openmp-unroll-full01.mlir
@@ -0,0 +1,21 @@
+// Test lowering of omp.unroll_full (single loop)
+// RUN: mlir-translate -mlir-to-llvmir %s | FileCheck %s
+
+llvm.func @unroll_full_trivial_loop(%baseptr: !llvm.ptr, %tc: i32) -> () {
+ %literal_cli = omp.new_cli
+ omp.canonical_loop(%literal_cli) %iv : i32 in range(%tc) {
+ %ptr = llvm.getelementptr inbounds %baseptr[%iv] : (!llvm.ptr, i32) -> !llvm.ptr, f32
+ %val = llvm.mlir.constant(42.0 : f32) : f32
+ llvm.store %val, %ptr : f32, !llvm.ptr
+ omp.terminator
+ }
+ omp.unroll_full(%literal_cli)
+ llvm.return
+}
+
+// CHECK-LABEL: define void @unroll_full_trivial_loop(
+// The loop is marked for full unrolling; LLVM's LoopUnroll pass performs it.
+// CHECK: br label %omp_omp.loop.header, !llvm.loop ![[MD:[0-9]+]]
+// CHECK: ![[MD]] = distinct !{![[MD]], ![[ENABLE:[0-9]+]], ![[FULL:[0-9]+]]}
+// CHECK-DAG: ![[ENABLE]] = !{!"llvm.loop.unroll.enable"}
+// CHECK-DAG: ![[FULL]] = !{!"llvm.loop.unroll.full"}
diff --git a/mlir/test/Target/LLVMIR/openmp-unroll-full02.mlir b/mlir/test/Target/LLVMIR/openmp-unroll-full02.mlir
new file mode 100644
index 0000000000000..ed20ac2fdfe6f
--- /dev/null
+++ b/mlir/test/Target/LLVMIR/openmp-unroll-full02.mlir
@@ -0,0 +1,26 @@
+// Test lowering of omp.unroll_full applie...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/214115
More information about the flang-commits
mailing list