[Mlir-commits] [flang] [mlir] [Flang][OpenACC] add loop annotations to acc.loop (PR #216769)
Scott Manley
llvmlistbot at llvm.org
Tue Aug 18 06:21:05 PDT 2026
https://github.com/rscottmanley updated https://github.com/llvm/llvm-project/pull/216769
>From 57315b4d852fa854d65ec149a216ef527b1675af Mon Sep 17 00:00:00 2001
From: Scott Manley <scmanley at nvidia.com>
Date: Mon, 17 Aug 2026 09:45:22 -0700
Subject: [PATCH 1/5] [Flang][OpenACC] add loop annotations to acc.loop
Add loop annotations that come from directives to acc.loop just like
other loops
---
flang/docs/Directives.md | 5 +-
.../flang/Lower/Support/LoopAnnotation.h | 36 +++
flang/lib/Lower/Bridge.cpp | 205 ++++--------------
flang/lib/Lower/CMakeLists.txt | 1 +
flang/lib/Lower/OpenACC.cpp | 52 ++++-
flang/lib/Lower/Support/LoopAnnotation.cpp | 156 +++++++++++++
.../OpenACC/acc-loop-directive-annotation.f90 | 17 ++
.../OpenACC/Utils/OpenACCUtilsTiling.cpp | 2 +
8 files changed, 309 insertions(+), 165 deletions(-)
create mode 100644 flang/include/flang/Lower/Support/LoopAnnotation.h
create mode 100644 flang/lib/Lower/Support/LoopAnnotation.cpp
create mode 100644 flang/test/Lower/OpenACC/acc-loop-directive-annotation.f90
diff --git a/flang/docs/Directives.md b/flang/docs/Directives.md
index 45080acb778e3..e74db041d01f3 100644
--- a/flang/docs/Directives.md
+++ b/flang/docs/Directives.md
@@ -171,8 +171,9 @@ directives. Directives on loops are used to specify additional transformation to
be performed by the compiler like enabling vectorisation, unrolling, interchange
etc.
-Currently loop directives are not accepted in the presence of OpenMP or OpenACC
-constructs on the loop. This should be implemented as it is used in some
+Currently loop directives are accepted in the presence of OpenACC loop and
+combined constructs on the loop. Loop directives with OpenMP constructs on the
+loop are not yet accepted. This should be implemented as it is used in some
applications.
### Array Expressions
diff --git a/flang/include/flang/Lower/Support/LoopAnnotation.h b/flang/include/flang/Lower/Support/LoopAnnotation.h
new file mode 100644
index 0000000000000..9ff1ec5c521fe
--- /dev/null
+++ b/flang/include/flang/Lower/Support/LoopAnnotation.h
@@ -0,0 +1,36 @@
+//===-- Lower/Support/LoopAnnotation.h -- loop annotation attrs -*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Helpers to lower Fortran `!dir$` loop directives to LLVM LoopAnnotationAttr.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef FORTRAN_LOWER_SUPPORT_LOOPANNOTATION_H
+#define FORTRAN_LOWER_SUPPORT_LOOPANNOTATION_H
+
+#include "mlir/Dialect/LLVMIR/LLVMAttrs.h"
+#include "llvm/ADT/ArrayRef.h"
+
+namespace Fortran {
+namespace parser {
+struct CompilerDirective;
+} // namespace parser
+
+namespace lower {
+
+/// Build an LLVM loop annotation attribute from Fortran compiler directives
+/// associated with a loop. Returns null if \p dirs does not contain any
+/// recognized loop directives.
+mlir::LLVM::LoopAnnotationAttr genLoopAnnotationAttr(
+ mlir::MLIRContext *context,
+ llvm::ArrayRef<const Fortran::parser::CompilerDirective *> dirs);
+
+} // namespace lower
+} // namespace Fortran
+
+#endif // FORTRAN_LOWER_SUPPORT_LOOPANNOTATION_H
diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index bff6b51e50e18..a0f39d51727d0 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -30,6 +30,7 @@
#include "flang/Lower/PFTBuilder.h"
#include "flang/Lower/Runtime.h"
#include "flang/Lower/StatementContext.h"
+#include "flang/Lower/Support/LoopAnnotation.h"
#include "flang/Lower/Support/ReductionProcessor.h"
#include "flang/Lower/Support/Utils.h"
#include "flang/Optimizer/Builder/BoxValue.h"
@@ -2859,164 +2860,21 @@ class FirConverter : public Fortran::lower::AbstractConverter {
// For unroll directives without a value, force full unrolling.
// For unroll directives with a value, if the value is greater than 1,
// force unrolling with the given factor. Otherwise, disable unrolling.
- mlir::LLVM::LoopUnrollAttr
- genLoopUnrollAttr(std::optional<std::uint64_t> directiveArg) {
- mlir::BoolAttr falseAttr =
- mlir::BoolAttr::get(builder->getContext(), false);
- mlir::BoolAttr trueAttr = mlir::BoolAttr::get(builder->getContext(), true);
- mlir::IntegerAttr countAttr;
- mlir::BoolAttr fullUnrollAttr;
- bool shouldUnroll = true;
- if (directiveArg.has_value()) {
- auto unrollingFactor = directiveArg.value();
- if (unrollingFactor == 0 || unrollingFactor == 1) {
- shouldUnroll = false;
- } else {
- countAttr =
- builder->getIntegerAttr(builder->getI64Type(), unrollingFactor);
- }
- } else {
- fullUnrollAttr = trueAttr;
- }
-
- mlir::BoolAttr disableAttr = shouldUnroll ? falseAttr : trueAttr;
- return mlir::LLVM::LoopUnrollAttr::get(
- builder->getContext(), /*disable=*/disableAttr, /*count=*/countAttr, {},
- /*full=*/fullUnrollAttr, {}, {}, {});
- }
-
- // Enabling unroll and jamming directive without a value.
- // For directives with a value, if the value is greater than 1,
- // force unrolling with the given factor. Otherwise, disable unrolling and
- // jamming.
- mlir::LLVM::LoopUnrollAndJamAttr
- genLoopUnrollAndJamAttr(std::optional<std::uint64_t> count) {
- mlir::BoolAttr falseAttr =
- mlir::BoolAttr::get(builder->getContext(), false);
- mlir::BoolAttr trueAttr = mlir::BoolAttr::get(builder->getContext(), true);
- mlir::IntegerAttr countAttr;
- bool shouldUnroll = true;
- if (count.has_value()) {
- auto unrollingFactor = count.value();
- if (unrollingFactor == 0 || unrollingFactor == 1) {
- shouldUnroll = false;
- } else {
- countAttr =
- builder->getIntegerAttr(builder->getI64Type(), unrollingFactor);
- }
- }
-
- mlir::BoolAttr disableAttr = shouldUnroll ? falseAttr : trueAttr;
- return mlir::LLVM::LoopUnrollAndJamAttr::get(
- builder->getContext(), /*disable=*/disableAttr, /*count*/ countAttr, {},
- {}, {}, {}, {});
- }
-
- // Enabling loop vectorization attribute.
- mlir::LLVM::LoopVectorizeAttr
- genLoopVectorizeAttr(mlir::BoolAttr disableAttr,
- mlir::BoolAttr scalableEnable,
- mlir::IntegerAttr vectorWidth) {
- mlir::LLVM::LoopVectorizeAttr va;
- if (disableAttr)
- va = mlir::LLVM::LoopVectorizeAttr::get(
- builder->getContext(),
- /*disable=*/disableAttr, /*predicate=*/{},
- /*scalableEnable=*/scalableEnable,
- /*vectorWidth=*/vectorWidth, {}, {}, {});
- return va;
- }
-
+ // Enabling unroll and jamming / vectorization is handled in
+ // genLoopAnnotationAttr.
void addLoopAnnotationAttr(
IncrementLoopInfo &info,
llvm::SmallVectorImpl<const Fortran::parser::CompilerDirective *> &dirs) {
- mlir::BoolAttr disableVecAttr;
- mlir::BoolAttr scalableEnable;
- mlir::IntegerAttr vectorWidth;
- mlir::LLVM::LoopUnrollAttr ua;
- mlir::LLVM::LoopUnrollAndJamAttr uja;
- llvm::SmallVector<mlir::LLVM::AccessGroupAttr> aga;
- bool has_attrs = false;
- for (const auto *dir : dirs) {
- Fortran::common::visit(
- Fortran::common::visitors{
- [&](const Fortran::parser::CompilerDirective::VectorAlways &) {
- disableVecAttr =
- mlir::BoolAttr::get(builder->getContext(), false);
- has_attrs = true;
- },
- [&](const Fortran::parser::CompilerDirective::VectorLength &vl) {
- using Kind =
- Fortran::parser::CompilerDirective::VectorLength::Kind;
- Kind kind = std::get<Kind>(vl.t);
- uint64_t length = std::get<uint64_t>(vl.t);
- disableVecAttr =
- mlir::BoolAttr::get(builder->getContext(), false);
- if (length != 0)
- vectorWidth =
- builder->getIntegerAttr(builder->getI64Type(), length);
- switch (kind) {
- case Kind::Scalable:
- scalableEnable =
- mlir::BoolAttr::get(builder->getContext(), true);
- break;
- case Kind::Fixed:
- scalableEnable =
- mlir::BoolAttr::get(builder->getContext(), false);
- break;
- case Kind::Auto:
- break;
- }
- has_attrs = true;
- },
- [&](const Fortran::parser::CompilerDirective::Unroll &u) {
- ua = genLoopUnrollAttr(u.v);
- has_attrs = true;
- },
- [&](const Fortran::parser::CompilerDirective::UnrollAndJam &u) {
- uja = genLoopUnrollAndJamAttr(u.v);
- has_attrs = true;
- },
- [&](const Fortran::parser::CompilerDirective::NoVector &u) {
- disableVecAttr =
- mlir::BoolAttr::get(builder->getContext(), true);
- has_attrs = true;
- },
- [&](const Fortran::parser::CompilerDirective::NoUnroll &u) {
- ua = genLoopUnrollAttr(/*unrollingFactor=*/0);
- has_attrs = true;
- },
- [&](const Fortran::parser::CompilerDirective::NoUnrollAndJam &u) {
- uja = genLoopUnrollAndJamAttr(/*unrollingFactor=*/0);
- has_attrs = true;
- },
- [&](const Fortran::parser::CompilerDirective::IVDep &iv) {
- aga.push_back(
- mlir::LLVM::AccessGroupAttr::get(builder->getContext()));
- has_attrs = true;
- },
- [&](const Fortran::parser::CompilerDirective::Simd &simd) {
- disableVecAttr =
- mlir::BoolAttr::get(builder->getContext(), false);
- has_attrs = true;
- },
- [&](const auto &) {}},
- dir->u);
- }
- mlir::LLVM::LoopVectorizeAttr va =
- genLoopVectorizeAttr(disableVecAttr, scalableEnable, vectorWidth);
- mlir::LLVM::LoopAnnotationAttr la = mlir::LLVM::LoopAnnotationAttr::get(
- builder->getContext(), {}, /*vectorize=*/va, {}, /*unroll*/ ua,
- /*unroll_and_jam*/ uja, {}, {}, {}, {}, {}, {}, {}, {}, {},
- /*parallelAccesses*/ aga);
- if (has_attrs) {
- if (auto loopOp = mlir::dyn_cast<fir::DoLoopOp>(info.loopOp))
- loopOp.setLoopAnnotationAttr(la);
+ mlir::LLVM::LoopAnnotationAttr la =
+ Fortran::lower::genLoopAnnotationAttr(builder->getContext(), dirs);
+ if (!la)
+ return;
+ if (auto loopOp = mlir::dyn_cast<fir::DoLoopOp>(info.loopOp))
+ loopOp.setLoopAnnotationAttr(la);
- if (auto doConcurrentOp =
- mlir::dyn_cast<fir::DoConcurrentLoopOp>(info.loopOp))
- doConcurrentOp.setLoopAnnotationAttr(la);
- }
+ if (auto doConcurrentOp =
+ mlir::dyn_cast<fir::DoConcurrentLoopOp>(info.loopOp))
+ doConcurrentOp.setLoopAnnotationAttr(la);
}
/// Generate FIR to begin a structured or unstructured increment loop nest.
@@ -3548,11 +3406,44 @@ class FirConverter : public Fortran::lower::AbstractConverter {
void attachDirectiveToLoop(const Fortran::parser::CompilerDirective &dir,
Fortran::lower::pft::Evaluation *e) {
- while (e->isDirective())
+ auto attachToDoStmt = [&](Fortran::lower::pft::Evaluation *doEval) {
+ if (doEval && doEval->isA<Fortran::parser::NonLabelDoStmt>())
+ doEval->dirs.push_back(&dir);
+ };
+
+ // Dive into an OpenACC loop/combined construct to find its NonLabelDoStmt.
+ auto findDoStmtInOpenACC = [](Fortran::lower::pft::Evaluation *eval)
+ -> Fortran::lower::pft::Evaluation * {
+ const auto *acc = eval->getIf<Fortran::parser::OpenACCConstruct>();
+ if (!acc)
+ return nullptr;
+ if (!std::holds_alternative<Fortran::parser::OpenACCLoopConstruct>(
+ acc->u) &&
+ !std::holds_alternative<Fortran::parser::OpenACCCombinedConstruct>(
+ acc->u))
+ return nullptr;
+ if (!eval->hasNestedEvaluations())
+ return nullptr;
+ for (Fortran::lower::pft::Evaluation &nested :
+ eval->getNestedEvaluations()) {
+ if (nested.isA<Fortran::parser::DoConstruct>() &&
+ nested.hasNestedEvaluations())
+ return &nested.getFirstNestedEvaluation();
+ }
+ return nullptr;
+ };
+
+ while (e->isDirective()) {
+ if (Fortran::lower::pft::Evaluation *doStmt = findDoStmtInOpenACC(e)) {
+ attachToDoStmt(doStmt);
+ return;
+ }
e = e->lexicalSuccessor;
+ if (!e)
+ return;
+ }
- if (e->isA<Fortran::parser::NonLabelDoStmt>())
- e->dirs.push_back(&dir);
+ attachToDoStmt(e);
}
void markCurrentFuncAsAlwaysInline(
diff --git a/flang/lib/Lower/CMakeLists.txt b/flang/lib/Lower/CMakeLists.txt
index 584e4a28e5641..246b6e2f5adff 100644
--- a/flang/lib/Lower/CMakeLists.txt
+++ b/flang/lib/Lower/CMakeLists.txt
@@ -30,6 +30,7 @@ add_flang_library(FortranLower
OpenMP/Utils.cpp
PFTBuilder.cpp
Runtime.cpp
+ Support/LoopAnnotation.cpp
Support/PrivateReductionUtils.cpp
Support/ReductionProcessor.cpp
Support/Utils.cpp
diff --git a/flang/lib/Lower/OpenACC.cpp b/flang/lib/Lower/OpenACC.cpp
index 2ea3659e8064f..73f1c18bfaac2 100644
--- a/flang/lib/Lower/OpenACC.cpp
+++ b/flang/lib/Lower/OpenACC.cpp
@@ -20,6 +20,7 @@
#include "flang/Lower/Mangler.h"
#include "flang/Lower/PFTBuilder.h"
#include "flang/Lower/StatementContext.h"
+#include "flang/Lower/Support/LoopAnnotation.h"
#include "flang/Lower/Support/Utils.h"
#include "flang/Lower/SymbolMap.h"
#include "flang/Optimizer/Builder/BoxValue.h"
@@ -2235,6 +2236,32 @@ static bool hasEarlyReturn(Fortran::lower::pft::Evaluation &eval) {
return hasReturnStmt;
}
+/// Return the NonLabelDoStmt evaluation associated with an OpenACC loop or
+/// a DoConstruct being lowered as an acc.loop.
+static Fortran::lower::pft::Evaluation *
+getAccLoopDoStmtEval(Fortran::lower::pft::Evaluation &eval) {
+ Fortran::lower::pft::Evaluation *e = &eval;
+ if (e->isA<Fortran::parser::OpenACCConstruct>()) {
+ if (!e->hasNestedEvaluations())
+ return nullptr;
+ e = nullptr;
+ for (Fortran::lower::pft::Evaluation &nested :
+ eval.getNestedEvaluations()) {
+ if (nested.isA<Fortran::parser::DoConstruct>()) {
+ e = &nested;
+ break;
+ }
+ }
+ if (!e)
+ return nullptr;
+ }
+ if (e->isA<Fortran::parser::DoConstruct>() && e->hasNestedEvaluations())
+ return &e->getFirstNestedEvaluation();
+ if (e->isA<Fortran::parser::NonLabelDoStmt>())
+ return e;
+ return nullptr;
+}
+
static mlir::acc::LoopOp createLoopOp(
Fortran::lower::AbstractConverter &converter,
mlir::Location currentLocation,
@@ -2503,12 +2530,15 @@ static mlir::acc::LoopOp createLoopOp(
loopOp.setCombinedAttr(mlir::acc::CombinedConstructsTypeAttr::get(
builder.getContext(), *combinedConstructs));
- // TODO: retrieve directives from NonLabelDoStmt pft::Evaluation, and add them
- // as attribute to the acc.loop as an extra attribute. It is not quite clear
- // how useful these $dir are in acc contexts, but they could still provide
- // more information about the loop acc codegen. They can be obtained by
- // looking for the first lexicalSuccessor of eval that is a NonLabelDoStmt,
- // and using the related `dirs` member.
+ // Apply `!dir$` loop directives associated with the DO statement as a
+ // discardable LLVM loop annotation attribute on the acc.loop.
+ if (Fortran::lower::pft::Evaluation *doStmtEval =
+ getAccLoopDoStmtEval(eval)) {
+ if (mlir::LLVM::LoopAnnotationAttr la =
+ Fortran::lower::genLoopAnnotationAttr(builder.getContext(),
+ doStmtEval->dirs))
+ loopOp->setDiscardableAttr("loopAnnotation", la);
+ }
return loopOp;
}
@@ -5436,5 +5466,15 @@ mlir::Operation *Fortran::lower::genOpenACCLoopFromDoConstruct(
llvm_unreachable("Unexpected loop par mode");
}
+ // Apply `!dir$` loop directives associated with the DO statement as a
+ // discardable LLVM loop annotation attribute on the acc.loop.
+ if (Fortran::lower::pft::Evaluation *doStmtEval =
+ getAccLoopDoStmtEval(eval)) {
+ if (mlir::LLVM::LoopAnnotationAttr la =
+ Fortran::lower::genLoopAnnotationAttr(builder.getContext(),
+ doStmtEval->dirs))
+ loopOp->setDiscardableAttr("loopAnnotation", la);
+ }
+
return loopOp;
}
diff --git a/flang/lib/Lower/Support/LoopAnnotation.cpp b/flang/lib/Lower/Support/LoopAnnotation.cpp
new file mode 100644
index 0000000000000..976d7495e78dc
--- /dev/null
+++ b/flang/lib/Lower/Support/LoopAnnotation.cpp
@@ -0,0 +1,156 @@
+//===-- Lower/Support/LoopAnnotation.cpp ----------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "flang/Lower/Support/LoopAnnotation.h"
+#include "flang/Common/idioms.h"
+#include "flang/Parser/parse-tree.h"
+#include "mlir/IR/BuiltinAttributes.h"
+
+static mlir::LLVM::LoopUnrollAttr
+genLoopUnrollAttr(mlir::MLIRContext *context,
+ std::optional<std::uint64_t> directiveArg) {
+ mlir::BoolAttr falseAttr = mlir::BoolAttr::get(context, false);
+ mlir::BoolAttr trueAttr = mlir::BoolAttr::get(context, true);
+ mlir::IntegerAttr countAttr;
+ mlir::BoolAttr fullUnrollAttr;
+ bool shouldUnroll = true;
+ if (directiveArg.has_value()) {
+ auto unrollingFactor = directiveArg.value();
+ if (unrollingFactor == 0 || unrollingFactor == 1) {
+ shouldUnroll = false;
+ } else {
+ countAttr = mlir::IntegerAttr::get(mlir::IntegerType::get(context, 64),
+ unrollingFactor);
+ }
+ } else {
+ fullUnrollAttr = trueAttr;
+ }
+
+ mlir::BoolAttr disableAttr = shouldUnroll ? falseAttr : trueAttr;
+ return mlir::LLVM::LoopUnrollAttr::get(context, /*disable=*/disableAttr,
+ /*count=*/countAttr, {},
+ /*full=*/fullUnrollAttr, {}, {}, {});
+}
+
+static mlir::LLVM::LoopUnrollAndJamAttr
+genLoopUnrollAndJamAttr(mlir::MLIRContext *context,
+ std::optional<std::uint64_t> count) {
+ mlir::BoolAttr falseAttr = mlir::BoolAttr::get(context, false);
+ mlir::BoolAttr trueAttr = mlir::BoolAttr::get(context, true);
+ mlir::IntegerAttr countAttr;
+ bool shouldUnroll = true;
+ if (count.has_value()) {
+ auto unrollingFactor = count.value();
+ if (unrollingFactor == 0 || unrollingFactor == 1) {
+ shouldUnroll = false;
+ } else {
+ countAttr = mlir::IntegerAttr::get(mlir::IntegerType::get(context, 64),
+ unrollingFactor);
+ }
+ }
+
+ mlir::BoolAttr disableAttr = shouldUnroll ? falseAttr : trueAttr;
+ return mlir::LLVM::LoopUnrollAndJamAttr::get(context, /*disable=*/disableAttr,
+ /*count*/ countAttr, {}, {}, {},
+ {}, {});
+}
+
+static mlir::LLVM::LoopVectorizeAttr
+genLoopVectorizeAttr(mlir::MLIRContext *context, mlir::BoolAttr disableAttr,
+ mlir::BoolAttr scalableEnable,
+ mlir::IntegerAttr vectorWidth) {
+ mlir::LLVM::LoopVectorizeAttr va;
+ if (disableAttr)
+ va = mlir::LLVM::LoopVectorizeAttr::get(context,
+ /*disable=*/disableAttr,
+ /*predicate=*/{},
+ /*scalableEnable=*/scalableEnable,
+ /*vectorWidth=*/vectorWidth, {}, {},
+ {});
+ return va;
+}
+
+mlir::LLVM::LoopAnnotationAttr Fortran::lower::genLoopAnnotationAttr(
+ mlir::MLIRContext *context,
+ llvm::ArrayRef<const Fortran::parser::CompilerDirective *> dirs) {
+ mlir::BoolAttr disableVecAttr;
+ mlir::BoolAttr scalableEnable;
+ mlir::IntegerAttr vectorWidth;
+ mlir::LLVM::LoopUnrollAttr ua;
+ mlir::LLVM::LoopUnrollAndJamAttr uja;
+ llvm::SmallVector<mlir::LLVM::AccessGroupAttr> aga;
+ bool hasAttrs = false;
+ for (const auto *dir : dirs) {
+ Fortran::common::visit(
+ Fortran::common::visitors{
+ [&](const Fortran::parser::CompilerDirective::VectorAlways &) {
+ disableVecAttr = mlir::BoolAttr::get(context, false);
+ hasAttrs = true;
+ },
+ [&](const Fortran::parser::CompilerDirective::VectorLength &vl) {
+ using Kind =
+ Fortran::parser::CompilerDirective::VectorLength::Kind;
+ Kind kind = std::get<Kind>(vl.t);
+ uint64_t length = std::get<uint64_t>(vl.t);
+ disableVecAttr = mlir::BoolAttr::get(context, false);
+ if (length != 0)
+ vectorWidth = mlir::IntegerAttr::get(
+ mlir::IntegerType::get(context, 64), length);
+ switch (kind) {
+ case Kind::Scalable:
+ scalableEnable = mlir::BoolAttr::get(context, true);
+ break;
+ case Kind::Fixed:
+ scalableEnable = mlir::BoolAttr::get(context, false);
+ break;
+ case Kind::Auto:
+ break;
+ }
+ hasAttrs = true;
+ },
+ [&](const Fortran::parser::CompilerDirective::Unroll &u) {
+ ua = genLoopUnrollAttr(context, u.v);
+ hasAttrs = true;
+ },
+ [&](const Fortran::parser::CompilerDirective::UnrollAndJam &u) {
+ uja = genLoopUnrollAndJamAttr(context, u.v);
+ hasAttrs = true;
+ },
+ [&](const Fortran::parser::CompilerDirective::NoVector &) {
+ disableVecAttr = mlir::BoolAttr::get(context, true);
+ hasAttrs = true;
+ },
+ [&](const Fortran::parser::CompilerDirective::NoUnroll &) {
+ ua = genLoopUnrollAttr(context, /*directiveArg=*/0);
+ hasAttrs = true;
+ },
+ [&](const Fortran::parser::CompilerDirective::NoUnrollAndJam &) {
+ uja = genLoopUnrollAndJamAttr(context, /*count=*/0);
+ hasAttrs = true;
+ },
+ [&](const Fortran::parser::CompilerDirective::IVDep &) {
+ aga.push_back(mlir::LLVM::AccessGroupAttr::get(context));
+ hasAttrs = true;
+ },
+ [&](const Fortran::parser::CompilerDirective::Simd &) {
+ disableVecAttr = mlir::BoolAttr::get(context, false);
+ hasAttrs = true;
+ },
+ [&](const auto &) {}},
+ dir->u);
+ }
+ if (!hasAttrs)
+ return {};
+
+ mlir::LLVM::LoopVectorizeAttr va = genLoopVectorizeAttr(
+ context, disableVecAttr, scalableEnable, vectorWidth);
+ return mlir::LLVM::LoopAnnotationAttr::get(
+ context, {}, /*vectorize=*/va, {}, /*unroll*/ ua,
+ /*unroll_and_jam*/ uja, {}, {}, {}, {}, {}, {}, {}, {}, {},
+ /*parallelAccesses*/ aga);
+}
diff --git a/flang/test/Lower/OpenACC/acc-loop-directive-annotation.f90 b/flang/test/Lower/OpenACC/acc-loop-directive-annotation.f90
new file mode 100644
index 0000000000000..a6e2f324fbebe
--- /dev/null
+++ b/flang/test/Lower/OpenACC/acc-loop-directive-annotation.f90
@@ -0,0 +1,17 @@
+! Test that !dir$ loop directives are applied as loopAnnotation on acc.loop.
+
+! RUN: bbc -fopenacc -emit-hlfir %s -o - | FileCheck %s
+
+subroutine acc_loop_unroll(a, n)
+ real :: a(n)
+ integer :: i, n
+ !dir$ unroll
+ !$acc loop
+ do i = 1, n
+ a(i) = a(i) + 1
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPacc_loop_unroll
+! CHECK: acc.loop {{.*}} {
+! CHECK: } attributes {{{.*}}loopAnnotation = #llvm.loop_annotation<unroll = <disable = false, full = true>>}
diff --git a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsTiling.cpp b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsTiling.cpp
index 32fd3957894fb..b0bb39ec51c19 100644
--- a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsTiling.cpp
+++ b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsTiling.cpp
@@ -70,6 +70,8 @@ static mlir::acc::LoopOp createACCLoopFromOriginal(
mlir::ArrayAttr{}, origLoop.getCacheOperands(),
origLoop.getPrivateOperands(), origLoop.getFirstprivateOperands(),
origLoop.getReductionOperands(), combinedAttr);
+ // Preserve discardable attributes such as loopAnnotation.
+ newLoop->setDiscardableAttrs(origLoop->getDiscardableAttrDictionary());
return newLoop;
}
>From f9b231c3a78306ec9a6bc9f261d3128ead21d7d8 Mon Sep 17 00:00:00 2001
From: Scott Manley <scmanley at nvidia.com>
Date: Tue, 18 Aug 2026 05:58:14 -0700
Subject: [PATCH 2/5] address comments
---
flang/docs/Directives.md | 7 +-
.../flang/Lower/Support/LoopAnnotation.h | 5 +
flang/lib/Lower/OpenACC.cpp | 2 +-
flang/lib/Lower/Support/LoopAnnotation.cpp | 3 +
.../OpenACC/acc-loop-directive-annotation.f90 | 105 +++++++++++++++++-
.../OpenACC/Utils/OpenACCUtilsTiling.cpp | 2 -
6 files changed, 114 insertions(+), 10 deletions(-)
diff --git a/flang/docs/Directives.md b/flang/docs/Directives.md
index e74db041d01f3..8b7355edb0512 100644
--- a/flang/docs/Directives.md
+++ b/flang/docs/Directives.md
@@ -171,10 +171,9 @@ directives. Directives on loops are used to specify additional transformation to
be performed by the compiler like enabling vectorisation, unrolling, interchange
etc.
-Currently loop directives are accepted in the presence of OpenACC loop and
-combined constructs on the loop. Loop directives with OpenMP constructs on the
-loop are not yet accepted. This should be implemented as it is used in some
-applications.
+Currently loop directives in the presence of OpenACC loop are a work in progress
+and may be applied as a loop annotation attribute. Loop directives with OpenMP
+constructs on the loop are not yet applied.
### Array Expressions
It is to be decided whether loop directives should also be able to be associated
diff --git a/flang/include/flang/Lower/Support/LoopAnnotation.h b/flang/include/flang/Lower/Support/LoopAnnotation.h
index 9ff1ec5c521fe..0f2cd4fe7f057 100644
--- a/flang/include/flang/Lower/Support/LoopAnnotation.h
+++ b/flang/include/flang/Lower/Support/LoopAnnotation.h
@@ -14,7 +14,12 @@
#define FORTRAN_LOWER_SUPPORT_LOOPANNOTATION_H
#include "mlir/Dialect/LLVMIR/LLVMAttrs.h"
+#include "mlir/IR/BuiltinTypes.h"
#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/SmallVector.h"
+#include <cstdint>
+#include <optional>
+#include <tuple>
namespace Fortran {
namespace parser {
diff --git a/flang/lib/Lower/OpenACC.cpp b/flang/lib/Lower/OpenACC.cpp
index 73f1c18bfaac2..e60845421eef5 100644
--- a/flang/lib/Lower/OpenACC.cpp
+++ b/flang/lib/Lower/OpenACC.cpp
@@ -2537,7 +2537,7 @@ static mlir::acc::LoopOp createLoopOp(
if (mlir::LLVM::LoopAnnotationAttr la =
Fortran::lower::genLoopAnnotationAttr(builder.getContext(),
doStmtEval->dirs))
- loopOp->setDiscardableAttr("loopAnnotation", la);
+ loopOp->setDiscardableAttr(mlir::LLVM::LoopAnnotationAttr::name, la);
}
return loopOp;
diff --git a/flang/lib/Lower/Support/LoopAnnotation.cpp b/flang/lib/Lower/Support/LoopAnnotation.cpp
index 976d7495e78dc..99093e0ac86b9 100644
--- a/flang/lib/Lower/Support/LoopAnnotation.cpp
+++ b/flang/lib/Lower/Support/LoopAnnotation.cpp
@@ -11,6 +11,9 @@
#include "flang/Parser/parse-tree.h"
#include "mlir/IR/BuiltinAttributes.h"
+ // For unroll directives without a value, force full unrolling.
+ // For unroll directives with a value, if the value is greater than 1,
+ // force unrolling with the given factor. Otherwise, disable unrolling.
static mlir::LLVM::LoopUnrollAttr
genLoopUnrollAttr(mlir::MLIRContext *context,
std::optional<std::uint64_t> directiveArg) {
diff --git a/flang/test/Lower/OpenACC/acc-loop-directive-annotation.f90 b/flang/test/Lower/OpenACC/acc-loop-directive-annotation.f90
index a6e2f324fbebe..8910398b4240c 100644
--- a/flang/test/Lower/OpenACC/acc-loop-directive-annotation.f90
+++ b/flang/test/Lower/OpenACC/acc-loop-directive-annotation.f90
@@ -1,8 +1,27 @@
-! Test that !dir$ loop directives are applied as loopAnnotation on acc.loop.
+! Test that !dir$ loop directives are applied as loopAnnotation on acc.loop,
+! including combined !$acc parallel loop constructs.
! RUN: bbc -fopenacc -emit-hlfir %s -o - | FileCheck %s
+! CHECK-LABEL: func.func @_QPacc_loop_unroll
+! CHECK: acc.loop {{.*}} {
+! CHECK: } attributes {{{.*}}llvm.loop_annotation = #llvm.loop_annotation<unroll = <disable = false, full = true>>}
+
subroutine acc_loop_unroll(a, n)
+ real :: a(n)
+ integer :: i, n
+ !$acc loop
+ !dir$ unroll
+ do i = 1, n
+ a(i) = a(i) + 1
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPunroll_acc_loop
+! CHECK: acc.loop {{.*}} {
+! CHECK: } attributes {{{.*}}llvm.loop_annotation = #llvm.loop_annotation<unroll = <disable = false, full = true>>}
+
+subroutine unroll_acc_loop(a, n)
real :: a(n)
integer :: i, n
!dir$ unroll
@@ -12,6 +31,86 @@ subroutine acc_loop_unroll(a, n)
end do
end subroutine
-! CHECK-LABEL: func.func @_QPacc_loop_unroll
+! CHECK-LABEL: func.func @_QPacc_loop_unroll_count
+! CHECK: acc.loop {{.*}} {
+! CHECK: } attributes {{{.*}}llvm.loop_annotation = #llvm.loop_annotation<unroll = <disable = false, count = 4 : i64>>}
+
+subroutine acc_loop_unroll_count(a, n)
+ real :: a(n)
+ integer :: i, n
+ !$acc loop
+ !dir$ unroll 4
+ do i = 1, n
+ a(i) = a(i) + 1
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPunroll_count_acc_loop
+! CHECK: acc.loop {{.*}} {
+! CHECK: } attributes {{{.*}}llvm.loop_annotation = #llvm.loop_annotation<unroll = <disable = false, count = 4 : i64>>}
+
+subroutine unroll_count_acc_loop(a, n)
+ real :: a(n)
+ integer :: i, n
+ !dir$ unroll 4
+ !$acc loop
+ do i = 1, n
+ a(i) = a(i) + 1
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPacc_parallel_loop_unroll
! CHECK: acc.loop {{.*}} {
-! CHECK: } attributes {{{.*}}loopAnnotation = #llvm.loop_annotation<unroll = <disable = false, full = true>>}
+! CHECK: } attributes {{{.*}}llvm.loop_annotation = #llvm.loop_annotation<unroll = <disable = false, full = true>>}
+
+subroutine acc_parallel_loop_unroll(a, n)
+ real :: a(n)
+ integer :: i, n
+ !$acc parallel loop
+ !dir$ unroll
+ do i = 1, n
+ a(i) = a(i) + 1
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPunroll_acc_parallel_loop
+! CHECK: acc.loop {{.*}} {
+! CHECK: } attributes {{{.*}}llvm.loop_annotation = #llvm.loop_annotation<unroll = <disable = false, full = true>>}
+
+subroutine unroll_acc_parallel_loop(a, n)
+ real :: a(n)
+ integer :: i, n
+ !dir$ unroll
+ !$acc parallel loop
+ do i = 1, n
+ a(i) = a(i) + 1
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPacc_parallel_loop_unroll_count
+! CHECK: acc.loop {{.*}} {
+! CHECK: } attributes {{{.*}}llvm.loop_annotation = #llvm.loop_annotation<unroll = <disable = false, count = 4 : i64>>}
+
+subroutine acc_parallel_loop_unroll_count(a, n)
+ real :: a(n)
+ integer :: i, n
+ !$acc parallel loop
+ !dir$ unroll 4
+ do i = 1, n
+ a(i) = a(i) + 1
+ end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPunroll_count_acc_parallel_loop
+! CHECK: acc.loop {{.*}} {
+! CHECK: } attributes {{{.*}}llvm.loop_annotation = #llvm.loop_annotation<unroll = <disable = false, count = 4 : i64>>}
+
+subroutine unroll_count_acc_parallel_loop(a, n)
+ real :: a(n)
+ integer :: i, n
+ !dir$ unroll 4
+ !$acc parallel loop
+ do i = 1, n
+ a(i) = a(i) + 1
+ end do
+end subroutine
\ No newline at end of file
diff --git a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsTiling.cpp b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsTiling.cpp
index b0bb39ec51c19..32fd3957894fb 100644
--- a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsTiling.cpp
+++ b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsTiling.cpp
@@ -70,8 +70,6 @@ static mlir::acc::LoopOp createACCLoopFromOriginal(
mlir::ArrayAttr{}, origLoop.getCacheOperands(),
origLoop.getPrivateOperands(), origLoop.getFirstprivateOperands(),
origLoop.getReductionOperands(), combinedAttr);
- // Preserve discardable attributes such as loopAnnotation.
- newLoop->setDiscardableAttrs(origLoop->getDiscardableAttrDictionary());
return newLoop;
}
>From 881d19191ff461e8dffceb659ab758d473992371 Mon Sep 17 00:00:00 2001
From: Scott Manley <scmanley at nvidia.com>
Date: Tue, 18 Aug 2026 06:02:14 -0700
Subject: [PATCH 3/5] comment fixes
---
flang/docs/Directives.md | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/flang/docs/Directives.md b/flang/docs/Directives.md
index 8b7355edb0512..f2f4078c479a2 100644
--- a/flang/docs/Directives.md
+++ b/flang/docs/Directives.md
@@ -168,11 +168,12 @@ to be performed by the compiler. The directives are always specified with the
Some directives are associated with the following construct, for example loop
directives. Directives on loops are used to specify additional transformation to
-be performed by the compiler like enabling vectorisation, unrolling, interchange
+be performed by the compiler like enabling vectorization, unrolling, interchange
etc.
-Currently loop directives in the presence of OpenACC loop are a work in progress
-and may be applied as a loop annotation attribute. Loop directives with OpenMP
+Loop directives in the presence of OpenACC loop are a work in progress and may be
+applied as a loop annotation attribute. OpenACC directives will take precedence
+over general loop directives when "in conflict". Loop directives with OpenMP
constructs on the loop are not yet applied.
### Array Expressions
>From c9f6b503fa9aaa5dd545fa81e90b333a976e5991 Mon Sep 17 00:00:00 2001
From: Scott Manley <scmanley at nvidia.com>
Date: Tue, 18 Aug 2026 06:02:43 -0700
Subject: [PATCH 4/5] another comment fix
---
flang/lib/Lower/Bridge.cpp | 5 -----
1 file changed, 5 deletions(-)
diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index a0f39d51727d0..70d3c14248735 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -2857,11 +2857,6 @@ class FirConverter : public Fortran::lower::AbstractConverter {
return builder->createIntegerConstant(loc, controlType, 1); // step
}
- // For unroll directives without a value, force full unrolling.
- // For unroll directives with a value, if the value is greater than 1,
- // force unrolling with the given factor. Otherwise, disable unrolling.
- // Enabling unroll and jamming / vectorization is handled in
- // genLoopAnnotationAttr.
void addLoopAnnotationAttr(
IncrementLoopInfo &info,
llvm::SmallVectorImpl<const Fortran::parser::CompilerDirective *> &dirs) {
>From 6f6524e0df53746c61693012b8f26da8d14556a3 Mon Sep 17 00:00:00 2001
From: Scott Manley <scmanley at nvidia.com>
Date: Tue, 18 Aug 2026 06:06:23 -0700
Subject: [PATCH 5/5] add more tests
---
flang/lib/Lower/OpenACC.cpp | 3 +-
flang/lib/Lower/Support/LoopAnnotation.cpp | 6 +-
.../OpenACC/acc-loop-directive-annotation.f90 | 66 ++++++++++++++++++-
3 files changed, 70 insertions(+), 5 deletions(-)
diff --git a/flang/lib/Lower/OpenACC.cpp b/flang/lib/Lower/OpenACC.cpp
index e60845421eef5..f59ce763ab8ae 100644
--- a/flang/lib/Lower/OpenACC.cpp
+++ b/flang/lib/Lower/OpenACC.cpp
@@ -2532,6 +2532,7 @@ static mlir::acc::LoopOp createLoopOp(
// Apply `!dir$` loop directives associated with the DO statement as a
// discardable LLVM loop annotation attribute on the acc.loop.
+ // TODO: consider limiting to directives that are reasonable to apply
if (Fortran::lower::pft::Evaluation *doStmtEval =
getAccLoopDoStmtEval(eval)) {
if (mlir::LLVM::LoopAnnotationAttr la =
@@ -5473,7 +5474,7 @@ mlir::Operation *Fortran::lower::genOpenACCLoopFromDoConstruct(
if (mlir::LLVM::LoopAnnotationAttr la =
Fortran::lower::genLoopAnnotationAttr(builder.getContext(),
doStmtEval->dirs))
- loopOp->setDiscardableAttr("loopAnnotation", la);
+ loopOp->setDiscardableAttr(mlir::LLVM::LoopAnnotationAttr::name, la);
}
return loopOp;
diff --git a/flang/lib/Lower/Support/LoopAnnotation.cpp b/flang/lib/Lower/Support/LoopAnnotation.cpp
index 99093e0ac86b9..c290d17c4400a 100644
--- a/flang/lib/Lower/Support/LoopAnnotation.cpp
+++ b/flang/lib/Lower/Support/LoopAnnotation.cpp
@@ -11,9 +11,9 @@
#include "flang/Parser/parse-tree.h"
#include "mlir/IR/BuiltinAttributes.h"
- // For unroll directives without a value, force full unrolling.
- // For unroll directives with a value, if the value is greater than 1,
- // force unrolling with the given factor. Otherwise, disable unrolling.
+// For unroll directives without a value, force full unrolling.
+// For unroll directives with a value, if the value is greater than 1,
+// force unrolling with the given factor. Otherwise, disable unrolling.
static mlir::LLVM::LoopUnrollAttr
genLoopUnrollAttr(mlir::MLIRContext *context,
std::optional<std::uint64_t> directiveArg) {
diff --git a/flang/test/Lower/OpenACC/acc-loop-directive-annotation.f90 b/flang/test/Lower/OpenACC/acc-loop-directive-annotation.f90
index 8910398b4240c..c9b3e2e26ba3f 100644
--- a/flang/test/Lower/OpenACC/acc-loop-directive-annotation.f90
+++ b/flang/test/Lower/OpenACC/acc-loop-directive-annotation.f90
@@ -1,5 +1,5 @@
! Test that !dir$ loop directives are applied as loopAnnotation on acc.loop,
-! including combined !$acc parallel loop constructs.
+! including combined constructs and loops inside a compute construct.
! RUN: bbc -fopenacc -emit-hlfir %s -o - | FileCheck %s
@@ -113,4 +113,68 @@ subroutine unroll_count_acc_parallel_loop(a, n)
do i = 1, n
a(i) = a(i) + 1
end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPacc_parallel_unroll
+! CHECK: acc.parallel {
+! CHECK: acc.loop {{.*}} {
+! CHECK: } attributes {{{.*}}llvm.loop_annotation = #llvm.loop_annotation<unroll = <disable = false, full = true>>}
+
+subroutine acc_parallel_unroll(a, n)
+ real :: a(n)
+ integer :: i, n
+ !$acc parallel
+ !dir$ unroll
+ do i = 1, n
+ a(i) = a(i) + 1
+ end do
+ !$acc end parallel
+end subroutine
+
+! CHECK-LABEL: func.func @_QPunroll_acc_parallel
+! CHECK: acc.parallel {
+! CHECK: acc.loop {{.*}} {
+! CHECK: } attributes {{{.*}}llvm.loop_annotation = #llvm.loop_annotation<unroll = <disable = false, full = true>>}
+
+subroutine unroll_acc_parallel(a, n)
+ real :: a(n)
+ integer :: i, n
+ !dir$ unroll
+ !$acc parallel
+ do i = 1, n
+ a(i) = a(i) + 1
+ end do
+ !$acc end parallel
+end subroutine
+
+! CHECK-LABEL: func.func @_QPacc_parallel_unroll_count
+! CHECK: acc.parallel {
+! CHECK: acc.loop {{.*}} {
+! CHECK: } attributes {{{.*}}llvm.loop_annotation = #llvm.loop_annotation<unroll = <disable = false, count = 4 : i64>>}
+
+subroutine acc_parallel_unroll_count(a, n)
+ real :: a(n)
+ integer :: i, n
+ !$acc parallel
+ !dir$ unroll 4
+ do i = 1, n
+ a(i) = a(i) + 1
+ end do
+ !$acc end parallel
+end subroutine
+
+! CHECK-LABEL: func.func @_QPunroll_count_acc_parallel
+! CHECK: acc.parallel {
+! CHECK: acc.loop {{.*}} {
+! CHECK: } attributes {{{.*}}llvm.loop_annotation = #llvm.loop_annotation<unroll = <disable = false, count = 4 : i64>>}
+
+subroutine unroll_count_acc_parallel(a, n)
+ real :: a(n)
+ integer :: i, n
+ !dir$ unroll 4
+ !$acc parallel
+ do i = 1, n
+ a(i) = a(i) + 1
+ end do
+ !$acc end parallel
end subroutine
\ No newline at end of file
More information about the Mlir-commits
mailing list