[flang-commits] [flang] e7b0b42 - [Flang][OpenMP] Support conditional lastprivate on host (#200086)
via flang-commits
flang-commits at lists.llvm.org
Tue Aug 11 06:32:42 PDT 2026
Author: Sunil Shrestha
Date: 2026-08-11T06:32:36-07:00
New Revision: e7b0b420957ed4a250a6b137fc6834c9d5d247bd
URL: https://github.com/llvm/llvm-project/commit/e7b0b420957ed4a250a6b137fc6834c9d5d247bd
DIFF: https://github.com/llvm/llvm-project/commit/e7b0b420957ed4a250a6b137fc6834c9d5d247bd.diff
LOG: [Flang][OpenMP] Support conditional lastprivate on host (#200086)
This patch lowers lastprivate(conditional:) on the host by leveraging
the existing user-defined reduction (UDR) infrastructure. A packed
struct is created where each thread tracks, for every lastprivate
variable, both the candidate value and the canonical iteration index of
its last update. The reduction combiner selects the value from the
sequentially later iteration (for do loops) or from the lexically later
section (for sections).
The implementation locates the enclosing omp.parallel and places the
shared struct before it so that all threads in the team reduce into the
same storage. For orphaned worksharing constructs — where no enclosing
parallel is visible at compile time — a module-scope global of the
struct type is used instead. This is correct for a single level of
parallelism, but concurrent nested teams executing the same orphaned
construct would race on the shared global. This limitation mirrors the
current Clang behavior, which also uses a single global and does not
support nested parallelism for conditional lastprivate.
Assisted-by : Claude Opus 4.6
Added:
flang/test/Lower/OpenMP/lastprivate-conditional-atomic.f90
flang/test/Lower/OpenMP/lastprivate-conditional-collapse.f90
flang/test/Lower/OpenMP/lastprivate-conditional-do-simd.f90
flang/test/Lower/OpenMP/lastprivate-conditional-firstprivate.f90
flang/test/Lower/OpenMP/lastprivate-conditional-loop.f90
flang/test/Lower/OpenMP/lastprivate-conditional-nested-parallel.f90
flang/test/Lower/OpenMP/lastprivate-conditional-never-assigned.f90
flang/test/Lower/OpenMP/lastprivate-conditional-parallel-do-simd.f90
flang/test/Lower/OpenMP/lastprivate-conditional-parallel-loop.f90
flang/test/Lower/OpenMP/lastprivate-conditional-sections-firstprivate.f90
flang/test/Lower/OpenMP/lastprivate-conditional-sections-nowait.f90
flang/test/Lower/OpenMP/lastprivate-conditional-sections-orphaned.f90
flang/test/Lower/OpenMP/lastprivate-conditional-sections.f90
flang/test/Lower/OpenMP/lastprivate-conditional-simd.f90
flang/test/Lower/OpenMP/lastprivate-conditional-standalone-do.f90
flang/test/Lower/OpenMP/lastprivate-conditional-standalone-sections.f90
flang/test/Lower/OpenMP/lastprivate-conditional-step.f90
flang/test/Lower/OpenMP/lastprivate-conditional-teams.f90
flang/test/Lower/OpenMP/lastprivate-conditional-types.f90
flang/test/Lower/OpenMP/lastprivate-conditional-wsloop-nowait.f90
flang/test/Lower/OpenMP/lastprivate-conditional-wsloop-orphaned.f90
flang/test/Lower/OpenMP/lastprivate-conditional-wsloop.f90
flang/test/Semantics/OpenMP/lastprivate-conditional-aggregate.f90
flang/test/Semantics/OpenMP/lastprivate-conditional-scalar.f90
flang/test/Semantics/OpenMP/test_distribute_lastprivate_semantic_restrictions.f90
Modified:
flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
flang/lib/Lower/OpenMP/DataSharingProcessor.h
flang/lib/Lower/OpenMP/OpenMP.cpp
flang/lib/Lower/Support/ReductionProcessor.cpp
flang/lib/Semantics/check-omp-structure.cpp
flang/test/Semantics/OpenMP/test_taskloop_lastprivate_semantic_restrictions.f90
Removed:
flang/test/Lower/OpenMP/Todo/lastprivate-conditional.f90
################################################################################
diff --git a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
index 401c039f43973..6b57934a087f6 100644
--- a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
@@ -221,7 +221,10 @@ void DataSharingProcessor::copyFirstPrivateSymbol(
void DataSharingProcessor::copyLastPrivateSymbol(
const semantics::Symbol *sym, mlir::OpBuilder::InsertPoint *lastPrivIP) {
- if (sym->test(semantics::Symbol::Flag::OmpLastPrivate))
+ // Conditional-lastprivate symbols use their own guarded copy-back (from the
+ // reduction accumulator), not the standard "last iteration wins" copy-back.
+ if (sym->test(semantics::Symbol::Flag::OmpLastPrivate) &&
+ !conditionalLastPrivatizedSymbols.contains(sym))
converter.copyHostAssociateVar(*sym, lastPrivIP, /*hostIsSource=*/false);
}
@@ -274,19 +277,51 @@ void DataSharingProcessor::collectSymbolsForPrivatization() {
explicitlyPrivatizedSymbols);
} else if (const auto &lastPrivateClause =
std::get_if<omp::clause::Lastprivate>(&clause.u)) {
- lastprivateModifierNotSupported(*lastPrivateClause,
- converter.getCurrentLocation());
+ auto &modifier = std::get<
+ std::optional<omp::clause::Lastprivate::LastprivateModifier>>(
+ lastPrivateClause->t);
+
const ObjectList &objects = std::get<ObjectList>(lastPrivateClause->t);
- collectOmpObjectListSymbol(objects, explicitlyPrivatizedSymbols);
+ if (modifier) {
+ assert(*modifier ==
+ omp::clause::Lastprivate::LastprivateModifier::Conditional &&
+ "unsupported lastprivate modifier");
+ // The conditional modifier was added in OpenMP 5.0. In earlier
+ // versions semantics only warns and ignores it, so fall back to a
+ // regular lastprivate here to keep lowering consistent and avoid the
+ // conditional path for entities it cannot handle (e.g. characters).
+ if (semaCtx.langOptions().OpenMPVersion >= 50) {
+ collectOmpObjectListSymbol(objects, conditionalLastPrivatizedSymbols);
+ } else {
+ collectOmpObjectListSymbol(objects, explicitlyPrivatizedSymbols);
+ }
+ } else {
+ collectOmpObjectListSymbol(objects, explicitlyPrivatizedSymbols);
+ }
}
}
// TODO For common blocks, add the underlying objects within the block. Doing
// so, we won't need to explicitly handle block objects (or forget to do
// so).
+ // A conditional-lastprivate symbol is bound directly to the reduction struct
+ // (not privatized) UNLESS the construct opts into the private-copy lowering
+ // (conditionalLpUsesPrivateCopy, set by worksharing loops). In that mode it
+ // gets an ordinary private copy -- the "working" value that in-loop reads see
+ // and that carries the execution-order value across iterations -- with the
+ // reduction struct acting as the conditional-last accumulator, and the
+ // standard lastprivate copy-back suppressed (see copyLastPrivateSymbol /
+ // insertLastPrivateCompare) in favor of the conditional copy-back.
for (auto *sym : explicitlyPrivatizedSymbols)
- if (!isException(sym))
+ if (!isException(sym) && (conditionalLpUsesPrivateCopy ||
+ !conditionalLastPrivatizedSymbols.contains(sym)))
allPrivatizedSymbols.insert(sym);
+ if (conditionalLpUsesPrivateCopy)
+ for (auto *sym : conditionalLastPrivatizedSymbols)
+ if (!isException(sym))
+ allPrivatizedSymbols.insert(sym);
+ // (A firstprivate + conditional-lastprivate symbol appears in both lists;
+ // allPrivatizedSymbols is a SetVector, so it is inserted only once.)
}
bool DataSharingProcessor::isCoveredByReductionElement(
@@ -344,7 +379,8 @@ void DataSharingProcessor::insertLastPrivateCompare(mlir::Operation *op) {
for (const auto &mem : commonDet->objects())
if (mem->test(semantics::Symbol::Flag::OmpLastPrivate))
return true;
- } else if (sym->test(semantics::Symbol::Flag::OmpLastPrivate))
+ } else if (sym->test(semantics::Symbol::Flag::OmpLastPrivate) &&
+ !conditionalLastPrivatizedSymbols.contains(sym))
return true;
}
diff --git a/flang/lib/Lower/OpenMP/DataSharingProcessor.h b/flang/lib/Lower/OpenMP/DataSharingProcessor.h
index 01a25e041ef15..f2f34ee32fd20 100644
--- a/flang/lib/Lower/OpenMP/DataSharingProcessor.h
+++ b/flang/lib/Lower/OpenMP/DataSharingProcessor.h
@@ -99,6 +99,14 @@ class DataSharingProcessor {
llvm::SetVector<const semantics::Symbol *> explicitlyPrivatizedSymbols;
llvm::SetVector<const semantics::Symbol *> defaultSymbols;
llvm::SetVector<const semantics::Symbol *> allPrivatizedSymbols;
+ llvm::SetVector<const semantics::Symbol *> conditionalLastPrivatizedSymbols;
+ // When true, conditional-lastprivate list items get an ordinary private copy
+ // (their in-loop working value) plus a separate reduction struct as the
+ // conditional-last accumulator. Used by worksharing loops, where a
+ // nonmonotonic schedule can execute chunks out of order. When false the list
+ // item is bound directly to the reduction struct (used by sections, which are
+ // lexically ordered and never need the private copy).
+ bool conditionalLpUsesPrivateCopy = false;
lower::AbstractConverter &converter;
semantics::SemanticsContext &semaCtx;
@@ -207,6 +215,15 @@ class DataSharingProcessor {
void privatizeSymbol(const semantics::Symbol *symToPrivatize,
mlir::omp::PrivateClauseOps *clauseOps,
std::optional<llvm::omp::Directive> dir = std::nullopt);
+
+ const llvm::SetVector<const semantics::Symbol *> &
+ getConditionalLastprivateSymbols() const {
+ return conditionalLastPrivatizedSymbols;
+ }
+
+ void setConditionalLpUsesPrivateCopy(bool v) {
+ conditionalLpUsesPrivateCopy = v;
+ }
};
} // namespace omp
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 1a6819cf10ee7..89ec1953e6eb2 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -55,18 +55,65 @@
#include "mlir/Dialect/OpenMP/OpenMPDialect.h"
#include "mlir/IR/IRMapping.h"
#include "mlir/Support/StateStack.h"
+#include "mlir/Transforms/RegionUtils.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/MapVector.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Frontend/OpenMP/OMP.h"
+#include <atomic>
using namespace Fortran::lower::omp;
using namespace Fortran::common::openmp;
using namespace Fortran::utils::openmp;
+// Forward declarations
+static fir::RecordType buildConditionalLpType(
+ Fortran::lower::AbstractConverter &converter,
+ const llvm::SetVector<const Fortran::semantics::Symbol *> &condLpSyms,
+ mlir::Location loc);
+
+static mlir::omp::DeclareReductionOp buildConditionalLastPrivateReduction(
+ Fortran::lower::AbstractConverter &converter, fir::RecordType lpCondType,
+ const llvm::SetVector<const Fortran::semantics::Symbol *> &condLpSyms);
+
+static llvm::MapVector<mlir::Value, std::string> bindCondLpSymsToStructFields(
+ Fortran::lower::AbstractConverter &converter, mlir::Location loc,
+ fir::RecordType lpType, mlir::Value structArg,
+ const llvm::SetVector<const Fortran::semantics::Symbol *> &condLpSyms);
+
+static void injectCondLpIndexStores(
+ fir::FirOpBuilder &builder, mlir::Location loc, fir::RecordType lpType,
+ mlir::Value structArg, mlir::Region ®ion,
+ const llvm::MapVector<mlir::Value, std::string> &valAddrToSymName,
+ llvm::function_ref<mlir::Value(fir::FirOpBuilder &, mlir::Location)>
+ genIndexVal);
+
+static mlir::Value
+computeFlattenedCanonicalIV(fir::FirOpBuilder &builder, mlir::Location loc,
+ mlir::omp::LoopNestOp loopNestOp);
+
+static void initConditionalLpStructDefault(fir::FirOpBuilder &builder,
+ mlir::Location loc,
+ fir::RecordType lpCondType,
+ mlir::Value structRef);
+
+static void initConditionalLpStruct(
+ Fortran::lower::AbstractConverter &converter, mlir::Location loc,
+ fir::RecordType lpCondType, mlir::Value structRef,
+ const llvm::SetVector<const Fortran::semantics::Symbol *> &condLpSyms);
+
+static mlir::Value
+getOrCreateConditionalLpGlobal(Fortran::lower::AbstractConverter &converter,
+ mlir::Location loc, fir::RecordType lpType);
+
+static void
+emitNestedParallelGuardForCondLp(lower::AbstractConverter &converter,
+ mlir::Location loc);
+
//===----------------------------------------------------------------------===//
// Code generation helper functions
//===----------------------------------------------------------------------===//
@@ -848,6 +895,17 @@ static void bindEntryBlockArgs(lower::AbstractConverter &converter,
llvm::SmallVector<const Object *> processedObjects;
for (const Object &object : objects) {
const semantics::Symbol *sym = object.sym();
+ if (!sym) {
+ // Null sentinel: this entry corresponds to a compiler-synthesized
+ // reduction (e.g. the conditional lastprivate struct) that has no
+ // Fortran symbol. We must keep a placeholder so that processedSyms
+ // stays in lock-step with `vars` and `args` — the later
+ // llvm::zip_equal(processedSyms, vars, args) asserts equal lengths.
+ // The matching block argument is silently skipped below.
+ processedSyms.push_back(nullptr);
+ processedObjects.push_back(nullptr);
+ continue;
+ }
if (const auto *commonDet =
sym->detailsIf<semantics::CommonBlockDetails>()) {
for (auto &mem : commonDet->objects()) {
@@ -863,13 +921,14 @@ static void bindEntryBlockArgs(lower::AbstractConverter &converter,
assert(processedSyms.size() == processedObjects.size());
for (auto [sym, var, arg, object] :
llvm::zip_equal(processedSyms, vars, args, processedObjects)) {
+ if (!sym)
+ continue; // Skip synthetic reduction entries (no Fortran symbol).
bool skipBind =
ReductionProcessor::isExpressionLoweredAsReductionObject(object) ||
(object && sym->Rank() > 0 &&
!fir::unwrapUntilSeqType(arg.getType()));
if (skipBind)
continue;
-
converter.bindSymbol(
*sym,
hlfir::translateToExtendedValue(
@@ -3025,17 +3084,17 @@ genFlushOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
operandRange);
}
-static mlir::omp::LoopNestOp
-genLoopNestOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
- semantics::SemanticsContext &semaCtx,
- lower::pft::Evaluation &eval, mlir::Location loc,
- const ConstructQueue &queue, ConstructQueue::const_iterator item,
- mlir::omp::LoopNestOperands &clauseOps,
- llvm::ArrayRef<const semantics::Symbol *> iv,
- llvm::ArrayRef<std::pair<mlir::omp::BlockArgOpenMPOpInterface,
- const ObjectEntryBlockArgs &>>
- wrapperArgs,
- llvm::omp::Directive directive, DataSharingProcessor &dsp) {
+static mlir::omp::LoopNestOp genLoopNestOp(
+ lower::AbstractConverter &converter, lower::SymMap &symTable,
+ semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval,
+ mlir::Location loc, const ConstructQueue &queue,
+ ConstructQueue::const_iterator item, mlir::omp::LoopNestOperands &clauseOps,
+ llvm::ArrayRef<const semantics::Symbol *> iv,
+ llvm::ArrayRef<std::pair<mlir::omp::BlockArgOpenMPOpInterface,
+ const ObjectEntryBlockArgs &>>
+ wrapperArgs,
+ llvm::omp::Directive directive, DataSharingProcessor &dsp,
+ llvm::function_ref<void(mlir::Operation *)> loopPostIvCb = nullptr) {
const lower::ExprToValueMap *oldOverrides = converter.getExprOverrides();
lower::ExprToValueMap loopNestOverrides;
auto ivCallback = [&](mlir::Operation *op) {
@@ -3055,6 +3114,8 @@ genLoopNestOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
}
converter.overrideExprValues(
loopNestOverrides.empty() ? oldOverrides : &loopNestOverrides);
+ if (loopPostIvCb)
+ loopPostIvCb(op);
return llvm::SmallVector<const semantics::Symbol *>(iv);
};
@@ -3624,6 +3685,21 @@ genScanOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
converter.getCurrentLocation(), clauseOps);
}
+/// Walk up the parent-op chain from the current insertion point and return
+/// the nearest enclosing \c omp::ParallelOp, or \c nullptr if none exists
+/// (i.e. the construct is orphaned). The walk handles intervening ops such
+/// as \c fir::IfOp that may appear between the worksharing construct and its
+/// enclosing parallel region.
+static mlir::omp::ParallelOp
+findEnclosingParallelOp(fir::FirOpBuilder &builder) {
+ for (auto *op = builder.getInsertionBlock()->getParentOp(); op;
+ op = op->getParentOp()) {
+ if (auto parallelOp = mlir::dyn_cast<mlir::omp::ParallelOp>(op))
+ return parallelOp;
+ }
+ return {};
+}
+
static mlir::omp::SectionsOp
genSectionsOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
semantics::SemanticsContext &semaCtx,
@@ -3650,13 +3726,58 @@ genSectionsOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
/*useDelayedPrivatization=*/false, symTable);
dsp.processStep1();
+ // Detect conditional lastprivate symbols for sections.
+ auto &condLpSyms = dsp.getConditionalLastprivateSymbols();
+ fir::RecordType lpType;
+ mlir::Value lpAlloca;
+ if (!condLpSyms.empty()) {
+ lpType = buildConditionalLpType(converter, condLpSyms, loc);
+ mlir::omp::DeclareReductionOp declRedOp =
+ buildConditionalLastPrivateReduction(converter, lpType, condLpSyms);
+
+ // Create the struct alloca outside the parent parallel (if any).
+ // In the orphaned case (no enclosing ParallelOp), use a
+ // module-scope global so that all threads share one reduction target.
+ auto enclosingParallel = findEnclosingParallelOp(builder);
+ bool isOrphaned = !enclosingParallel;
+
+ // Guard against nested parallelism in the orphaned case.
+ // Emit this BEFORE touching the global to avoid racing on it.
+ if (isOrphaned)
+ emitNestedParallelGuardForCondLp(converter, loc);
+
+ if (enclosingParallel) {
+ mlir::OpBuilder::InsertionGuard guard(builder);
+ builder.setInsertionPoint(enclosingParallel);
+ lpAlloca = builder.createTemporary(loc, lpType);
+ initConditionalLpStruct(converter, loc, lpType, lpAlloca, condLpSyms);
+ } else {
+ lpAlloca = getOrCreateConditionalLpGlobal(converter, loc, lpType);
+ // The global is shared across all threads. Use omp.single (which
+ // has an implicit barrier at exit) so that exactly one thread
+ // initialises and all threads wait before entering the construct.
+ mlir::omp::SingleOperands initSingleOps;
+ auto singleOp = mlir::omp::SingleOp::create(builder, loc, initSingleOps);
+ mlir::Block *singleBlock = builder.createBlock(&singleOp.getRegion());
+ builder.setInsertionPointToStart(singleBlock);
+ initConditionalLpStruct(converter, loc, lpType, lpAlloca, condLpSyms);
+ mlir::omp::TerminatorOp::create(builder, loc);
+ builder.setInsertionPointAfter(singleOp);
+ }
+
+ clauseOps.reductionVars.push_back(lpAlloca);
+ clauseOps.reductionByref.push_back(true);
+ clauseOps.reductionSyms.push_back(
+ mlir::SymbolRefAttr::get(builder.getContext(), declRedOp.getSymName()));
+ reductionObjects.push_back(Object{{nullptr, std::nullopt}});
+ }
+
List<Clause> nonDsaClauses;
List<const clause::Lastprivate *> lastprivates;
for (const Clause &clause : item->clauses) {
if (clause.id == llvm::omp::Clause::OMPC_lastprivate) {
auto &lastp = std::get<clause::Lastprivate>(clause.u);
- lastprivateModifierNotSupported(lastp, converter.getCurrentLocation());
lastprivates.push_back(&lastp);
} else {
switch (clause.id) {
@@ -3683,10 +3804,26 @@ genSectionsOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
mlir::Operation *terminator =
lower::genOpenMPTerminator(builder, sectionsOp, loc);
+ // Save address-to-name mapping for conditional LP symbols before section
+ // bodies are lowered (binding will overwrite them inside each section's
+ // callback). The addresses are needed for the post-reduction copy-back.
+ llvm::MapVector<mlir::Value, std::string> condLpOrigAddrs;
+ for (const auto *sym : condLpSyms) {
+ mlir::Value addr = converter.getSymbolAddress(*sym);
+ if (addr)
+ condLpOrigAddrs[addr] = sym->name().ToString();
+ }
+
// Generate nested SECTION constructs.
// This is done here rather than in genOMP([...], OmpSectionDirective )
// because we need to run genReductionVars on each omp.section so that the
- // reduction variable gets mapped to the private version
+ // reduction variable gets mapped to the private version.
+ //
+ // When conditional lastprivate symbols are present, a custom region entry
+ // callback binds them to the section's struct value-field addresses before
+ // the body is lowered, so that lowering naturally uses the struct fields.
+ llvm::SmallVector<llvm::MapVector<mlir::Value, std::string>>
+ perSectionValAddrs;
for (auto [construct, nestedEval] :
llvm::zip(sectionBlocks, eval.getNestedEvaluations())) {
const auto *sectionConstruct =
@@ -3702,15 +3839,72 @@ genSectionsOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
sectionConstruct->source, llvm::omp::Directive::OMPD_section, {})};
builder.setInsertionPoint(terminator);
- genOpWithBody<mlir::omp::SectionOp>(
- OpWithBodyGenInfo(converter, symTable, semaCtx, loc, nestedEval,
- llvm::omp::Directive::OMPD_section)
- .setClauses(§ionQueue.begin()->clauses)
- .setDataSharingProcessor(&dsp)
- .setEntryBlockArgs(&args),
- sectionQueue, sectionQueue.begin());
+
+ if (condLpSyms.empty()) {
+ genOpWithBody<mlir::omp::SectionOp>(
+ OpWithBodyGenInfo(converter, symTable, semaCtx, loc, nestedEval,
+ llvm::omp::Directive::OMPD_section)
+ .setClauses(§ionQueue.begin()->clauses)
+ .setDataSharingProcessor(&dsp)
+ .setEntryBlockArgs(&args),
+ sectionQueue, sectionQueue.begin());
+ } else {
+ llvm::MapVector<mlir::Value, std::string> sectionValAddrs;
+ auto sectionRegionEntryCb = [&](mlir::Operation *op)
+ -> llvm::SmallVector<const semantics::Symbol *> {
+ genEntryBlock(builder, args.asEntryBlockArgs(), op->getRegion(0));
+ auto blockArgIface =
+ mlir::cast<mlir::omp::BlockArgOpenMPOpInterface>(*op);
+ bindEntryBlockArgs(converter, blockArgIface, args);
+ mlir::Value structArg = blockArgIface.getReductionBlockArgs().back();
+ sectionValAddrs = bindCondLpSymsToStructFields(converter, loc, lpType,
+ structArg, condLpSyms);
+ return args.getSyms();
+ };
+ genOpWithBody<mlir::omp::SectionOp>(
+ OpWithBodyGenInfo(converter, symTable, semaCtx, loc, nestedEval,
+ llvm::omp::Directive::OMPD_section)
+ .setClauses(§ionQueue.begin()->clauses)
+ .setDataSharingProcessor(&dsp)
+ .setGenRegionEntryCb(sectionRegionEntryCb),
+ sectionQueue, sectionQueue.begin());
+ perSectionValAddrs.push_back(std::move(sectionValAddrs));
+ }
}
+ // Inject index stores after each assignment to a conditional LP value field
+ // inside every section.
+ if (!condLpSyms.empty()) {
+ unsigned sectionIdx = 0;
+ for (mlir::Operation &op : sectionsOp.getRegion().front()) {
+ auto sectionOp = mlir::dyn_cast<mlir::omp::SectionOp>(op);
+ if (!sectionOp)
+ continue;
+
+ auto sectionArgIface =
+ mlir::cast<mlir::omp::BlockArgOpenMPOpInterface>(*sectionOp);
+ mlir::Value sectionStructArg =
+ sectionArgIface.getReductionBlockArgs().back();
+
+ unsigned idx = sectionIdx;
+ injectCondLpIndexStores(
+ builder, loc, lpType, sectionStructArg, sectionOp.getRegion(),
+ perSectionValAddrs[sectionIdx],
+ [idx](fir::FirOpBuilder &b, mlir::Location l) -> mlir::Value {
+ return b.createIntegerConstant(l, b.getI64Type(), idx);
+ });
+ ++sectionIdx;
+ }
+ }
+
+ // Collect conditional LP symbol names so we can skip them in the normal
+ // lastprivate copy-back (they are handled by the reduction path).
+ llvm::SmallDenseSet<const semantics::Symbol *> condLpSymSet(
+ condLpSyms.begin(), condLpSyms.end());
+
+ // Track whether any non-conditional lastprivate copy-backs were emitted.
+ bool hasNonCondLastprivate = false;
+
if (!lastprivates.empty()) {
mlir::Region §ionsBody = sectionsOp.getRegion();
assert(sectionsBody.hasOneBlock());
@@ -3729,6 +3923,10 @@ genSectionsOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
const auto &objList = std::get<ObjectList>(lastp->t);
for (const Object &object : objList) {
semantics::Symbol *sym = object.sym();
+ // Skip conditional LP symbols — handled by the reduction path.
+ if (condLpSymSet.count(sym))
+ continue;
+ hasNonCondLastprivate = true;
if (const auto *common =
sym->detailsIf<semantics::CommonBlockDetails>()) {
for (const auto &obj : common->objects())
@@ -3743,12 +3941,67 @@ genSectionsOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
// Perform DataSharingProcessor's step2 out of SECTIONS
builder.setInsertionPointAfter(sectionsOp.getOperation());
dsp.processStep2(sectionsOp, false);
- // Emit implicit barrier to synchronize threads and avoid data
- // races on post-update of lastprivate variables when `nowait`
- // clause is present.
- if (clauseOps.nowait && !lastprivates.empty())
+ // Emit barrier when nowait is present and there are lastprivate copy-backs
+ // (either non-conditional or conditional). The barrier ensures all threads
+ // have completed their work before lastprivate values are read/copied.
+ //
+ // NOTE: The LLVM OpenMP runtime currently imposes an implicit barrier
+ // inside __kmpc_reduce for tree reductions. If the runtime were modified
+ // to release losing threads early when nowait is specified, we could use
+ // the return value from the tree reduction (case 1 = winner) to let the
+ // winner thread perform the copy-back without a separate barrier.
+ if (clauseOps.nowait && (hasNonCondLastprivate || !condLpSyms.empty()))
mlir::omp::BarrierOp::create(builder, loc);
+ // Copy-back: copy winning values from the shared reduction struct to the
+ // original variables. When nowait is absent, the worksharing construct's
+ // implicit end-barrier guarantees all reductions are combined before we
+ // reach this point. When nowait is present, the barrier above ensures
+ // the reduction is fully finalized before reading the struct. Wrapped in
+ // omp.single so exactly one thread performs the stores, at the sections
+ // construct's barrier (the semantically correct finalization point) inside
+ // the enclosing parallel. Because this copy-back adds a second
+ // immediately-nested construct to the parallel, the parallel is not marked
+ // omp.combined (see the combined-marking logic in genOMPDispatch).
+ if (!condLpSyms.empty()) {
+ mlir::omp::SingleOperands singleClauseOps;
+ auto singleOp = mlir::omp::SingleOp::create(builder, loc, singleClauseOps);
+ mlir::Block *singleBlock = builder.createBlock(&singleOp.getRegion());
+ builder.setInsertionPointToStart(singleBlock);
+
+ for (auto &[origAddr, symName] : condLpOrigAddrs) {
+ unsigned valFieldIdx = lpType.getFieldIndex(symName);
+ mlir::Type valType = lpType.getType(valFieldIdx);
+
+ fir::IntOrValue valFIdx =
+ mlir::IntegerAttr::get(builder.getI32Type(), valFieldIdx);
+ mlir::Value fieldAddr = fir::CoordinateOp::create(
+ builder, loc, builder.getRefType(valType), lpAlloca,
+ llvm::SmallVector<fir::IntOrValue, 1>{valFIdx});
+ mlir::Value val = fir::LoadOp::create(builder, loc, fieldAddr);
+
+ // Only copy back if some iteration actually assigned to this variable
+ // (index >= 0). Otherwise the original must not be overwritten.
+ unsigned idxFieldIdx = lpType.getFieldIndex("$" + symName);
+ fir::IntOrValue idxFIdx =
+ mlir::IntegerAttr::get(builder.getI32Type(), idxFieldIdx);
+ mlir::Value idxAddr = fir::CoordinateOp::create(
+ builder, loc, builder.getRefType(builder.getI64Type()), lpAlloca,
+ llvm::SmallVector<fir::IntOrValue, 1>{idxFIdx});
+ mlir::Value idxVal = fir::LoadOp::create(builder, loc, idxAddr);
+ mlir::Value zero =
+ builder.createIntegerConstant(loc, builder.getI64Type(), 0);
+ mlir::Value cond = mlir::arith::CmpIOp::create(
+ builder, loc, mlir::arith::CmpIPredicate::sge, idxVal, zero);
+ auto ifOp =
+ fir::IfOp::create(builder, loc, cond, /*withElseRegion=*/false);
+ builder.setInsertionPointToStart(&ifOp.getThenRegion().front());
+ fir::StoreOp::create(builder, loc, val, origAddr);
+ builder.setInsertionPointAfter(ifOp);
+ }
+ mlir::omp::TerminatorOp::create(builder, loc);
+ }
+
return sectionsOp;
}
@@ -4414,6 +4667,200 @@ static mlir::omp::DistributeOp genStandaloneDistribute(
return distributeOp;
}
+/// Zero-initialize the value fields and set index fields to -1 in a
+/// conditional-lastprivate reduction struct.
+///
+/// The struct groups all value fields first, then all index fields:
+/// {val_0, val_1, ..., idx_0, idx_1, ...}
+/// so fields [0, numVars) are value fields and [numVars, 2*numVars) are
+/// the corresponding iteration index fields.
+///
+/// The -1 sentinel on index fields ensures the combiner's "sequentially
+/// last" comparison treats the slot as "no iteration has written yet"
+/// (any real canonical loop IV >= 0 beats -1).
+static void initConditionalLpStructDefault(fir::FirOpBuilder &builder,
+ mlir::Location loc,
+ fir::RecordType lpCondType,
+ mlir::Value structRef) {
+ llvm::ArrayRef<std::pair<std::string, mlir::Type>> fields =
+ lpCondType.getTypeList();
+ unsigned numVars = fields.size() / 2;
+ for (unsigned i = 0, e = fields.size(); i < e; ++i) {
+ mlir::Type fieldTy = fields[i].second;
+ fir::IntOrValue idx = mlir::IntegerAttr::get(builder.getI32Type(), i);
+ mlir::Value fieldAddr = fir::CoordinateOp::create(
+ builder, loc, builder.getRefType(fieldTy), structRef,
+ llvm::SmallVector<fir::IntOrValue, 1>{idx});
+ mlir::Value initVal;
+ if (i >= numVars) // index field (second half)
+ initVal = builder.createIntegerConstant(loc, fieldTy, -1);
+ else if (fir::isa_trivial(fieldTy))
+ initVal = fir::factory::createZeroValue(builder, loc, fieldTy);
+ else // derived type or other non-trivial: use all-bits-zero
+ initVal = fir::ZeroOp::create(builder, loc, fieldTy);
+ fir::StoreOp::create(builder, loc, initVal, fieldAddr);
+ }
+}
+
+/// Initialize the shared reduction seed struct for conditional lastprivate.
+///
+/// First applies the identity initialization: every index field is set to -1
+/// and every value field to 0. Then, for any list item that is also
+/// firstprivate, that value field's 0 is overwritten with the original
+/// variable's incoming value. The per-thread reduction init region copies
+/// each value field from this seed struct, so overwriting a firstprivate
+/// item's field here is what makes it observe its initial value inside the
+/// loop.
+static void initConditionalLpStruct(
+ lower::AbstractConverter &converter, mlir::Location loc,
+ fir::RecordType lpCondType, mlir::Value structRef,
+ const llvm::SetVector<const semantics::Symbol *> &condLpSyms) {
+ fir::FirOpBuilder &builder = converter.getFirOpBuilder();
+ initConditionalLpStructDefault(builder, loc, lpCondType, structRef);
+
+ fir::RecordType lpType = lpCondType; // non-const copy for getFieldIndex
+ for (const semantics::Symbol *sym : condLpSyms) {
+ if (!sym->test(semantics::Symbol::Flag::OmpFirstPrivate))
+ continue;
+ mlir::Value origAddr = converter.getSymbolAddress(*sym);
+ assert(origAddr &&
+ "firstprivate conditional-lastprivate symbol must have an address");
+ if (!origAddr)
+ continue;
+ unsigned valIdx = lpType.getFieldIndex(sym->name().ToString());
+ mlir::Type fieldTy = lpType.getType(valIdx);
+ fir::IntOrValue idx = mlir::IntegerAttr::get(builder.getI32Type(), valIdx);
+ mlir::Value fieldAddr = fir::CoordinateOp::create(
+ builder, loc, builder.getRefType(fieldTy), structRef,
+ llvm::SmallVector<fir::IntOrValue, 1>{idx});
+ mlir::Value origVal = fir::LoadOp::create(builder, loc, origAddr);
+ fir::StoreOp::create(builder, loc, origVal, fieldAddr);
+ }
+}
+
+/// Emit a runtime guard for orphaned conditional-lastprivate worksharing
+/// constructs. The module-scope global used for the reduction struct is
+/// shared across all teams, so concurrent nested teams would race on it.
+/// Clang has a similar limitation for conditional lastprivate due to its
+/// use of a shared global variable.
+///
+/// Emits: if (omp_get_level() > 1) ERROR STOP "<message>"
+static void
+emitNestedParallelGuardForCondLp(lower::AbstractConverter &converter,
+ mlir::Location loc) {
+ fir::FirOpBuilder &builder = converter.getFirOpBuilder();
+ mlir::MLIRContext *ctx = builder.getContext();
+ mlir::Type i32Ty = builder.getI32Type();
+
+ // Declare omp_get_level_() -> i32 if not already present.
+ auto funcTy = mlir::FunctionType::get(ctx, {}, {i32Ty});
+ if (!builder.getNamedFunction("omp_get_level_"))
+ builder.createFunction(loc, "omp_get_level_", funcTy);
+
+ mlir::Value level =
+ fir::CallOp::create(builder, loc,
+ builder.getNamedFunction("omp_get_level_"),
+ mlir::ValueRange{})
+ .getResult(0);
+ mlir::Value one = builder.createIntegerConstant(loc, i32Ty, 1);
+ mlir::Value isNested = mlir::arith::CmpIOp::create(
+ builder, loc, mlir::arith::CmpIPredicate::sgt, level, one);
+
+ auto ifOp = fir::IfOp::create(builder, loc, /*resultTypes=*/{}, isNested,
+ /*withElse=*/false);
+ builder.setInsertionPoint(ifOp.getThenRegion().front().getTerminator());
+
+ // Build a global string constant for the error message.
+ llvm::StringRef msg =
+ "orphaned worksharing construct with lastprivate(conditional:) "
+ "is not supported in nested parallelism";
+ std::string globalName = "_lp_cond_nested_msg";
+ size_t msgLen = msg.size();
+ auto charTy = fir::CharacterType::get(ctx, 1, msgLen);
+ if (!builder.getNamedGlobal(globalName)) {
+ fir::GlobalOp global = builder.createGlobal(
+ loc, charTy, globalName, builder.createInternalLinkage(),
+ /*value=*/mlir::Attribute{}, /*isConst=*/true);
+ mlir::Region ®ion = global.getRegion();
+ mlir::Block *block = builder.createBlock(®ion);
+ builder.setInsertionPointToStart(block);
+ mlir::Value val = fir::StringLitOp::create(builder, loc, charTy, msg);
+ fir::HasValueOp::create(builder, loc, val);
+ builder.setInsertionPoint(ifOp.getThenRegion().front().getTerminator());
+ }
+
+ // Declare _FortranAStopStatementText if not already present.
+ mlir::Type i64Ty = builder.getI64Type();
+ mlir::Type i1Ty = builder.getI1Type();
+ mlir::Type ptrTy = builder.getRefType(builder.getIntegerType(8));
+ auto stopTy = mlir::FunctionType::get(ctx, {ptrTy, i64Ty, i1Ty, i1Ty}, {});
+ if (!builder.getNamedFunction("_FortranAStopStatementText"))
+ builder.createFunction(loc, "_FortranAStopStatementText", stopTy);
+
+ mlir::Value msgAddr =
+ fir::AddrOfOp::create(builder, loc, builder.getRefType(charTy),
+ builder.getSymbolRefAttr(globalName));
+ mlir::Value msgPtr = builder.createConvert(loc, ptrTy, msgAddr);
+ mlir::Value len = builder.createIntegerConstant(loc, i64Ty, msgLen);
+ mlir::Value trueVal = builder.createIntegerConstant(loc, i1Ty, 1);
+ mlir::Value falseVal = builder.createIntegerConstant(loc, i1Ty, 0);
+ fir::CallOp::create(builder, loc,
+ builder.getNamedFunction("_FortranAStopStatementText"),
+ mlir::ValueRange{msgPtr, len, trueVal, falseVal});
+
+ builder.setInsertionPointAfter(ifOp);
+}
+
+/// Return the address of a module-scope global for the conditional-lastprivate
+/// reduction struct. This is used in the *orphaned* worksharing case (sections
+/// or wsloop inside a subroutine called from a parallel region) where the
+/// parent op is a FuncOp, not a ParallelOp.
+///
+/// Because there is no enclosing omp.parallel in the same function, a stack
+/// alloca would give every thread its own private copy and the cross-thread
+/// reduction combine would never merge results. A global provides a single
+/// shared address that all threads in the team can reduce into — the same
+/// semantics a dummy argument provides for ordinary user REDUCTION variables.
+///
+/// Nested parallelism (concurrent teams executing the same orphaned construct)
+/// would race on this global; a runtime guard emitted by
+/// emitNestedParallelGuardForCondLp() aborts in that case.
+///
+/// Note: the guard only catches nested parallelism (omp_get_level() > 1). Two
+/// independent top-level parallel regions invoking this routine concurrently
+/// (both at level 1) would still race on the global and are not diagnosed.
+/// Clang's host lowering shares this class of limitation: it tracks the
+/// conditional-last value/index in named internal globals (updated under a
+/// critical region), which are likewise shared across concurrent invocations.
+static mlir::Value
+getOrCreateConditionalLpGlobal(lower::AbstractConverter &converter,
+ mlir::Location loc, fir::RecordType lpType) {
+ fir::FirOpBuilder &builder = converter.getFirOpBuilder();
+
+ // Derive a unique global name from the RecordType name.
+ // Type name is "_lp_cond_t.lN.M", global becomes "_lp_cond_global.lN.M".
+ llvm::StringRef typeName = lpType.getName();
+ assert(typeName.starts_with("_lp_cond_t") &&
+ "unexpected conditional LP type name prefix");
+ std::string globalName =
+ "_lp_cond_global" +
+ typeName.substr(llvm::StringRef("_lp_cond_t").size()).str();
+
+ // Create the global if it does not already exist.
+ // The global is re-initialized by initConditionalLpStruct before each
+ // worksharing construct invocation (to reset values from prior calls),
+ // so a simple zero-init suffices here.
+ fir::GlobalOp global = builder.getNamedGlobal(globalName);
+ if (!global) {
+ builder.createGlobal(loc, lpType, globalName,
+ builder.createInternalLinkage());
+ global = builder.getNamedGlobal(globalName);
+ }
+ assert(global && "global should have been created");
+ return fir::AddrOfOp::create(builder, loc, global.resultType(),
+ global.getSymbol());
+}
+
static mlir::omp::WsloopOp genStandaloneDo(
lower::AbstractConverter &converter, lower::SymMap &symTable,
lower::StatementContext &stmtCtx, semantics::SemanticsContext &semaCtx,
@@ -4427,8 +4874,72 @@ static mlir::omp::WsloopOp genStandaloneDo(
DataSharingProcessor dsp(converter, semaCtx, item->clauses, eval,
/*shouldCollectPreDeterminedSymbols=*/true,
enableDelayedPrivatization, symTable);
+ // Worksharing loops use the private-copy lowering for conditional lastprivate
+ // (each list item gets an ordinary private copy + a reduction accumulator),
+ // which is correct under any schedule including nonmonotonic.
+ dsp.setConditionalLpUsesPrivateCopy(true);
dsp.processStep1(&wsloopClauseOps);
+ // Conditional lastprivate: build struct type, declare_reduction, and
+ // inject a synthetic reduction variable into the wsloop.
+ auto &condLpSyms = dsp.getConditionalLastprivateSymbols();
+ fir::RecordType lpType; // hoisted for post-loop rewrite pass
+ mlir::Value lpAlloca; // hoisted for post-reduction copy-back
+ if (!condLpSyms.empty()) {
+ fir::FirOpBuilder &builder = converter.getFirOpBuilder();
+ // lastprivate(conditional:) is correct under any schedule (including
+ // nonmonotonic): each list item has an ordinary private copy for its
+ // in-loop working value, and a guarded commit (see injectCondLpIndexStores)
+ // keeps the value from the highest canonical index in the reduction
+ // accumulator, so out-of-order chunk execution cannot corrupt the result.
+ lpType = buildConditionalLpType(converter, condLpSyms, loc);
+ mlir::omp::DeclareReductionOp declRedOp =
+ buildConditionalLastPrivateReduction(converter, lpType, condLpSyms);
+
+ // Create the struct alloca OUTSIDE the parent omp.parallel (if any),
+ // so the reduction result persists after the parallel region ends.
+ // In the orphaned case (no enclosing ParallelOp), use a
+ // module-scope global so that all threads share one reduction target.
+ auto enclosingParallel = findEnclosingParallelOp(builder);
+ bool isOrphaned = !enclosingParallel;
+
+ // Guard against nested parallelism in the orphaned case.
+ // Emit this BEFORE touching the global to avoid racing on it.
+ if (isOrphaned)
+ emitNestedParallelGuardForCondLp(converter, loc);
+
+ if (enclosingParallel) {
+ mlir::OpBuilder::InsertionGuard guard(builder);
+ builder.setInsertionPoint(enclosingParallel);
+ lpAlloca = builder.createTemporary(loc, lpType);
+ // Index fields are initialised to -1 so the combiner's "sequentially
+ // last" comparison treats them as "no iteration has written yet"
+ // (any real canonical loop IV >= 0 beats -1).
+ initConditionalLpStructDefault(builder, loc, lpType, lpAlloca);
+ } else {
+ lpAlloca = getOrCreateConditionalLpGlobal(converter, loc, lpType);
+ // The global is shared across all threads. Use omp.single (which
+ // has an implicit barrier at exit) so that exactly one thread
+ // initialises and all threads wait before entering the construct.
+ mlir::omp::SingleOperands initSingleOps;
+ auto singleOp = mlir::omp::SingleOp::create(builder, loc, initSingleOps);
+ mlir::Block *singleBlock = builder.createBlock(&singleOp.getRegion());
+ builder.setInsertionPointToStart(singleBlock);
+ initConditionalLpStructDefault(builder, loc, lpType, lpAlloca);
+ mlir::omp::TerminatorOp::create(builder, loc);
+ builder.setInsertionPointAfter(singleOp);
+ }
+
+ // Append to wsloop clause operands.
+ wsloopClauseOps.reductionVars.push_back(lpAlloca);
+ wsloopClauseOps.reductionByref.push_back(true);
+ wsloopClauseOps.reductionSyms.push_back(
+ mlir::SymbolRefAttr::get(builder.getContext(), declRedOp.getSymName()));
+
+ // Use a null-symbol Object as a sentinel — bindPrivateLike will skip it.
+ wsloopReductionObjects.push_back(Object{{nullptr, std::nullopt}});
+ }
+
mlir::omp::LoopNestOperands loopNestClauseOps;
llvm::SmallVector<const semantics::Symbol *> iv;
genLoopNestClauses(converter, semaCtx, eval, item->clauses, loc,
@@ -4442,9 +4953,102 @@ static mlir::omp::WsloopOp genStandaloneDo(
auto wsloopOp = genWrapperOp<mlir::omp::WsloopOp>(
converter, loc, wsloopClauseOps, wsloopArgs);
+ // Save address-to-name mapping for conditional LP symbols before scoped
+ // binding overwrites them — needed for the post-reduction copy-back.
+ llvm::MapVector<mlir::Value, std::string> condLpOrigAddrs;
+ for (const auto *sym : condLpSyms) {
+ mlir::Value addr = converter.getSymbolAddress(*sym);
+ if (addr)
+ condLpOrigAddrs[addr] = sym->name().ToString();
+ }
+
+ // Conditional LP symbols are bound to their ordinary private copies by normal
+ // privatization. Capture each private-copy address (while the bindings are
+ // live) so the injection pass can find writes to it and commit them into the
+ // reduction accumulator struct.
+ llvm::MapVector<mlir::Value, std::string> condLpValAddrs;
+ auto loopPostIvCb = [&](mlir::Operation *) {
+ if (condLpSyms.empty())
+ return;
+ for (const auto *sym : condLpSyms)
+ condLpValAddrs[converter.getSymbolAddress(*sym)] = sym->name().ToString();
+ };
+
genLoopNestOp(converter, symTable, semaCtx, eval, loc, queue, item,
loopNestClauseOps, iv, {{wsloopOp, wsloopArgs}},
- llvm::omp::Directive::OMPD_do, dsp);
+ llvm::omp::Directive::OMPD_do, dsp, loopPostIvCb);
+
+ // Inject index stores after each assignment to a conditional LP value field.
+ if (!condLpSyms.empty()) {
+ fir::FirOpBuilder &builder = converter.getFirOpBuilder();
+ auto blockArgIface =
+ mlir::cast<mlir::omp::BlockArgOpenMPOpInterface>(*wsloopOp);
+ mlir::Value structArg = blockArgIface.getReductionBlockArgs().back();
+ auto loopNestOp =
+ mlir::cast<mlir::omp::LoopNestOp>(wsloopOp.getWrappedLoop());
+ injectCondLpIndexStores(
+ builder, loc, lpType, structArg, loopNestOp.getRegion(), condLpValAddrs,
+ [&](fir::FirOpBuilder &b, mlir::Location l) -> mlir::Value {
+ return computeFlattenedCanonicalIV(b, l, loopNestOp);
+ });
+ }
+
+ // Post-reduction copy-back. When nowait is absent, the wsloop's implicit
+ // end-barrier guarantees all reductions are combined. When nowait is
+ // present, an explicit barrier is needed before reading the struct.
+ // Wrapped in omp.single so exactly one thread performs the stores, at the
+ // worksharing construct's barrier (the semantically correct finalization
+ // point) inside the enclosing parallel. Because this copy-back adds a second
+ // immediately-nested construct to the parallel, the parallel is not marked
+ // omp.combined (see the combined-marking logic in genOMPDispatch).
+ if (!condLpSyms.empty()) {
+ fir::FirOpBuilder &builder = converter.getFirOpBuilder();
+ mlir::OpBuilder::InsertionGuard guard(builder);
+
+ // Insert right after the wsloop, still inside the parallel body.
+ builder.setInsertionPointAfter(wsloopOp);
+
+ if (wsloopClauseOps.nowait)
+ mlir::omp::BarrierOp::create(builder, loc);
+
+ mlir::omp::SingleOperands singleClauseOps;
+ auto singleOp = mlir::omp::SingleOp::create(builder, loc, singleClauseOps);
+ mlir::Block *singleBlock = builder.createBlock(&singleOp.getRegion());
+ builder.setInsertionPointToStart(singleBlock);
+
+ for (auto &[origAddr, symName] : condLpOrigAddrs) {
+ unsigned valFieldIdx = lpType.getFieldIndex(symName);
+ mlir::Type valType = lpType.getType(valFieldIdx);
+
+ fir::IntOrValue valFIdx =
+ mlir::IntegerAttr::get(builder.getI32Type(), valFieldIdx);
+ mlir::Value fieldAddr = fir::CoordinateOp::create(
+ builder, loc, builder.getRefType(valType), lpAlloca,
+ llvm::SmallVector<fir::IntOrValue, 1>{valFIdx});
+ mlir::Value val = fir::LoadOp::create(builder, loc, fieldAddr);
+
+ // Only copy back if some iteration actually assigned to this variable
+ // (index >= 0). Otherwise the original must not be overwritten.
+ unsigned idxFieldIdx = lpType.getFieldIndex("$" + symName);
+ fir::IntOrValue idxFIdx =
+ mlir::IntegerAttr::get(builder.getI32Type(), idxFieldIdx);
+ mlir::Value idxAddr = fir::CoordinateOp::create(
+ builder, loc, builder.getRefType(builder.getI64Type()), lpAlloca,
+ llvm::SmallVector<fir::IntOrValue, 1>{idxFIdx});
+ mlir::Value idxVal = fir::LoadOp::create(builder, loc, idxAddr);
+ mlir::Value zero =
+ builder.createIntegerConstant(loc, builder.getI64Type(), 0);
+ mlir::Value cond = mlir::arith::CmpIOp::create(
+ builder, loc, mlir::arith::CmpIPredicate::sge, idxVal, zero);
+ auto ifOp =
+ fir::IfOp::create(builder, loc, cond, /*withElseRegion=*/false);
+ builder.setInsertionPointToStart(&ifOp.getThenRegion().front());
+ fir::StoreOp::create(builder, loc, val, origAddr);
+ builder.setInsertionPointAfter(ifOp);
+ }
+ mlir::omp::TerminatorOp::create(builder, loc);
+ }
+
return wsloopOp;
}
@@ -4494,6 +5098,8 @@ genStandaloneSimd(lower::AbstractConverter &converter, lower::SymMap &symTable,
enableDelayedPrivatization, symTable);
dsp.processStep1(&simdClauseOps);
+ if (!dsp.getConditionalLastprivateSymbols().empty())
+ TODO(loc, "lastprivate(conditional:) on simd construct");
mlir::omp::LoopNestOperands loopNestClauseOps;
llvm::SmallVector<const semantics::Symbol *> iv;
genLoopNestClauses(converter, semaCtx, eval, item->clauses, loc,
@@ -4862,6 +5468,8 @@ static mlir::omp::WsloopOp genCompositeDoSimd(
/*shouldCollectPreDeterminedSymbols=*/false,
/*useDelayedPrivatization=*/true, symTable);
wsloopItemDSP.processStep1(&wsloopClauseOps);
+ if (!wsloopItemDSP.getConditionalLastprivateSymbols().empty())
+ TODO(loc, "lastprivate(conditional:) on do simd composite construct");
DataSharingProcessor simdItemDSP(converter, semaCtx, simdItem->clauses, eval,
/*shouldCollectPreDeterminedSymbols=*/true,
@@ -5148,6 +5756,20 @@ static void genOMPDispatch(lower::AbstractConverter &converter,
if (combinableDirs.test(firstLeafDir))
isCombined = true;
}
+ // Conditional-lastprivate lowering emits an auxiliary omp.single copy-back
+ // as a sibling of the worksharing op inside the same region. That makes
+ // the region an immediate nesting of two constructs rather than one, so it
+ // no longer qualifies as combined. Detect this by counting eligible nested
+ // constructs and clear the combined status when there is more than one.
+ if (isCombined) {
+ int eligibleNested = 0;
+ for (mlir::Operation &nested : newOp->getRegion(0).getOps())
+ if (llvm::isa<mlir::omp::ComposableOpInterface,
+ mlir::omp::LoopWrapperInterface>(nested))
+ ++eligibleNested;
+ if (eligibleNested > 1)
+ isCombined = false;
+ }
if (isCombined)
llvm::cast<mlir::omp::ComposableOpInterface>(newOp).setCombined(true);
}
@@ -5414,6 +6036,423 @@ getReductionType(lower::AbstractConverter &converter,
return reductionType;
}
+/// Compute a flattened canonical (0-based, always ascending) iteration number
+/// from all loop IVs. For a single loop, this is simply (IV - LB) / step.
+/// For collapsed loops with dimensions d0..dN, the flattened index is:
+/// c0 * (N1*N2*...*Nk) + c1 * (N2*...*Nk) + ... + ck
+/// where ci = (IVi - LBi) / stepi and Ni = (UBi - LBi) / stepi + 1.
+/// This yields a unique monotonic index regardless of loop direction,
+/// which is essential for the combiner's `sgt` comparison to correctly
+/// identify the sequentially last iteration.
+static mlir::Value
+computeFlattenedCanonicalIV(fir::FirOpBuilder &builder, mlir::Location loc,
+ mlir::omp::LoopNestOp loopNestOp) {
+ mlir::Region ®ion = loopNestOp.getRegion();
+ auto lbs = loopNestOp.getLoopLowerBounds();
+ auto ubs = loopNestOp.getLoopUpperBounds();
+ auto steps = loopNestOp.getLoopSteps();
+ unsigned numDims = lbs.size();
+
+ // Use i64 for the flattened index to avoid overflow.
+ mlir::Type i64Ty = builder.getI64Type();
+
+ // Compute canonical IV and trip count for each dimension.
+ llvm::SmallVector<mlir::Value> canonIVs(numDims);
+ llvm::SmallVector<mlir::Value> tripCounts(numDims);
+ for (unsigned d = 0; d < numDims; ++d) {
+ // Widen the IV, bounds and step to i64 BEFORE any subtraction/division so
+ // that the intermediate arithmetic cannot overflow the (possibly narrower)
+ // loop-variable type. E.g. for an i32 loop with lb=INT_MIN, ub=INT_MAX,
+ // computing (ub - lb) in i32 would wrap; doing it in i64 is exact.
+ mlir::Value iv = fir::ConvertOp::create(builder, loc, i64Ty,
+ region.front().getArgument(d));
+ mlir::Value lb = fir::ConvertOp::create(builder, loc, i64Ty, lbs[d]);
+ mlir::Value ub = fir::ConvertOp::create(builder, loc, i64Ty, ubs[d]);
+ mlir::Value step = fir::ConvertOp::create(builder, loc, i64Ty, steps[d]);
+
+ mlir::Value
diff = mlir::arith::SubIOp::create(builder, loc, iv, lb);
+ canonIVs[d] = mlir::arith::DivSIOp::create(builder, loc,
diff , step);
+
+ // Trip count: (UB - LB) / step + 1 (loop bounds are inclusive).
+ mlir::Value range = mlir::arith::SubIOp::create(builder, loc, ub, lb);
+ mlir::Value trips = mlir::arith::DivSIOp::create(builder, loc, range, step);
+ mlir::Value one = builder.createIntegerConstant(loc, i64Ty, 1);
+ tripCounts[d] = mlir::arith::AddIOp::create(builder, loc, trips, one);
+ }
+
+ // Flatten: result = c0*N1*N2*...*Nk + c1*N2*...*Nk + ... + ck
+ mlir::Value flatIdx = canonIVs[0];
+ for (unsigned d = 1; d < numDims; ++d) {
+ flatIdx = mlir::arith::MulIOp::create(builder, loc, flatIdx, tripCounts[d]);
+ flatIdx = mlir::arith::AddIOp::create(builder, loc, flatIdx, canonIVs[d]);
+ }
+ return flatIdx;
+}
+
+/// Bind conditional lastprivate symbols to their value fields inside the
+/// reduction struct. This must be called \b before body lowering so that all
+/// references to the LP symbols resolve to struct field addresses directly,
+/// avoiding the need for a post-hoc address-replacement rewrite.
+///
+/// Returns a map from the newly-created struct-field addresses to symbol names
+/// so that \c injectCondLpIndexStores can later locate writes to these fields.
+static llvm::MapVector<mlir::Value, std::string> bindCondLpSymsToStructFields(
+ lower::AbstractConverter &converter, mlir::Location loc,
+ fir::RecordType lpType, mlir::Value structArg,
+ const llvm::SetVector<const semantics::Symbol *> &condLpSyms) {
+ fir::FirOpBuilder &builder = converter.getFirOpBuilder();
+ llvm::MapVector<mlir::Value, std::string> valAddrToSymName;
+ for (const auto *sym : condLpSyms) {
+ std::string symName = sym->name().ToString();
+ unsigned valFieldIdx = lpType.getFieldIndex(symName);
+ mlir::Type valType = lpType.getType(valFieldIdx);
+
+ fir::IntOrValue valFIdx =
+ mlir::IntegerAttr::get(builder.getI32Type(), valFieldIdx);
+ mlir::Value valAddr = fir::CoordinateOp::create(
+ builder, loc, builder.getRefType(valType), structArg,
+ llvm::SmallVector<fir::IntOrValue, 1>{valFIdx});
+
+ converter.bindSymbol(*sym, valAddr);
+ valAddrToSymName[valAddr] = symName;
+ }
+ return valAddrToSymName;
+}
+
+/// Walk the given region to find writes to a conditional-lastprivate item --
+/// hlfir.assign / fir.store and OpenMP atomic write/update/capture -- whose
+/// target address is one of those tracked in \p valAddrToSymName (a worksharing
+/// loop's ordinary private copy, or a section's struct value field). After
+/// each such write, inject a guarded commit (using \p genIndexVal for the
+/// canonical index) that stores the value and index into the reduction struct's
+/// paired fields when that index is the highest seen so far.
+static void injectCondLpIndexStores(
+ fir::FirOpBuilder &builder, mlir::Location loc, fir::RecordType lpType,
+ mlir::Value structArg, mlir::Region ®ion,
+ const llvm::MapVector<mlir::Value, std::string> &valAddrToSymName,
+ llvm::function_ref<mlir::Value(fir::FirOpBuilder &, mlir::Location)>
+ genIndexVal) {
+ // Look through hlfir.declare to find the underlying struct field address.
+ // When symbols are bound via bindCondLpSymsToStructFields, the lowering
+ // wraps the fir.coordinate_of result in hlfir.declare, so the actual write
+ // target is the declare result rather than the raw coordinate_of.
+ auto lookThroughDeclare = [](mlir::Value v) -> mlir::Value {
+ if (auto declOp = v.getDefiningOp<hlfir::DeclareOp>())
+ return declOp.getMemref();
+ return v;
+ };
+ // valAddrToSymName maps each conditional-lastprivate item's tracked address
+ // (a worksharing loop's ordinary private copy, or a section's struct value
+ // field) to its name. A write may target the hlfir.declare result or its
+ // memref, so match either form.
+ auto matchName = [&](mlir::Value v) -> const std::string * {
+ auto it = valAddrToSymName.find(v);
+ if (it != valAddrToSymName.end())
+ return &it->second;
+ it = valAddrToSymName.find(lookThroughDeclare(v));
+ if (it != valAddrToSymName.end())
+ return &it->second;
+ return nullptr;
+ };
+
+ llvm::SmallVector<std::pair<mlir::Operation *, mlir::Value>> writes;
+ region.walk([&](hlfir::AssignOp assignOp) {
+ if (matchName(assignOp.getLhs()))
+ writes.push_back({assignOp, assignOp.getLhs()});
+ });
+ region.walk([&](fir::StoreOp storeOp) {
+ if (matchName(storeOp.getMemref()))
+ writes.push_back({storeOp, storeOp.getMemref()});
+ });
+ // An OpenMP atomic write/update/capture of a conditional-lastprivate item
+ // also assigns it (clang tracks this too); instrument the same guarded commit
+ // after the atomic operation. (An atomic on a per-thread-private item is
+ // redundant but legal.) A standalone atomic write/update is committed right
+ // after it. For an atomic.capture the write/update lives inside the capture
+ // op's region -- injecting there would violate the capture verifier -- so
+ // those nested ops are skipped and the commit is emitted after the whole
+ // capture op, reading the (already updated) item.
+ auto notInCapture = [](mlir::Operation *op) {
+ return !op->getParentOfType<mlir::omp::AtomicCaptureOp>();
+ };
+ region.walk([&](mlir::omp::AtomicWriteOp atomicOp) {
+ if (matchName(atomicOp.getX()) && notInCapture(atomicOp))
+ writes.push_back({atomicOp, atomicOp.getX()});
+ });
+ region.walk([&](mlir::omp::AtomicUpdateOp atomicOp) {
+ if (matchName(atomicOp.getX()) && notInCapture(atomicOp))
+ writes.push_back({atomicOp, atomicOp.getX()});
+ });
+ region.walk([&](mlir::omp::AtomicCaptureOp captureOp) {
+ // A capture assigns two locations: the write/update target (getX) and the
+ // read destination (getV, which captures the item's value). Either may be
+ // a conditional-lastprivate item, so record every match and commit each
+ // after the whole capture op (reading the item's post-capture value).
+ captureOp.walk([&](mlir::Operation *inner) {
+ if (auto w = mlir::dyn_cast<mlir::omp::AtomicWriteOp>(inner)) {
+ if (matchName(w.getX()))
+ writes.push_back({captureOp, w.getX()});
+ } else if (auto u = mlir::dyn_cast<mlir::omp::AtomicUpdateOp>(inner)) {
+ if (matchName(u.getX()))
+ writes.push_back({captureOp, u.getX()});
+ } else if (auto r = mlir::dyn_cast<mlir::omp::AtomicReadOp>(inner)) {
+ if (matchName(r.getV()))
+ writes.push_back({captureOp, r.getV()});
+ }
+ });
+ });
+
+ // Compute the canonical index once at the region entry so that it dominates
+ // all write sites (which may be inside nested fir.if blocks).
+ mlir::Value indexVal;
+ if (!writes.empty()) {
+ mlir::OpBuilder::InsertionGuard guard(builder);
+ builder.setInsertionPointToStart(®ion.front());
+ indexVal = genIndexVal(builder, loc);
+ if (indexVal.getType() != builder.getI64Type())
+ indexVal =
+ fir::ConvertOp::create(builder, loc, builder.getI64Type(), indexVal);
+ }
+
+ for (auto &[writeOp, writeAddr] : writes) {
+ const std::string *namePtr = matchName(writeAddr);
+ assert(namePtr &&
+ "collected write site must map to a conditional-LP symbol");
+ const std::string &symName = *namePtr;
+ unsigned valFieldIdx = lpType.getFieldIndex(symName);
+ unsigned idxFieldIdx = lpType.getFieldIndex("$" + symName);
+ mlir::Type valType = lpType.getType(valFieldIdx);
+ mlir::Type idxType = lpType.getType(idxFieldIdx);
+
+ mlir::OpBuilder::InsertionGuard guard(builder);
+ builder.setInsertionPointAfter(writeOp);
+
+ // The value just written to the private copy of the list item.
+ mlir::Value curVal = fir::LoadOp::create(builder, loc, writeAddr);
+
+ fir::IntOrValue valFIdx =
+ mlir::IntegerAttr::get(builder.getI32Type(), valFieldIdx);
+ mlir::Value valAddr = fir::CoordinateOp::create(
+ builder, loc, builder.getRefType(valType), structArg,
+ llvm::SmallVector<fir::IntOrValue, 1>{valFIdx});
+ fir::IntOrValue idxFIdx =
+ mlir::IntegerAttr::get(builder.getI32Type(), idxFieldIdx);
+ mlir::Value idxAddr = fir::CoordinateOp::create(
+ builder, loc, builder.getRefType(idxType), structArg,
+ llvm::SmallVector<fir::IntOrValue, 1>{idxFIdx});
+
+ // Guarded commit into the accumulator: keep the value from the highest
+ // canonical index seen so far. Correct under any schedule (including
+ // nonmonotonic): a lower-index assignment executed out of order cannot
+ // overwrite a higher-index one.
+ mlir::Value curIdx = fir::LoadOp::create(builder, loc, idxAddr);
+ mlir::Value cmp = mlir::arith::CmpIOp::create(
+ builder, loc, mlir::arith::CmpIPredicate::sge, indexVal, curIdx);
+ auto ifOp = fir::IfOp::create(builder, loc, cmp, /*withElseRegion=*/false);
+ builder.setInsertionPointToStart(&ifOp.getThenRegion().front());
+ fir::StoreOp::create(builder, loc, curVal, valAddr);
+ fir::StoreOp::create(builder, loc, indexVal, idxAddr);
+ }
+}
+
+static mlir::omp::DeclareReductionOp buildConditionalLastPrivateReduction(
+ lower::AbstractConverter &converter, fir::RecordType lpCondType,
+ const llvm::SetVector<const semantics::Symbol *> &condLpSyms) {
+
+ // Determine, per value field, whether the corresponding list item is also
+ // firstprivate. Value fields are ordered the same as condLpSyms.
+ llvm::SmallVector<bool> valIsFirstprivate;
+ for (const semantics::Symbol *sym : condLpSyms)
+ valIsFirstprivate.push_back(
+ sym->test(semantics::Symbol::Flag::OmpFirstPrivate));
+
+ // Init callback: initialize all fields of each thread's private copy.
+ // Value fields get 0 and index fields get -1 (identity). For a list item
+ // that is also firstprivate, the value field is instead copied from the seed
+ // (ompOrig). This matters for the sections path, where the list item is
+ // bound directly to the struct value field and the caller seeds ompOrig with
+ // the original value, so firstprivate initialization is observed per thread.
+ // For worksharing loops ompOrig is only identity-seeded (the firstprivate
+ // value lives in the item's ordinary private copy, not the accumulator), so
+ // this copy is a harmless no-op there.
+ //
+ // Returns a null mlir::Value to signal that initialization has already
+ // been performed directly on ompPriv. The reduction infrastructure
+ // (populateByRefInitAndCleanupRegions → initAndCleanupUnboxedDerivedType)
+ // checks for a non-null scalarInitValue before emitting a store, so
+ // returning null here safely skips the redundant store.
+ auto genInitValueCB = [lpCondType, valIsFirstprivate](
+ fir::FirOpBuilder &builder, mlir::Location loc,
+ mlir::Type type, mlir::Value ompOrig,
+ mlir::Value ompPriv) -> mlir::Value {
+ initConditionalLpStructDefault(builder, loc, lpCondType, ompPriv);
+ fir::RecordType lpType = lpCondType;
+ llvm::ArrayRef<std::pair<std::string, mlir::Type>> fields =
+ lpType.getTypeList();
+ unsigned numVars = fields.size() / 2;
+ for (unsigned i = 0; i < numVars; ++i) {
+ if (!valIsFirstprivate[i])
+ continue;
+ mlir::Type fieldTy = fields[i].second;
+ fir::IntOrValue idx = mlir::IntegerAttr::get(builder.getI32Type(), i);
+ mlir::Value privAddr = fir::CoordinateOp::create(
+ builder, loc, builder.getRefType(fieldTy), ompPriv,
+ llvm::SmallVector<fir::IntOrValue, 1>{idx});
+ mlir::Value origAddr = fir::CoordinateOp::create(
+ builder, loc, builder.getRefType(fieldTy), ompOrig,
+ llvm::SmallVector<fir::IntOrValue, 1>{idx});
+ mlir::Value origVal = fir::LoadOp::create(builder, loc, origAddr);
+ fir::StoreOp::create(builder, loc, origVal, privAddr);
+ }
+ return mlir::Value{};
+ };
+
+ // Combiner callback: for each (value, index) pair, pick the later iteration.
+ // Fields are arranged as: {val_0, ..., val_{N-1}, idx_0, ..., idx_{N-1}}
+ // where idx field names are "$" + val field name.
+ // If rhs.idx > lhs.idx, copy rhs value and index into lhs.
+ auto genCombinerCB = [lpCondType](fir::FirOpBuilder &builder,
+ mlir::Location loc, mlir::Type type,
+ mlir::Value lhs, mlir::Value rhs,
+ bool isByRef) {
+ fir::RecordType lpType = lpCondType; // non-const copy for getFieldIndex
+ llvm::ArrayRef<std::pair<std::string, mlir::Type>> fields =
+ lpType.getTypeList();
+ unsigned numVars = fields.size() / 2;
+
+ // Walk the first half (value fields). Index field name = "$" +
+ // value name. The "$" character is invalid in Fortran identifiers,
+ // so the prefix cannot collide with any user variable name.
+ for (unsigned i = 0; i < numVars; ++i) {
+ auto [valName, valType] = fields[i];
+ std::string idxName = "$" + valName;
+ unsigned valIdx = lpType.getFieldIndex(valName);
+ unsigned idxIdx = lpType.getFieldIndex(idxName);
+ mlir::Type idxType = lpType.getType(idxIdx);
+
+ // Get addresses of LHS and RHS index fields
+ fir::IntOrValue idxFieldIdx =
+ mlir::IntegerAttr::get(builder.getI32Type(), idxIdx);
+ mlir::Value lhsIdxAddr = fir::CoordinateOp::create(
+ builder, loc, builder.getRefType(idxType), lhs,
+ llvm::SmallVector<fir::IntOrValue, 1>{idxFieldIdx});
+ mlir::Value rhsIdxAddr = fir::CoordinateOp::create(
+ builder, loc, builder.getRefType(idxType), rhs,
+ llvm::SmallVector<fir::IntOrValue, 1>{idxFieldIdx});
+
+ mlir::Value lhsIdx = fir::LoadOp::create(builder, loc, lhsIdxAddr);
+ mlir::Value rhsIdx = fir::LoadOp::create(builder, loc, rhsIdxAddr);
+
+ // Compare: rhs index > lhs index (signed, iteration indices)
+ mlir::Value cmp = mlir::arith::CmpIOp::create(
+ builder, loc, mlir::arith::CmpIPredicate::sgt, rhsIdx, lhsIdx);
+
+ // If RHS comes from a later iteration, copy its value and index to LHS
+ auto ifOp = fir::IfOp::create(builder, loc, cmp, /*else*/ false);
+ builder.setInsertionPointToStart(&ifOp.getThenRegion().front());
+
+ // Copy value field: rhs.val_s → lhs.val_s
+ fir::IntOrValue valFieldIdx =
+ mlir::IntegerAttr::get(builder.getI32Type(), valIdx);
+ mlir::Value rhsValAddr = fir::CoordinateOp::create(
+ builder, loc, builder.getRefType(valType), rhs,
+ llvm::SmallVector<fir::IntOrValue, 1>{valFieldIdx});
+ mlir::Value lhsValAddr = fir::CoordinateOp::create(
+ builder, loc, builder.getRefType(valType), lhs,
+ llvm::SmallVector<fir::IntOrValue, 1>{valFieldIdx});
+ mlir::Value rhsVal = fir::LoadOp::create(builder, loc, rhsValAddr);
+ fir::StoreOp::create(builder, loc, rhsVal, lhsValAddr);
+
+ // Copy index field: rhs.idx_s → lhs.idx_s
+ fir::StoreOp::create(builder, loc, rhsIdx, lhsIdxAddr);
+
+ builder.setInsertionPointAfter(ifOp);
+ }
+
+ // By-ref: yield the accumulator (LHS)
+ mlir::omp::YieldOp::create(builder, loc, lhs);
+ };
+
+ // RecordType is always by-ref
+ bool isByRef = true;
+ mlir::Location loc = converter.getCurrentLocation();
+ mlir::Type redType = fir::ReferenceType::get(lpCondType);
+ std::string reductionName = ReductionProcessor::getReductionName(
+ "lp_cond", converter.getKindMap(), redType, isByRef);
+
+ return ReductionProcessor::createDeclareReductionHelper<
+ mlir::omp::DeclareReductionOp>(converter, reductionName, redType, loc,
+ isByRef, genCombinerCB, genInitValueCB);
+}
+
+/// Build a FIR RecordType for conditional lastprivate reduction.
+/// For symbols {x, y}, creates:
+/// !fir.type<_lp_cond_t.lN.M{x:T_x, y:T_y, kx:i64, ky:i64}>
+/// where N is the source line number and M is a monotonic counter.
+static fir::RecordType buildConditionalLpType(
+ lower::AbstractConverter &converter,
+ const llvm::SetVector<const semantics::Symbol *> &condLpSyms,
+ mlir::Location loc) {
+ fir::FirOpBuilder &builder = converter.getFirOpBuilder();
+ mlir::MLIRContext *context = builder.getContext();
+
+ // Derive a unique suffix from the source location and a monotonic counter.
+ // The line number makes names traceable to source; the counter prevents
+ // collisions when INCLUDE files place directives on identical line numbers.
+ // Use atomic for thread-safety in case flang ever lowers in parallel.
+ static std::atomic<unsigned> counter{0};
+ unsigned line = 0;
+ if (auto fileLoc = mlir::dyn_cast<mlir::FileLineColLoc>(loc))
+ line = fileLoc.getLine();
+ else if (auto fusedLoc = mlir::dyn_cast<mlir::FusedLoc>(loc)) {
+ for (mlir::Location sub : fusedLoc.getLocations()) {
+ if (auto fileSub = mlir::dyn_cast<mlir::FileLineColLoc>(sub)) {
+ line = fileSub.getLine();
+ break;
+ }
+ }
+ }
+ std::string typeName =
+ "_lp_cond_t.l" + std::to_string(line) + "." + std::to_string(counter++);
+
+ // The counter in typeName makes each call produce a fresh, unfinalized
+ // record type, so there is no existing type to reuse; always build it.
+ auto lpCondType = fir::RecordType::get(context, typeName);
+
+ // Build field list: first all value fields, then all index fields.
+ // Grouping values before indices (rather than interleaving value/index
+ // pairs) can reduce padding holes when value types
diff er from i64.
+ llvm::SmallVector<std::pair<std::string, mlir::Type>> fields;
+
+ // Value fields first. Semantics has already restricted the list items to
+ // whole scalar variables of intrinsic numeric or logical type; assert that
+ // invariant here to catch any semantic regression in assertions builds.
+ for (const auto *sym : condLpSyms) {
+ const semantics::Symbol &ultimate = sym->GetUltimate();
+ const semantics::DeclTypeSpec *type = ultimate.GetType();
+ assert(ultimate.Rank() == 0 && type &&
+ (type->category() == semantics::DeclTypeSpec::Category::Numeric ||
+ type->category() == semantics::DeclTypeSpec::Category::Logical) &&
+ !semantics::IsAllocatableOrPointer(ultimate) &&
+ "conditional lastprivate requires a scalar intrinsic "
+ "numeric/logical, non-pointer/allocatable variable");
+ std::string symName = sym->name().ToString();
+ mlir::Type symType = converter.genType(*sym);
+ fields.push_back({symName, symType});
+ }
+
+ // Then index fields (i64).
+ for (const auto *sym : condLpSyms) {
+ std::string indexName = "$" + sym->name().ToString();
+ fields.push_back({indexName, builder.getI64Type()});
+ }
+
+ // Finalize the type with the field list
+ lpCondType.finalize({}, fields);
+
+ return lpCondType;
+}
+
// Represent the reduction combiner as a clause, return reference to it.
// If there is a "combiner" clause already present, do nothing. Otherwise
// manufacture a combiner clause from the combiner expression on the reduction
diff --git a/flang/lib/Lower/Support/ReductionProcessor.cpp b/flang/lib/Lower/Support/ReductionProcessor.cpp
index 5bdc92454f029..8baf607052e65 100644
--- a/flang/lib/Lower/Support/ReductionProcessor.cpp
+++ b/flang/lib/Lower/Support/ReductionProcessor.cpp
@@ -80,6 +80,13 @@ ReductionProcessor::createDeclareReduction<fir::DeclareReductionOp>(
const ReductionIdentifier redId, mlir::Type type, mlir::Location loc,
bool isByRef);
+template mlir::omp::DeclareReductionOp
+ReductionProcessor::createDeclareReductionHelper<mlir::omp::DeclareReductionOp>(
+ AbstractConverter &converter, llvm::StringRef reductionOpName,
+ mlir::Type type, mlir::Location loc, bool isByRef,
+ GenCombinerCBTy genCombinerCB, GenInitValueCBTy genInitValueCB,
+ const semantics::Symbol *sym);
+
ReductionProcessor::ReductionIdentifier ReductionProcessor::getReductionType(
const omp::clause::ProcedureDesignator &pd) {
auto redType = llvm::StringSwitch<std::optional<ReductionIdentifier>>(
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index 7bd5f1720fb3c..ac7a3f425732f 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -5247,6 +5247,57 @@ void OmpStructureChecker::Enter(const parser::OmpClause::Lastprivate &x) {
using LastprivateModifier = parser::OmpLastprivateModifier;
if (auto *modifier{OmpGetUniqueModifier<LastprivateModifier>(modifiers)}) {
CheckLastprivateModifier(*modifier);
+ if (modifier->v == LastprivateModifier::Value::Conditional) {
+ // A conditional lastprivate list item must be a scalar variable of
+ // intrinsic Numeric or Logical category. Arrays are excluded by the
+ // rank check; character, derived, and polymorphic entities by the
+ // category check.
+ auto checkConditionalItem{[&](const Symbol &symbol,
+ const parser::CharBlock &source) {
+ // Resolve host/use-association so the type and attribute checks see
+ // the entity's real properties, not the local association symbol.
+ const Symbol &ultimate{symbol.GetUltimate()};
+ const DeclTypeSpec *type{ultimate.GetType()};
+ bool isScalarIntrinsicNonChar{ultimate.Rank() == 0 && type &&
+ (type->category() == DeclTypeSpec::Category::Numeric ||
+ type->category() == DeclTypeSpec::Category::Logical)};
+ if (!isScalarIntrinsicNonChar) {
+ context_.Say(source,
+ "A list item that appears in a LASTPRIVATE clause with the "
+ "CONDITIONAL modifier must be a scalar variable with intrinsic "
+ "type, as defined by the Fortran language, excluding character "
+ "type, but '%s' is not"_err_en_US,
+ symbol.name());
+ } else if (IsAllocatableOrPointer(ultimate)) {
+ // Standard-legal, but lowering does not yet preserve descriptors.
+ // TODO: support POINTER/ALLOCATABLE conditional lastprivate.
+ context_.Say(source,
+ "A POINTER or ALLOCATABLE list item is not yet supported by "
+ "Flang in a LASTPRIVATE clause with the CONDITIONAL modifier, "
+ "'%s'"_err_en_US,
+ symbol.name());
+ }
+ }};
+ // Check whole variables (Designator -> DataRef -> Name) and
+ // common blocks (a bare Name, rejected above as having no type).
+ // Array elements, sections, components, and substrings are other
+ // designator forms, left to the general OpenMP object diagnostics.
+ for (const parser::OmpObject &object : objectList.v) {
+ const parser::Name *name{std::get_if<parser::Name>(&object.u)};
+ if (!name) {
+ if (const auto *designator{
+ std::get_if<parser::Designator>(&object.u)}) {
+ if (const auto *dataRef{
+ std::get_if<parser::DataRef>(&designator->u)}) {
+ name = std::get_if<parser::Name>(&dataRef->u);
+ }
+ }
+ }
+ if (name && name->symbol) {
+ checkConditionalItem(*name->symbol, name->source);
+ }
+ }
+ }
}
}
}
@@ -5257,14 +5308,24 @@ void OmpStructureChecker::CheckLastprivateModifier(
const parser::OmpLastprivateModifier &modifier) {
using LastprivateModifier = parser::OmpLastprivateModifier;
const DirectiveContext &dirCtx{GetContext()};
- if (modifier.v == LastprivateModifier::Value::Conditional &&
- dirCtx.directive == llvm::omp::Directive::OMPD_taskloop) {
- // [5.2:268:17]
- // The conditional lastprivate-modifier must not be specified.
+ if (modifier.v != LastprivateModifier::Value::Conditional) {
+ return;
+ }
+ // The conditional lastprivate-modifier must not be specified on a taskloop
+ // or distribute directive. Checking leaf constructs also covers combined
+ // and composite forms such as `teams distribute` or `taskloop simd`.
+ llvm::ArrayRef<llvm::omp::Directive> leafs{
+ llvm::omp::getLeafConstructsOrSelf(dirCtx.directive)};
+ if (llvm::is_contained(leafs, llvm::omp::Directive::OMPD_taskloop)) {
context_.Say(GetContext().clauseSource,
"'CONDITIONAL' modifier on lastprivate clause with TASKLOOP "
"directive is not allowed"_err_en_US);
}
+ if (llvm::is_contained(leafs, llvm::omp::Directive::OMPD_distribute)) {
+ context_.Say(GetContext().clauseSource,
+ "'CONDITIONAL' modifier on lastprivate clause with DISTRIBUTE "
+ "directive is not allowed"_err_en_US);
+ }
}
void OmpStructureChecker::Enter(const parser::OmpClause::Copyin &x) {
diff --git a/flang/test/Lower/OpenMP/Todo/lastprivate-conditional.f90 b/flang/test/Lower/OpenMP/Todo/lastprivate-conditional.f90
deleted file mode 100644
index 2b96093da3a8f..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/lastprivate-conditional.f90
+++ /dev/null
@@ -1,12 +0,0 @@
-! RUN: %not_todo_cmd bbc -emit-fir -fopenmp -fopenmp-version=50 -o - %s 2>&1 | FileCheck %s
-! RUN: %not_todo_cmd %flang_fc1 -emit-fir -fopenmp -fopenmp-version=50 -o - %s 2>&1 | FileCheck %s
-
-! CHECK: not yet implemented: lastprivate clause with CONDITIONAL modifier
-subroutine foo()
- integer :: x, i
- x = 1
- !$omp parallel do lastprivate(conditional: x)
- do i = 1, 100
- x = x + 1
- enddo
-end
diff --git a/flang/test/Lower/OpenMP/lastprivate-conditional-atomic.f90 b/flang/test/Lower/OpenMP/lastprivate-conditional-atomic.f90
new file mode 100644
index 0000000000000..d175773210ca1
--- /dev/null
+++ b/flang/test/Lower/OpenMP/lastprivate-conditional-atomic.f90
@@ -0,0 +1,154 @@
+! Test that `lastprivate(conditional:)` instruments OpenMP atomic
+! write/update/capture of the list item with the same canonical-index guarded
+! commit used for ordinary assignments. An atomic on a per-thread-private item
+! is redundant but legal, and clang tracks atomic assignments to a conditional
+! lastprivate item too, so the value must still be captured.
+
+! RUN: bbc -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+
+subroutine test_atomic_write(n, x)
+ implicit none
+ integer, intent(in) :: n
+ integer, intent(inout) :: x
+ integer :: i
+ !$omp parallel do lastprivate(conditional: x)
+ do i = 1, n
+ if (mod(i, 2) == 0) then
+ !$omp atomic write
+ x = i
+ end if
+ end do
+end subroutine
+! CHECK-LABEL: func.func @_QPtest_atomic_write
+! CHECK: omp.atomic.write %[[XW:.*]] =
+! CHECK: %[[VW:.*]] = fir.load %[[XW]]
+! CHECK: %[[SXW:.*]] = fir.coordinate_of %[[STW:.*]], x
+! CHECK: %[[SIW:.*]] = fir.coordinate_of %[[STW]], $x
+! CHECK: %[[CURW:.*]] = fir.load %[[SIW]]
+! CHECK: %[[CMPW:.*]] = arith.cmpi sge, %{{.*}}, %[[CURW]]
+! CHECK: fir.if %[[CMPW]] {
+! CHECK: fir.store %[[VW]] to %[[SXW]]
+! CHECK: fir.store %{{.*}} to %[[SIW]]
+! CHECK: }
+
+subroutine test_atomic_update(n, x)
+ implicit none
+ integer, intent(in) :: n
+ integer, intent(inout) :: x
+ integer :: i
+ !$omp parallel do lastprivate(conditional: x)
+ do i = 1, n
+ if (mod(i, 2) == 0) then
+ !$omp atomic update
+ x = x + i
+ end if
+ end do
+end subroutine
+! CHECK-LABEL: func.func @_QPtest_atomic_update
+! CHECK: omp.atomic.update %[[XU:.*]] :
+! CHECK: %[[VU:.*]] = fir.load %[[XU]]
+! CHECK: %[[SXU:.*]] = fir.coordinate_of %[[STU:.*]], x
+! CHECK: %[[SIU:.*]] = fir.coordinate_of %[[STU]], $x
+! CHECK: %[[CURU:.*]] = fir.load %[[SIU]]
+! CHECK: %[[CMPU:.*]] = arith.cmpi sge, %{{.*}}, %[[CURU]]
+! CHECK: fir.if %[[CMPU]] {
+! CHECK: fir.store %[[VU]] to %[[SXU]]
+! CHECK: fir.store %{{.*}} to %[[SIU]]
+! CHECK: }
+
+subroutine test_atomic_capture(n, x)
+ implicit none
+ integer, intent(in) :: n
+ integer, intent(inout) :: x
+ integer :: i, v
+ !$omp parallel do lastprivate(conditional: x)
+ do i = 1, n
+ if (mod(i, 2) == 0) then
+ !$omp atomic capture
+ v = x
+ x = i
+ !$omp end atomic
+ end if
+ end do
+end subroutine
+! The commit is injected after the whole omp.atomic.capture op, not inside it.
+! CHECK-LABEL: func.func @_QPtest_atomic_capture
+! CHECK: omp.atomic.capture {
+! CHECK-NOT: arith.cmpi sge
+! CHECK: }
+! CHECK: %[[VC:.*]] = fir.load %[[XC:.*]]
+! CHECK: %[[SXC:.*]] = fir.coordinate_of %[[STC:.*]], x
+! CHECK: %[[SIC:.*]] = fir.coordinate_of %[[STC]], $x
+! CHECK: %[[CURC:.*]] = fir.load %[[SIC]]
+! CHECK: %[[CMPC:.*]] = arith.cmpi sge, %{{.*}}, %[[CURC]]
+! CHECK: fir.if %[[CMPC]] {
+! CHECK: fir.store %[[VC]] to %[[SXC]]
+! CHECK: fir.store %{{.*}} to %[[SIC]]
+! CHECK: }
+
+subroutine test_atomic_capture_update(n, x)
+ implicit none
+ integer, intent(in) :: n
+ integer, intent(inout) :: x
+ integer :: i, v
+ !$omp parallel do lastprivate(conditional: x)
+ do i = 1, n
+ if (mod(i, 2) == 0) then
+ !$omp atomic capture
+ v = x
+ x = x + i
+ !$omp end atomic
+ end if
+ end do
+end subroutine
+! A capture whose nested op is an atomic update is also committed after the
+! whole omp.atomic.capture op.
+! CHECK-LABEL: func.func @_QPtest_atomic_capture_update
+! CHECK: omp.atomic.capture {
+! CHECK: omp.atomic.update
+! CHECK: {atomic_control
+! CHECK-NOT: arith.cmpi sge
+! CHECK: }
+! CHECK: %[[VCU:.*]] = fir.load %[[XCU:.*]]
+! CHECK: %[[SXCU:.*]] = fir.coordinate_of %[[STCU:.*]], x
+! CHECK: %[[SICU:.*]] = fir.coordinate_of %[[STCU]], $x
+! CHECK: %[[CURCU:.*]] = fir.load %[[SICU]]
+! CHECK: %[[CMPCU:.*]] = arith.cmpi sge, %{{.*}}, %[[CURCU]]
+! CHECK: fir.if %[[CMPCU]] {
+! CHECK: fir.store %[[VCU]] to %[[SXCU]]
+! CHECK: fir.store %{{.*}} to %[[SICU]]
+! CHECK: }
+
+subroutine test_atomic_capture_dest(n)
+ implicit none
+ integer, intent(in) :: n
+ integer :: i, x, v
+ x = 0
+ !$omp parallel do lastprivate(conditional: v)
+ do i = 1, n
+ if (mod(i, 2) == 0) then
+ !$omp atomic capture
+ v = x
+ x = x + i
+ !$omp end atomic
+ end if
+ end do
+end subroutine
+! When the conditional-LP item is the capture READ destination (v), the value
+! captured into it must also be committed after the whole omp.atomic.capture op.
+! CHECK-LABEL: func.func @_QPtest_atomic_capture_dest
+! CHECK: omp.atomic.capture {
+! CHECK: omp.atomic.read
+! CHECK: {atomic_control
+! CHECK-NOT: arith.cmpi sge
+! CHECK: }
+! CHECK: %[[VD:.*]] = fir.load %[[XD:.*]]
+! CHECK: %[[SVD:.*]] = fir.coordinate_of %[[STD:.*]], v
+! CHECK: %[[SID:.*]] = fir.coordinate_of %[[STD]], $v
+! CHECK: %[[CURD:.*]] = fir.load %[[SID]]
+! CHECK: %[[CMPD:.*]] = arith.cmpi sge, %{{.*}}, %[[CURD]]
+! CHECK: fir.if %[[CMPD]] {
+! CHECK: fir.store %[[VD]] to %[[SVD]]
+! CHECK: fir.store %{{.*}} to %[[SID]]
+! CHECK: }
diff --git a/flang/test/Lower/OpenMP/lastprivate-conditional-collapse.f90 b/flang/test/Lower/OpenMP/lastprivate-conditional-collapse.f90
new file mode 100644
index 0000000000000..db82884c86151
--- /dev/null
+++ b/flang/test/Lower/OpenMP/lastprivate-conditional-collapse.f90
@@ -0,0 +1,43 @@
+! Test lowering of `lastprivate(conditional:)` on a collapsed loop nest. The
+! "sequentially last" iteration is over the COLLAPSED (flattened) iteration
+! space, so the canonical index stored in the reduction struct must be the
+! flattened index: outer_canonical * inner_extent + inner_canonical.
+
+! RUN: bbc -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+
+subroutine test_conditional_lp_collapse(n, m, x)
+ implicit none
+ integer, intent(in) :: n, m
+ integer, intent(inout) :: x
+ integer :: i, j
+
+ !$omp parallel do collapse(2) lastprivate(conditional: x)
+ do i = 1, n
+ do j = 1, m
+ if (mod(i + j, 2) == 1) x = i * 10 + j
+ end do
+ end do
+ !$omp end parallel do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_conditional_lp_collapse
+! CHECK: omp.wsloop
+! CHECK-SAME: reduction(byref @lp_cond_byref_rec__lp_cond_t
+! CHECK: omp.loop_nest {{.*}} collapse(2)
+! Flattened canonical index: normalise each IV ((iv - lb) / step), then combine
+! outer * inner_extent + inner, and store it into the $x index field.
+! CHECK: %[[OUTER:.*]] = arith.divsi
+! CHECK: %[[INNER:.*]] = arith.divsi
+! CHECK: %[[FLAT:.*]] = arith.muli %{{.*}}, %{{.*}} : i64
+! CHECK: %[[IDX:.*]] = arith.addi %[[FLAT]], %{{.*}} : i64
+! CHECK: fir.if
+! CHECK: fir.coordinate_of %{{.*}}, $x
+! CHECK: fir.store %[[IDX]] to %{{.*}} : !fir.ref<i64>
+! CHECK: }
+
+! Guarded copy-back in an omp.single sibling.
+! CHECK: omp.single {
+! CHECK: arith.cmpi sge, %{{.*}}, %{{.*}} : i64
+! CHECK: omp.terminator
+! CHECK: }
diff --git a/flang/test/Lower/OpenMP/lastprivate-conditional-do-simd.f90 b/flang/test/Lower/OpenMP/lastprivate-conditional-do-simd.f90
new file mode 100644
index 0000000000000..5eb47004cb133
--- /dev/null
+++ b/flang/test/Lower/OpenMP/lastprivate-conditional-do-simd.f90
@@ -0,0 +1,18 @@
+! Test that lastprivate(conditional:) on a composite do simd construct
+! produces a TODO diagnostic.
+
+! RUN: not bbc -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - 2>&1 | FileCheck %s
+! RUN: not %flang_fc1 -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - 2>&1 | FileCheck %s
+
+! CHECK: not yet implemented: lastprivate(conditional:) on do simd composite construct
+
+subroutine do_simd_conditional(a, n)
+ integer :: a(:), n, x, i
+ x = 0
+ !$omp do simd lastprivate(conditional: x)
+ do i = 1, n
+ if (a(i) > 0) x = a(i)
+ end do
+ !$omp end do simd
+ a(1) = x
+end subroutine
diff --git a/flang/test/Lower/OpenMP/lastprivate-conditional-firstprivate.f90 b/flang/test/Lower/OpenMP/lastprivate-conditional-firstprivate.f90
new file mode 100644
index 0000000000000..897b0a75ae211
--- /dev/null
+++ b/flang/test/Lower/OpenMP/lastprivate-conditional-firstprivate.f90
@@ -0,0 +1,91 @@
+! Test lowering of a list item that is both `firstprivate` and
+! `lastprivate(conditional:)` on a worksharing construct.
+!
+! Under the worksharing private-copy lowering, the item gets an ordinary
+! (firstprivate) private copy -- its in-loop working value, initialized from the
+! original -- while a separate reduction struct is the conditional-last
+! accumulator. A guarded commit copies the working value into the accumulator
+! when the current canonical index is highest so far; a guarded copy-back writes
+! the accumulator to the original after the loop.
+
+! RUN: bbc -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+
+subroutine test_fp_conditional_lp(n, x)
+ implicit none
+ integer, intent(in) :: n
+ integer :: x
+ integer :: k
+
+ !$omp parallel do firstprivate(x) lastprivate(conditional: x)
+ do k = 1, n
+ if (mod(k, 2) == 0) x = x + k
+ end do
+ !$omp end parallel do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_fp_conditional_lp
+! x is an ordinary firstprivate copy (working value); the struct is the
+! conditional-last accumulator carried as a by-ref reduction.
+! CHECK: omp.wsloop private(@{{.*}}Ex_firstprivate
+! CHECK-SAME: reduction(byref @lp_cond_byref_rec__lp_cond_t{{.*}} -> %[[SARG:.*]] :
+! -- Loop body: guarded commit into the accumulator. -------------------------
+! CHECK: fir.if
+! CHECK: hlfir.assign
+! CHECK: fir.coordinate_of %[[SARG]], x
+! CHECK: fir.coordinate_of %[[SARG]], $x
+! CHECK: arith.cmpi sge, %{{.*}}, %{{.*}} : i64
+! CHECK: fir.if
+! CHECK: fir.store %{{.*}} : !fir.ref<i32>
+! CHECK: fir.store %{{.*}} : !fir.ref<i64>
+! -- Guarded copy-back after the loop. ---------------------------------------
+! CHECK: omp.single {
+! CHECK: arith.cmpi sge, %{{.*}}, %{{.*}} : i64
+! CHECK: fir.if
+! CHECK: fir.store
+! CHECK: omp.terminator
+
+! =====================================================================
+! Mixed: one item is firstprivate + conditional (a), another is conditional
+! only (b). Both get a private copy; the struct accumulates both.
+! =====================================================================
+subroutine test_fp_cond_mixed(n, a, b)
+ implicit none
+ integer, intent(in) :: n
+ integer :: a, b
+ integer :: k
+
+ !$omp parallel do firstprivate(a) lastprivate(conditional: a, b)
+ do k = 1, n
+ if (mod(k, 2) == 0) a = a + k
+ if (mod(k, 3) == 0) b = k
+ end do
+ !$omp end parallel do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_fp_cond_mixed
+! a is firstprivate; b is an ordinary private copy; both feed the reduction.
+! CHECK: omp.wsloop private(@{{.*}}Ea_firstprivate
+! CHECK-SAME: reduction(byref @lp_cond_byref_rec__lp_cond_t
+
+! =====================================================================
+! Coexistence: a firstprivate item (q) and a distinct conditional item (p) on
+! the same construct. q keeps its firstprivate privatizer; p uses the
+! conditional-lastprivate reduction struct (and its own private copy).
+! =====================================================================
+subroutine test_fp_cond_coexist(n, p, q)
+ implicit none
+ integer, intent(in) :: n
+ integer :: p, q
+ integer :: k
+
+ !$omp parallel do firstprivate(q) lastprivate(conditional: p)
+ do k = 1, n
+ if (mod(k, 2) == 0) p = q + k
+ end do
+ !$omp end parallel do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_fp_cond_coexist
+! CHECK: omp.wsloop private(@{{.*}}Eq_firstprivate
+! CHECK-SAME: reduction(byref @lp_cond_byref_rec__lp_cond_t
diff --git a/flang/test/Lower/OpenMP/lastprivate-conditional-loop.f90 b/flang/test/Lower/OpenMP/lastprivate-conditional-loop.f90
new file mode 100644
index 0000000000000..09446428868bc
--- /dev/null
+++ b/flang/test/Lower/OpenMP/lastprivate-conditional-loop.f90
@@ -0,0 +1,21 @@
+! Test that lastprivate(conditional:) on the generic `loop` construct fails
+! gracefully with a TODO rather than silently miscompiling. Note the `loop`
+! construct does not implement lastprivate at all yet (even plain lastprivate
+! hits the same TODO); this locks in the graceful-failure behavior.
+! The combined `parallel loop` form behaves identically (the loop leaf hits the
+! same TODO).
+
+! RUN: not bbc -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - 2>&1 | FileCheck %s
+! RUN: not %flang_fc1 -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - 2>&1 | FileCheck %s
+
+! CHECK: not yet implemented: Unhandled clause LASTPRIVATE in LOOP construct
+
+subroutine loop_conditional(n, x)
+ integer :: n, x, i
+ x = 0
+ !$omp loop lastprivate(conditional: x)
+ do i = 1, n
+ if (mod(i, 2) == 0) x = i
+ end do
+ !$omp end loop
+end subroutine
diff --git a/flang/test/Lower/OpenMP/lastprivate-conditional-nested-parallel.f90 b/flang/test/Lower/OpenMP/lastprivate-conditional-nested-parallel.f90
new file mode 100644
index 0000000000000..1523f14eb8164
--- /dev/null
+++ b/flang/test/Lower/OpenMP/lastprivate-conditional-nested-parallel.f90
@@ -0,0 +1,46 @@
+! Test lowering of `lastprivate(conditional:)` on a worksharing do that is
+! lexically nested inside TWO parallel regions (nested parallelism, non-orphaned
+! / "inlined"). Because an enclosing omp.parallel exists in the same function,
+! the reduction struct is a per-thread stack alloca placed before the innermost
+! enclosing parallel, so each outer thread gets its own copy. No runtime
+! nested-parallelism guard is emitted here -- that guard is only needed for the
+! orphaned case, which shares a module-scope global (see
+! lastprivate-conditional-wsloop-orphaned.f90).
+
+! RUN: bbc -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+
+subroutine test_conditional_lp_nested_parallel(n, x)
+ implicit none
+ integer, intent(in) :: n
+ integer, intent(inout) :: x
+ integer :: i
+
+ !$omp parallel
+ !$omp parallel
+ !$omp do lastprivate(conditional: x)
+ do i = 1, n
+ if (mod(i, 3) == 0) x = i
+ end do
+ !$omp end do
+ !$omp end parallel
+ !$omp end parallel
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_conditional_lp_nested_parallel
+! No orphaned-case runtime guard for the inlined case: no omp_get_level check
+! and no ERROR STOP before the region.
+! CHECK-NOT: omp_get_level
+! CHECK-NOT: _FortranAStopStatementText
+! Outer parallel; the reduction struct is a per-thread alloca created inside it
+! (before the inner parallel), so each outer thread has its own copy.
+! CHECK: omp.parallel {
+! CHECK: %[[S:.*]] = fir.alloca !fir.type<_lp_cond_t{{.*}}> {pinned}
+! CHECK: omp.parallel {
+! The inner worksharing loop reduces into that per-outer-thread struct.
+! CHECK: omp.wsloop
+! CHECK-SAME: reduction(byref @lp_cond_byref_rec__lp_cond_t
+! CHECK: omp.single {
+! CHECK: arith.cmpi sge, %{{.*}}, %{{.*}} : i64
+! CHECK: omp.terminator
+! CHECK: }
diff --git a/flang/test/Lower/OpenMP/lastprivate-conditional-never-assigned.f90 b/flang/test/Lower/OpenMP/lastprivate-conditional-never-assigned.f90
new file mode 100644
index 0000000000000..df0d9f5e18ff7
--- /dev/null
+++ b/flang/test/Lower/OpenMP/lastprivate-conditional-never-assigned.f90
@@ -0,0 +1,42 @@
+! Test lowering of `lastprivate(conditional:)` where one list item (z) is never
+! assigned in the region. The lowering must statically guarantee such a
+! variable is not copied back: its index field is initialized to -1 and no
+! iteration-index store is emitted for it, so the guarded copy-back can never
+! fire. The assigned variable (x) behaves normally.
+
+! RUN: bbc -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+
+subroutine test_conditional_lp_never_assigned(n, x, z)
+ implicit none
+ integer, intent(in) :: n
+ integer :: x, z
+ integer :: k
+
+ !$omp parallel do lastprivate(conditional: x, z)
+ do k = 1, n
+ if (mod(k, 2) == 0) x = k ! x is assigned; z is never assigned
+ end do
+ !$omp end parallel do
+end subroutine
+
+! CHECK-LABEL: omp.declare_reduction @lp_cond_byref_rec__lp_cond_t
+! CHECK-SAME: {x:i32,z:i32,$x:i64,$z:i64}
+! Both index fields start at the -1 sentinel.
+! CHECK: init {
+! CHECK-DAG: arith.constant -1 : i64
+! CHECK: }
+
+! CHECK-LABEL: func.func @_QPtest_conditional_lp_never_assigned
+! CHECK: omp.loop_nest
+! The assigned variable x records its canonical iteration index; the
+! never-assigned variable z records no index store at all.
+! CHECK: fir.coordinate_of %{{.*}}, $x
+! CHECK-NOT: fir.coordinate_of %{{.*}}, $z
+! CHECK: omp.single {
+! Both variables still get a guarded copy-back; z's guard can never fire
+! because its index remains -1.
+! CHECK: arith.cmpi sge, %{{.*}}, %{{.*}} : i64
+! CHECK: arith.cmpi sge, %{{.*}}, %{{.*}} : i64
+! CHECK: omp.terminator
+! CHECK: }
diff --git a/flang/test/Lower/OpenMP/lastprivate-conditional-parallel-do-simd.f90 b/flang/test/Lower/OpenMP/lastprivate-conditional-parallel-do-simd.f90
new file mode 100644
index 0000000000000..d43c1b02ab1dc
--- /dev/null
+++ b/flang/test/Lower/OpenMP/lastprivate-conditional-parallel-do-simd.f90
@@ -0,0 +1,19 @@
+! Test that lastprivate(conditional:) on the composite `parallel do simd`
+! construct fails gracefully with a TODO rather than silently miscompiling.
+! The clause reaches the do-simd composite leaf, which does not yet implement
+! conditional lastprivate.
+
+! RUN: not bbc -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - 2>&1 | FileCheck %s
+! RUN: not %flang_fc1 -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - 2>&1 | FileCheck %s
+
+! CHECK: not yet implemented: lastprivate(conditional:) on do simd composite construct
+
+subroutine parallel_do_simd_conditional(n, x)
+ integer :: n, x, i
+ x = 0
+ !$omp parallel do simd lastprivate(conditional: x)
+ do i = 1, n
+ if (mod(i, 2) == 0) x = i
+ end do
+ !$omp end parallel do simd
+end subroutine
diff --git a/flang/test/Lower/OpenMP/lastprivate-conditional-parallel-loop.f90 b/flang/test/Lower/OpenMP/lastprivate-conditional-parallel-loop.f90
new file mode 100644
index 0000000000000..ffa1770ff7591
--- /dev/null
+++ b/flang/test/Lower/OpenMP/lastprivate-conditional-parallel-loop.f90
@@ -0,0 +1,17 @@
+! Test that lastprivate(conditional:) on the composite `parallel loop` construct
+! fails gracefully with a TODO rather than silently miscompiling.
+
+! RUN: not bbc -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - 2>&1 | FileCheck %s
+! RUN: not %flang_fc1 -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - 2>&1 | FileCheck %s
+
+! CHECK: not yet implemented: Unhandled clause LASTPRIVATE in LOOP construct
+
+subroutine parallel_loop_conditional(n, x)
+ integer :: n, x, i
+ x = 0
+ !$omp parallel loop lastprivate(conditional: x)
+ do i = 1, n
+ if (mod(i, 2) == 0) x = i
+ end do
+ !$omp end parallel loop
+end subroutine
diff --git a/flang/test/Lower/OpenMP/lastprivate-conditional-sections-firstprivate.f90 b/flang/test/Lower/OpenMP/lastprivate-conditional-sections-firstprivate.f90
new file mode 100644
index 0000000000000..d310ca3dc11b1
--- /dev/null
+++ b/flang/test/Lower/OpenMP/lastprivate-conditional-sections-firstprivate.f90
@@ -0,0 +1,43 @@
+! Test lowering of a list item that is both `firstprivate` and
+! `lastprivate(conditional:)` on a SECTIONS construct (the sections seed-store
+! path, analogous to the worksharing-do path in
+! lastprivate-conditional-firstprivate.f90).
+
+! RUN: bbc -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+
+subroutine test_fp_cond_sections(x)
+ implicit none
+ integer :: x
+
+ !$omp parallel sections firstprivate(x) lastprivate(conditional: x)
+ !$omp section
+ x = x + 5
+ !$omp end parallel sections
+end subroutine
+
+! The struct is the sole binding for x: no ordinary firstprivate privatizer.
+! CHECK-NOT: omp.private {{.*}}firstprivate
+
+! CHECK-LABEL: omp.declare_reduction @lp_cond_byref_rec__lp_cond_t
+! Init region seeds the value field from the seed struct (ompOrig) per thread.
+! CHECK: init {
+! CHECK: ^bb0(%[[ORIG:.*]]: {{.*}}, %[[PRIV:.*]]: {{.*}}):
+! CHECK: fir.coordinate_of %[[PRIV]], x
+! CHECK: fir.coordinate_of %[[ORIG]], x
+! CHECK: }
+
+! CHECK-LABEL: func.func @_QPtest_fp_cond_sections
+! Seed store before the region: struct value field = original x.
+! CHECK: %[[XD:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QF{{.*}}Ex"}
+! CHECK: fir.coordinate_of %[[S:.*]], $x
+! CHECK: %[[MX:.*]] = fir.coordinate_of %[[S]], x
+! CHECK: %[[SEED:.*]] = fir.load %[[XD]]#0
+! CHECK: fir.store %[[SEED]] to %[[MX]]
+! The conditional reduction is carried on omp.sections; guarded copy-back after.
+! CHECK: omp.sections
+! CHECK-SAME: reduction(byref @lp_cond_byref_rec__lp_cond_t
+! CHECK: omp.single {
+! CHECK: arith.cmpi sge, %{{.*}}, %{{.*}} : i64
+! CHECK: omp.terminator
+! CHECK: }
diff --git a/flang/test/Lower/OpenMP/lastprivate-conditional-sections-nowait.f90 b/flang/test/Lower/OpenMP/lastprivate-conditional-sections-nowait.f90
new file mode 100644
index 0000000000000..d0762a4812836
--- /dev/null
+++ b/flang/test/Lower/OpenMP/lastprivate-conditional-sections-nowait.f90
@@ -0,0 +1,45 @@
+! Test lowering of `lastprivate(conditional:)` on an omp sections construct with
+! the nowait clause. With nowait there is no closing barrier, so the lowering
+! emits an explicit barrier before the copy-back. The copy-back runs in an
+! omp.single sibling of the sections; the parallel is not marked omp.combined.
+
+! RUN: bbc -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+
+subroutine test_conditional_lp_sections_nowait(x)
+ implicit none
+ integer, intent(inout) :: x
+
+ !$omp parallel
+ !$omp sections lastprivate(conditional: x)
+ !$omp section
+ x = 10
+ !$omp section
+ x = 20
+ !$omp end sections nowait
+ !$omp end parallel
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_conditional_lp_sections_nowait
+! CHECK: %[[STRUCT:.*]] = fir.alloca !fir.type<_lp_cond_t.{{l[0-9]+\.[0-9]+}}{x:i32,$x:i64}>
+
+! CHECK: omp.parallel {
+! CHECK: omp.sections nowait
+! CHECK-SAME: reduction(byref @lp_cond_byref_rec__lp_cond_t
+! CHECK-SAME: %[[STRUCT]]
+
+! CHECK: omp.barrier
+
+! CHECK: omp.single {
+! CHECK: fir.coordinate_of %[[STRUCT]], x
+! CHECK: fir.load
+! CHECK: fir.coordinate_of %[[STRUCT]], $x
+! CHECK: fir.load
+! CHECK: arith.cmpi sge
+! CHECK: fir.if
+! CHECK: fir.store
+! CHECK: }
+! CHECK: omp.terminator
+! CHECK: }
+! CHECK: omp.terminator
+! CHECK: }
diff --git a/flang/test/Lower/OpenMP/lastprivate-conditional-sections-orphaned.f90 b/flang/test/Lower/OpenMP/lastprivate-conditional-sections-orphaned.f90
new file mode 100644
index 0000000000000..7e63a6775493a
--- /dev/null
+++ b/flang/test/Lower/OpenMP/lastprivate-conditional-sections-orphaned.f90
@@ -0,0 +1,81 @@
+! Test lowering of `lastprivate(conditional:)` on an ORPHANED omp sections
+! construct (sections inside a subroutine called from a parallel region).
+!
+! Because the subroutine has no enclosing omp.parallel, a stack alloca would
+! give each thread a private copy and the cross-thread reduction would never
+! merge. The lowering must therefore place the reduction struct in a
+! module-scope fir.global internal rather than a fir.alloca.
+
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+
+subroutine test_orphaned_sections(n)
+ implicit none
+ integer, intent(inout) :: n
+
+ !$omp sections lastprivate(conditional: n)
+ !$omp section
+ n = 10
+ !$omp section
+ n = 20
+ !$omp end sections
+end subroutine
+
+! -- declare_reduction for the struct type ------------------------------------
+! CHECK-LABEL: omp.declare_reduction @lp_cond_byref_rec__lp_cond_t
+! CHECK-SAME: : !fir.ref<!fir.type<_lp_cond_t.{{l[0-9]+\.[0-9]+}}{n:i32,$n:i64}>>
+
+! -- Function body: address_of global (no fir.alloca for the struct) ----------
+! CHECK-LABEL: func.func @_QPtest_orphaned_sections
+
+! -- Runtime guard: abort if called from nested parallelism ------------------
+! Guard is emitted BEFORE init to avoid racing on the global.
+! CHECK: %[[LEVEL:.*]] = fir.call @omp_get_level_() {{.*}} : () -> i32
+! CHECK: %[[ONE:.*]] = arith.constant 1 : i32
+! CHECK: %[[NESTED:.*]] = arith.cmpi sgt, %[[LEVEL]], %[[ONE]] : i32
+! CHECK: fir.if %[[NESTED]] {
+! CHECK: fir.call @_FortranAStopStatementText
+! CHECK: }
+
+! CHECK-NOT: fir.alloca !fir.type<_lp_cond_t
+! CHECK: %[[GADDR:.*]] = fir.address_of(@_lp_cond_global.{{l[0-9]+\.[0-9]+}}) : !fir.ref<!fir.type<_lp_cond_t.{{l[0-9]+\.[0-9]+}}{n:i32,$n:i64}>>
+
+! -- Init sentinels written inside omp.single --------------------------------
+! CHECK: omp.single {
+! CHECK: %[[NCOORD:.*]] = fir.coordinate_of %[[GADDR]], n
+! CHECK: %[[C0:.*]] = arith.constant 0 : i32
+! CHECK: fir.store %[[C0]] to %[[NCOORD]]
+! CHECK: %[[KNCOORD:.*]] = fir.coordinate_of %[[GADDR]], $n
+! CHECK: %[[CM1:.*]] = arith.constant -1 : i64
+! CHECK: fir.store %[[CM1]] to %[[KNCOORD]]
+! CHECK: omp.terminator
+! CHECK: }
+
+! -- Sections carries the global address as a by-ref reduction ---------------
+! CHECK: omp.sections
+! CHECK-SAME: reduction(byref @lp_cond_byref_rec__lp_cond_t
+! CHECK-SAME: %[[GADDR]]
+
+! -- Section 0: index constant hoisted to entry, stored after assignment ------
+! CHECK: omp.section {
+! CHECK: %[[IDX0:.*]] = arith.constant 0 : i64
+! CHECK: hlfir.assign
+! CHECK: fir.store %[[IDX0]]
+
+! -- Section 1: index constant hoisted to entry, stored after assignment ------
+! CHECK: omp.section {
+! CHECK: %[[IDX1:.*]] = arith.constant 1 : i64
+! CHECK: hlfir.assign
+! CHECK: fir.store %[[IDX1]]
+
+! -- Copy-back: load winning value from global and store to dummy arg ---------
+! CHECK: fir.coordinate_of %[[GADDR]], n
+! CHECK: fir.load
+! CHECK: fir.coordinate_of %[[GADDR]], $n
+! CHECK: fir.load
+! CHECK: arith.cmpi sge
+! CHECK: fir.if
+! CHECK: fir.store
+! CHECK: }
+
+! -- Module-level global declared at end of module (not a stack alloca) -------
+! CHECK: fir.global internal @_lp_cond_global.{{l[0-9]+\.[0-9]+}} : !fir.type<_lp_cond_t.{{l[0-9]+\.[0-9]+}}{n:i32,$n:i64}>
diff --git a/flang/test/Lower/OpenMP/lastprivate-conditional-sections.f90 b/flang/test/Lower/OpenMP/lastprivate-conditional-sections.f90
new file mode 100644
index 0000000000000..661a817d4d924
--- /dev/null
+++ b/flang/test/Lower/OpenMP/lastprivate-conditional-sections.f90
@@ -0,0 +1,97 @@
+! Test lowering of `lastprivate(conditional:)` on an omp sections construct
+! with multiple variables. The lowering must:
+! 1. Build a packed struct type {val, val, ..., idx, idx, ...}
+! 2. Create an omp.declare_reduction with identity 0 / -1
+! 3. Inject the struct as a by-ref reduction variable on the sections
+! 4. Rewrite assignments to use struct value fields + store constant section
+! index (0, 1, ...) into the index fields
+! 5. Copy back the winning values after the sections
+
+! RUN: bbc -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+
+subroutine test_conditional_lp_sections(x, y)
+ implicit none
+ integer, intent(inout) :: x, y
+
+ !$omp parallel sections lastprivate(conditional: x, y)
+ !$omp section
+ x = 10
+ y = 20
+
+ !$omp section
+ x = 30
+ y = 40
+ !$omp end parallel sections
+end subroutine
+
+! -- declare_reduction with struct type containing value/index pairs ----------
+! CHECK-LABEL: omp.declare_reduction @lp_cond_byref_rec__lp_cond_t
+! CHECK-SAME: : !fir.ref<!fir.type<_lp_cond_t.{{l[0-9]+\.[0-9]+}}{x:i32,y:i32,$x:i64,$y:i64}>>
+
+! -- Init region: value fields = 0, index fields = -1 ------------------------
+! CHECK: init {
+! CHECK-DAG: arith.constant 0 : i32
+! CHECK-DAG: arith.constant -1 : i64
+! CHECK: }
+
+! -- Combiner: sgt on i64 index fields, two pairs ----------------------------
+! CHECK: combiner {
+! CHECK: arith.cmpi sgt, %{{.*}}, %{{.*}} : i64
+! CHECK: fir.if
+! CHECK: arith.cmpi sgt, %{{.*}}, %{{.*}} : i64
+! CHECK: fir.if
+! CHECK: omp.yield
+
+! -- Struct alloca + init before parallel -------------------------------------
+! CHECK-LABEL: func.func @_QPtest_conditional_lp_sections
+! CHECK: %[[STRUCT:.*]] = fir.alloca !fir.type<_lp_cond_t.{{l[0-9]+\.[0-9]+}}{x:i32,y:i32,$x:i64,$y:i64}>
+! CHECK-DAG: arith.constant 0 : i32
+! CHECK-DAG: arith.constant -1 : i64
+
+! -- Sections carries the struct as a by-ref reduction ------------------------
+! CHECK: omp.parallel {
+! CHECK: omp.sections
+! CHECK-SAME: reduction(byref @lp_cond_byref_rec__lp_cond_t
+! CHECK-SAME: %[[STRUCT]]
+
+! -- Section 0: index constant hoisted to entry, stored after each assign -----
+! CHECK: omp.section {
+! CHECK: %[[IDX0:.*]] = arith.constant 0 : i64
+! CHECK: hlfir.assign
+! CHECK: fir.store %[[IDX0]]
+! CHECK: hlfir.assign
+! CHECK: fir.store %[[IDX0]]
+
+! -- Section 1: index constant hoisted to entry, stored after each assign -----
+! CHECK: omp.section {
+! CHECK: %[[IDX1:.*]] = arith.constant 1 : i64
+! CHECK: hlfir.assign
+! CHECK: fir.store %[[IDX1]]
+! CHECK: hlfir.assign
+! CHECK: fir.store %[[IDX1]]
+
+! -- Copy-back in an omp.single sibling of the sections inside the parallel. The
+! -- extra immediately-nested construct means the parallel is not omp.combined.
+! -- Guarded: only store if index >= 0. --------------------------------------
+! CHECK: omp.single {
+! CHECK: fir.coordinate_of %[[STRUCT]], {{[xy]}}
+! CHECK: fir.load
+! CHECK: fir.coordinate_of %[[STRUCT]], ${{[xy]}}
+! CHECK: fir.load
+! CHECK: arith.cmpi sge
+! CHECK: fir.if
+! CHECK: fir.store
+! CHECK: }
+! CHECK: fir.coordinate_of %[[STRUCT]], {{[xy]}}
+! CHECK: fir.load
+! CHECK: fir.coordinate_of %[[STRUCT]], ${{[xy]}}
+! CHECK: fir.load
+! CHECK: arith.cmpi sge
+! CHECK: fir.if
+! CHECK: fir.store
+! CHECK: }
+! CHECK: omp.terminator
+! CHECK: }
+! CHECK: omp.terminator
+! CHECK: }
diff --git a/flang/test/Lower/OpenMP/lastprivate-conditional-simd.f90 b/flang/test/Lower/OpenMP/lastprivate-conditional-simd.f90
new file mode 100644
index 0000000000000..0cf9e6b999fa3
--- /dev/null
+++ b/flang/test/Lower/OpenMP/lastprivate-conditional-simd.f90
@@ -0,0 +1,18 @@
+! Test that lastprivate(conditional:) on a standalone simd construct
+! produces a TODO diagnostic.
+
+! RUN: not bbc -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - 2>&1 | FileCheck %s
+! RUN: not %flang_fc1 -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - 2>&1 | FileCheck %s
+
+! CHECK: not yet implemented: lastprivate(conditional:) on simd construct
+
+subroutine simd_conditional(a, n)
+ integer :: a(:), n, x, i
+ x = 0
+ !$omp simd lastprivate(conditional: x)
+ do i = 1, n
+ if (a(i) > 0) x = a(i)
+ end do
+ !$omp end simd
+ a(1) = x
+end subroutine
diff --git a/flang/test/Lower/OpenMP/lastprivate-conditional-standalone-do.f90 b/flang/test/Lower/OpenMP/lastprivate-conditional-standalone-do.f90
new file mode 100644
index 0000000000000..ae4d11610b8ac
--- /dev/null
+++ b/flang/test/Lower/OpenMP/lastprivate-conditional-standalone-do.f90
@@ -0,0 +1,36 @@
+! Test lowering of `lastprivate(conditional:)` on a STANDALONE `omp do` nested
+! inside a separate `omp parallel` (as opposed to the combined `parallel do`).
+
+! RUN: bbc -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+
+subroutine test_nested_if(n, a, x, flag)
+ implicit none
+ integer, intent(in) :: n, flag
+ integer, intent(in) :: a(n)
+ integer, intent(inout) :: x
+ integer :: k
+
+ !$omp parallel
+ !$omp do lastprivate(conditional: x)
+ do k = 1, n
+ if (a(k) < 150) then
+ x = k + 1
+ end if
+ end do
+ !$omp end do
+ !$omp end parallel
+end subroutine
+
+! -- The struct is stack-allocated (fir.alloca), not a global -----------------
+! CHECK-LABEL: func.func @_QPtest_nested_if
+! CHECK: fir.alloca !fir.type<_lp_cond_t.{{l[0-9]+\.[0-9]+}}{x:i32,$x:i64}>
+! CHECK-NOT: fir.address_of(@_lp_cond_global
+
+! -- No nesting guard emitted (this is not orphaned) -------------------------
+! CHECK-NOT: fir.call @omp_get_level_
+
+! -- omp.parallel with the struct as reduction --------------------------------
+! CHECK: omp.parallel
+! CHECK: omp.wsloop
+! CHECK-SAME: reduction(byref @lp_cond_byref_rec__lp_cond_t
diff --git a/flang/test/Lower/OpenMP/lastprivate-conditional-standalone-sections.f90 b/flang/test/Lower/OpenMP/lastprivate-conditional-standalone-sections.f90
new file mode 100644
index 0000000000000..75b5888aef5a6
--- /dev/null
+++ b/flang/test/Lower/OpenMP/lastprivate-conditional-standalone-sections.f90
@@ -0,0 +1,48 @@
+! Test lowering of `lastprivate(conditional:)` on a STANDALONE `omp sections`
+! nested inside a separate `omp parallel` (as opposed to the combined
+! `parallel sections`), where the assignment inside each section is guarded by
+! an `if`. The canonical section-index store must be emitted INSIDE the fir.if,
+! so a section that does not take its branch records no index (its slot keeps
+! the -1 sentinel) and is not copied back. (All other sections tests use
+! unconditional assignments, where the index store is not guarded.)
+
+! RUN: bbc -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+
+subroutine test_conditional_lp_sections_if(sel, x)
+ implicit none
+ integer, intent(in) :: sel
+ integer, intent(inout) :: x
+
+ !$omp parallel
+ !$omp sections lastprivate(conditional: x)
+ !$omp section
+ if (sel > 0) x = 11
+ !$omp section
+ if (sel < 0) x = 22
+ !$omp end sections
+ !$omp end parallel
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_conditional_lp_sections_if
+! CHECK: omp.parallel {
+! CHECK: omp.sections
+! CHECK-SAME: reduction(byref @lp_cond_byref_rec__lp_cond_t
+! Section 1: the value store and the $x index store are both inside the fir.if,
+! so the section only records its index when its branch is taken.
+! CHECK: fir.if
+! CHECK: hlfir.assign
+! CHECK: fir.coordinate_of %{{.*}}, $x
+! CHECK: fir.store %{{.*}} to %{{.*}} : !fir.ref<i64>
+! CHECK: }
+! Section 2 similarly guards its index store.
+! CHECK: fir.if
+! CHECK: hlfir.assign
+! CHECK: fir.coordinate_of %{{.*}}, $x
+! CHECK: fir.store %{{.*}} to %{{.*}} : !fir.ref<i64>
+! CHECK: }
+! Guarded copy-back in an omp.single sibling.
+! CHECK: omp.single {
+! CHECK: arith.cmpi sge, %{{.*}}, %{{.*}} : i64
+! CHECK: omp.terminator
+! CHECK: }
diff --git a/flang/test/Lower/OpenMP/lastprivate-conditional-step.f90 b/flang/test/Lower/OpenMP/lastprivate-conditional-step.f90
new file mode 100644
index 0000000000000..7808b268a34d5
--- /dev/null
+++ b/flang/test/Lower/OpenMP/lastprivate-conditional-step.f90
@@ -0,0 +1,59 @@
+! Test lowering of `lastprivate(conditional:)` on loops with a non-unit and a
+! negative step. The reduction struct records the CANONICAL (normalised)
+! iteration index -- (iv - lb) / step -- not the loop-variable value, so that
+! the combiner's "sequentially last" selection is correct regardless of step.
+
+! RUN: bbc -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+
+! Non-unit positive step.
+subroutine test_conditional_lp_step2(n, x)
+ implicit none
+ integer, intent(in) :: n
+ integer, intent(inout) :: x
+ integer :: i
+
+ !$omp parallel do lastprivate(conditional: x)
+ do i = 1, n, 2
+ if (mod(i, 3) == 0) x = i * 10
+ end do
+ !$omp end parallel do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_conditional_lp_step2
+! CHECK: omp.loop_nest
+! CHECK-SAME: step (%c2{{[^)]*}})
+! Canonical index = (iv - lb) / step, computed in i64 (operands widened first),
+! stored into the $x index field.
+! CHECK: %[[STEP2:.*]] = fir.convert %c2{{[^ ]*}} : (i32) -> i64
+! CHECK: arith.subi %{{.*}}, %{{.*}} : i64
+! CHECK: %[[IDX2:.*]] = arith.divsi %{{.*}}, %[[STEP2]] : i64
+! CHECK: fir.if
+! CHECK: fir.coordinate_of %{{.*}}, $x
+! CHECK: fir.store %{{.*}} to %{{.*}} : !fir.ref<i64>
+
+! Negative step.
+subroutine test_conditional_lp_negstep(n, x)
+ implicit none
+ integer, intent(in) :: n
+ integer, intent(inout) :: x
+ integer :: i
+
+ !$omp parallel do lastprivate(conditional: x)
+ do i = n, 1, -1
+ if (mod(i, 3) == 0) x = i * 10
+ end do
+ !$omp end parallel do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_conditional_lp_negstep
+! CHECK: omp.loop_nest
+! CHECK-SAME: step (%c-1{{[^)]*}})
+! Canonical index normalises by the negative step, so the last EXECUTED
+! iteration gets the largest index. Computed in i64 (operands widened first).
+! CHECK: %[[STEPN:.*]] = fir.convert %c-1{{[^ ]*}} : (i32) -> i64
+! CHECK: arith.subi %{{.*}}, %{{.*}} : i64
+! CHECK: arith.divsi %{{.*}}, %[[STEPN]] : i64
+! CHECK: fir.if
+! CHECK: fir.coordinate_of %{{.*}}, $x
+! CHECK: fir.store %{{.*}} to %{{.*}} : !fir.ref<i64>
diff --git a/flang/test/Lower/OpenMP/lastprivate-conditional-teams.f90 b/flang/test/Lower/OpenMP/lastprivate-conditional-teams.f90
new file mode 100644
index 0000000000000..10d9a78974999
--- /dev/null
+++ b/flang/test/Lower/OpenMP/lastprivate-conditional-teams.f90
@@ -0,0 +1,79 @@
+! Test lowering of `lastprivate(conditional:)` on a worksharing loop nested in a
+! host `teams` region (teams is valid on the host without `target`). The
+! conditional-lastprivate reduction struct is a per-team (pinned) stack
+! allocation inside the teams region -- placed before the enclosing
+! omp.parallel -- so multiple teams each get their own copy. The guarded
+! copy-back runs in an omp.single sibling of the wsloop.
+
+! RUN: bbc -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+
+subroutine test_teams_conditional_lp(n, x)
+ implicit none
+ integer, intent(in) :: n
+ integer :: x
+ integer :: i
+
+ !$omp teams num_teams(4)
+ !$omp parallel do lastprivate(conditional: x)
+ do i = 1, n
+ if (mod(i, 3) == 0) x = i
+ end do
+ !$omp end parallel do
+ !$omp end teams
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_teams_conditional_lp
+! CHECK: omp.teams
+! The reduction struct is a per-team (pinned) stack allocation inside the teams
+! region, initialized before the enclosing parallel.
+! CHECK: %[[STRUCT:.*]] = fir.alloca {{.*}}_lp_cond_t{{.*}} {pinned}
+! CHECK: fir.coordinate_of %[[STRUCT]], x
+! CHECK: fir.coordinate_of %[[STRUCT]], $x
+! CHECK: fir.store %{{.*}} to %{{.*}} : !fir.ref<i64>
+! CHECK: omp.parallel {
+! CHECK: omp.wsloop
+! CHECK-SAME: reduction(byref @lp_cond_byref_rec__lp_cond_t
+! Guarded copy-back in an omp.single sibling of the wsloop.
+! CHECK: omp.single {
+! CHECK: fir.coordinate_of %[[STRUCT]], x
+! CHECK: fir.coordinate_of %[[STRUCT]], $x
+! CHECK: arith.cmpi sge, %{{.*}}, %{{.*}} : i64
+! CHECK: fir.if
+! CHECK: omp.terminator
+! CHECK: }
+! CHECK: omp.terminator
+! CHECK: }
+! CHECK: omp.terminator
+! CHECK: }
+
+! Host teams enclosing parallel sections with two conditional list items.
+subroutine test_teams_sections_conditional_lp(x, y)
+ implicit none
+ integer :: x, y
+
+ !$omp teams num_teams(4)
+ !$omp parallel sections lastprivate(conditional: x, y)
+ !$omp section
+ x = 11
+ !$omp section
+ y = 22
+ !$omp end parallel sections
+ !$omp end teams
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_teams_sections_conditional_lp
+! CHECK: omp.teams
+! CHECK: omp.parallel {
+! CHECK: omp.sections
+! CHECK-SAME: reduction(byref @lp_cond_byref_rec__lp_cond_t
+! CHECK: omp.single {
+! Two guarded copy-backs, one per conditional list item.
+! CHECK: arith.cmpi sge, %{{.*}}, %{{.*}} : i64
+! CHECK: arith.cmpi sge, %{{.*}}, %{{.*}} : i64
+! CHECK: omp.terminator
+! CHECK: }
+! CHECK: omp.terminator
+! CHECK: }
+! CHECK: omp.terminator
+! CHECK: }
diff --git a/flang/test/Lower/OpenMP/lastprivate-conditional-types.f90 b/flang/test/Lower/OpenMP/lastprivate-conditional-types.f90
new file mode 100644
index 0000000000000..dae518ae97873
--- /dev/null
+++ b/flang/test/Lower/OpenMP/lastprivate-conditional-types.f90
@@ -0,0 +1,46 @@
+! Test lowering of `lastprivate(conditional:)` for the supported scalar
+! intrinsic type categories other than default integer: real, logical, complex
+! (and a non-default integer kind). The packed reduction struct must carry a
+! value field of the variable's own type plus an i64 index field per variable.
+
+! RUN: bbc -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+
+subroutine test_conditional_lp_types(n, r, lg, k8, cx)
+ implicit none
+ integer, intent(in) :: n
+ real, intent(inout) :: r
+ logical, intent(inout) :: lg
+ integer(8), intent(inout) :: k8
+ complex, intent(inout) :: cx
+ integer :: i
+
+ !$omp parallel do lastprivate(conditional: r, lg, k8, cx)
+ do i = 1, n
+ if (mod(i, 3) == 0) r = real(i)
+ if (mod(i, 4) == 0) lg = (mod(i, 2) == 0)
+ if (mod(i, 5) == 0) k8 = int(i, 8)
+ if (mod(i, 6) == 0) cx = cmplx(real(i), 1.0)
+ end do
+ !$omp end parallel do
+end subroutine
+
+! Value fields keep each variable's own type; index fields are i64.
+! CHECK-LABEL: omp.declare_reduction @lp_cond_byref_rec__lp_cond_t
+! CHECK-SAME: {r:f32,lg:!fir.logical<4>,k8:i64,cx:complex<f32>,$r:i64,$lg:i64,$k8:i64,$cx:i64}
+! CHECK: init {
+! CHECK-DAG: arith.constant -1 : i64
+! CHECK: }
+! CHECK: combiner {
+! Four (value,index) pairs -> four sgt comparisons on i64 index fields.
+! CHECK-COUNT-4: arith.cmpi sgt, %{{.*}}, %{{.*}} : i64
+! CHECK: }
+
+! CHECK-LABEL: func.func @_QPtest_conditional_lp_types
+! CHECK: omp.wsloop
+! CHECK-SAME: reduction(byref @lp_cond_byref_rec__lp_cond_t
+! Guarded copy-back for each variable in an omp.single sibling.
+! CHECK: omp.single {
+! CHECK-COUNT-4: arith.cmpi sge, %{{.*}}, %{{.*}} : i64
+! CHECK: omp.terminator
+! CHECK: }
diff --git a/flang/test/Lower/OpenMP/lastprivate-conditional-wsloop-nowait.f90 b/flang/test/Lower/OpenMP/lastprivate-conditional-wsloop-nowait.f90
new file mode 100644
index 0000000000000..382edb008f464
--- /dev/null
+++ b/flang/test/Lower/OpenMP/lastprivate-conditional-wsloop-nowait.f90
@@ -0,0 +1,47 @@
+! Test lowering of `lastprivate(conditional:)` on a worksharing do loop with the
+! nowait clause. With nowait there is no closing barrier, so the lowering emits
+! an explicit barrier before the copy-back. The copy-back runs in an omp.single
+! sibling of the wsloop; the extra nested construct means the parallel is not
+! marked omp.combined.
+
+! RUN: bbc -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+
+subroutine test_conditional_lp_nowait(n, x)
+ implicit none
+ integer, intent(in) :: n
+ integer, intent(inout) :: x
+ integer :: k
+
+ !$omp parallel
+ !$omp do lastprivate(conditional: x)
+ do k = 1, n
+ x = k
+ end do
+ !$omp end do nowait
+ !$omp end parallel
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_conditional_lp_nowait
+! CHECK: %[[STRUCT:.*]] = fir.alloca !fir.type<_lp_cond_t.{{l[0-9]+\.[0-9]+}}{x:i32,$x:i64}>
+
+! CHECK: omp.parallel {
+! CHECK: omp.wsloop nowait
+! CHECK-SAME: reduction(byref @lp_cond_byref_rec__lp_cond_t
+! CHECK-SAME: %[[STRUCT]]
+
+! CHECK: omp.barrier
+
+! CHECK: omp.single {
+! CHECK: fir.coordinate_of %[[STRUCT]], x
+! CHECK: fir.load
+! CHECK: fir.coordinate_of %[[STRUCT]], $x
+! CHECK: fir.load
+! CHECK: arith.cmpi sge
+! CHECK: fir.if
+! CHECK: fir.store
+! CHECK: }
+! CHECK: omp.terminator
+! CHECK: }
+! CHECK: omp.terminator
+! CHECK: }
diff --git a/flang/test/Lower/OpenMP/lastprivate-conditional-wsloop-orphaned.f90 b/flang/test/Lower/OpenMP/lastprivate-conditional-wsloop-orphaned.f90
new file mode 100644
index 0000000000000..4a35375b5e7fa
--- /dev/null
+++ b/flang/test/Lower/OpenMP/lastprivate-conditional-wsloop-orphaned.f90
@@ -0,0 +1,83 @@
+! Test lowering of `lastprivate(conditional:)` on an ORPHANED omp do loop
+! (wsloop inside a subroutine called from a parallel region).
+!
+! Because the subroutine has no enclosing omp.parallel, a stack alloca would
+! give each thread a private copy and the cross-thread reduction would never
+! merge. The lowering must therefore place the reduction struct in a
+! module-scope fir.global internal rather than a fir.alloca.
+
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+
+subroutine test_orphaned_wsloop(n, x)
+ implicit none
+ integer, intent(in) :: n
+ integer, intent(inout) :: x
+ integer :: k
+
+ !$omp do lastprivate(conditional: x)
+ do k = 1, n
+ x = k
+ end do
+ !$omp end do
+end subroutine
+
+! -- declare_reduction for the struct type ------------------------------------
+! CHECK-LABEL: omp.declare_reduction @lp_cond_byref_rec__lp_cond_t
+! CHECK-SAME: : !fir.ref<!fir.type<_lp_cond_t.{{l[0-9]+\.[0-9]+}}{x:i32,$x:i64}>>
+
+! -- Function body: address_of global (no fir.alloca for the struct) ----------
+! CHECK-LABEL: func.func @_QPtest_orphaned_wsloop
+
+! -- Runtime guard: abort if called from nested parallelism ------------------
+! Guard is emitted BEFORE init to avoid racing on the global.
+! CHECK: %[[LEVEL:.*]] = fir.call @omp_get_level_() {{.*}} : () -> i32
+! CHECK: %[[ONE:.*]] = arith.constant 1 : i32
+! CHECK: %[[NESTED:.*]] = arith.cmpi sgt, %[[LEVEL]], %[[ONE]] : i32
+! CHECK: fir.if %[[NESTED]] {
+! CHECK: fir.call @_FortranAStopStatementText
+! CHECK: }
+
+! CHECK-NOT: fir.alloca !fir.type<_lp_cond_t
+! CHECK: %[[GADDR:.*]] = fir.address_of(@_lp_cond_global.{{l[0-9]+\.[0-9]+}}) : !fir.ref<!fir.type<_lp_cond_t.{{l[0-9]+\.[0-9]+}}{x:i32,$x:i64}>>
+
+! -- Init sentinels written inside omp.single --------------------------------
+! CHECK: omp.single {
+! CHECK: %[[XCOORD:.*]] = fir.coordinate_of %[[GADDR]], x
+! CHECK: %[[C0:.*]] = arith.constant 0 : i32
+! CHECK: fir.store %[[C0]] to %[[XCOORD]]
+! CHECK: %[[KXCOORD:.*]] = fir.coordinate_of %[[GADDR]], $x
+! CHECK: %[[CM1:.*]] = arith.constant -1 : i64
+! CHECK: fir.store %[[CM1]] to %[[KXCOORD]]
+! CHECK: omp.terminator
+! CHECK: }
+
+! -- Wsloop carries the global address as a by-ref reduction -----------------
+! CHECK: omp.wsloop
+! CHECK-SAME: private(@{{.*}}_private
+! CHECK-SAME: reduction(byref @lp_cond_byref_rec__lp_cond_t
+! CHECK-SAME: %[[GADDR]]
+! CHECK-SAME: -> %[[SARG:.*]] :
+
+! -- Loop body: x is an ordinary private copy; a guarded commit updates the
+! -- global accumulator's value and index fields. ----------------------------
+! CHECK: omp.loop_nest
+! CHECK: hlfir.assign
+! CHECK: fir.coordinate_of %[[SARG]], x
+! CHECK: fir.coordinate_of %[[SARG]], $x
+! CHECK: arith.cmpi sge, %{{.*}}, %{{.*}} : i64
+! CHECK: fir.if
+! CHECK: fir.store %{{.*}} : !fir.ref<i32>
+! CHECK: fir.store %{{.*}} : !fir.ref<i64>
+
+! -- Copy-back: load winning value from global and store to dummy arg ---------
+! CHECK: fir.coordinate_of %[[GADDR]], x
+! CHECK: fir.load
+! CHECK: fir.coordinate_of %[[GADDR]], $x
+! CHECK: fir.load
+! CHECK: arith.cmpi sge
+! CHECK: fir.if
+! CHECK: fir.store
+! CHECK: }
+
+! -- Module-level global declared at end of module (not a stack alloca) -------
+! CHECK: fir.global internal @_lp_cond_global.{{l[0-9]+\.[0-9]+}} : !fir.type<_lp_cond_t.{{l[0-9]+\.[0-9]+}}{x:i32,$x:i64}>
diff --git a/flang/test/Lower/OpenMP/lastprivate-conditional-wsloop.f90 b/flang/test/Lower/OpenMP/lastprivate-conditional-wsloop.f90
new file mode 100644
index 0000000000000..992cdfa18e7ae
--- /dev/null
+++ b/flang/test/Lower/OpenMP/lastprivate-conditional-wsloop.f90
@@ -0,0 +1,107 @@
+! Test lowering of `lastprivate(conditional:)` on a worksharing do loop
+! with multiple variables. The lowering must:
+! 1. Build a packed struct type {val, val, ..., idx, idx, ...}
+! 2. Create an omp.declare_reduction with identity 0 / -1
+! 3. Inject the struct as a by-ref reduction variable on the wsloop
+! 4. Rewrite assignments to use struct value fields + store canonical IV
+! 5. Copy back the winning values after the wsloop
+
+! RUN: bbc -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=50 -emit-hlfir %s -o - | FileCheck %s
+
+subroutine test_conditional_lp(n, a, x, y)
+ implicit none
+ integer, intent(in) :: n
+ integer, intent(in) :: a(n)
+ integer, intent(inout) :: x, y
+ integer :: k
+
+ !$omp parallel do lastprivate(conditional: x, y)
+ do k = 1, n
+ if (a(k) < 150) then
+ x = k + 1
+ end if
+ if (a(k) < 100) then
+ y = k
+ end if
+ end do
+ !$omp end parallel do
+end subroutine
+
+! -- declare_reduction with struct type containing value/index pairs ----------
+! CHECK-LABEL: omp.declare_reduction @lp_cond_byref_rec__lp_cond_t
+! CHECK-SAME: : !fir.ref<!fir.type<_lp_cond_t.{{l[0-9]+\.[0-9]+}}{x:i32,y:i32,$x:i64,$y:i64}>>
+
+! -- Init region: value fields = 0, index fields = -1 ------------------------
+! CHECK: init {
+! CHECK-DAG: arith.constant 0 : i32
+! CHECK-DAG: arith.constant -1 : i64
+! CHECK: }
+
+! -- Combiner: sgt on i64 index fields, two pairs ----------------------------
+! CHECK: combiner {
+! CHECK: arith.cmpi sgt, %{{.*}}, %{{.*}} : i64
+! CHECK: fir.if
+! CHECK: arith.cmpi sgt, %{{.*}}, %{{.*}} : i64
+! CHECK: fir.if
+! CHECK: omp.yield
+
+! -- Struct alloca + init (0 / -1) before parallel ----------------------------
+! CHECK-LABEL: func.func @_QPtest_conditional_lp
+! CHECK: %[[STRUCT:.*]] = fir.alloca !fir.type<_lp_cond_t.{{l[0-9]+\.[0-9]+}}{x:i32,y:i32,$x:i64,$y:i64}>
+! CHECK-DAG: arith.constant 0 : i32
+! CHECK-DAG: arith.constant -1 : i64
+
+! -- Wsloop carries the struct as a by-ref reduction; list items x,y are their
+! -- own private copies (the working values). --------------------------------
+! CHECK: omp.parallel {
+! CHECK: omp.wsloop
+! CHECK-SAME: private(@{{.*}}Ex_private
+! CHECK-SAME: reduction(byref @lp_cond_byref_rec__lp_cond_t
+! CHECK-SAME: %[[STRUCT]]
+! CHECK-SAME: -> %[[SARG:.*]] :
+
+! -- Loop body: each list item is an ordinary private copy; a guarded commit
+! -- keeps the value from the highest canonical index in the accumulator. ------
+! CHECK: omp.loop_nest (%{{.*}}) : i32
+! CHECK: fir.if
+! CHECK: hlfir.assign
+! CHECK: fir.coordinate_of %[[SARG]], x
+! CHECK: fir.coordinate_of %[[SARG]], $x
+! CHECK: arith.cmpi sge, %{{.*}}, %{{.*}} : i64
+! CHECK: fir.if
+! CHECK: fir.store %{{.*}} : !fir.ref<i32>
+! CHECK: fir.store %{{.*}} : !fir.ref<i64>
+! CHECK: fir.if
+! CHECK: hlfir.assign
+! CHECK: fir.coordinate_of %[[SARG]], y
+! CHECK: fir.coordinate_of %[[SARG]], $y
+! CHECK: arith.cmpi sge, %{{.*}}, %{{.*}} : i64
+! CHECK: fir.if
+! CHECK: fir.store %{{.*}} : !fir.ref<i32>
+! CHECK: fir.store %{{.*}} : !fir.ref<i64>
+
+! -- Copy-back in an omp.single sibling of the wsloop inside the parallel. The
+! -- extra immediately-nested construct means the parallel is not omp.combined.
+! -- Guarded: only store if index >= 0. --------------------------------------
+! CHECK: omp.single {
+! CHECK: fir.coordinate_of %[[STRUCT]], {{[xy]}}
+! CHECK: fir.load
+! CHECK: fir.coordinate_of %[[STRUCT]], ${{[xy]}}
+! CHECK: fir.load
+! CHECK: arith.cmpi sge
+! CHECK: fir.if
+! CHECK: fir.store
+! CHECK: }
+! CHECK: fir.coordinate_of %[[STRUCT]], {{[xy]}}
+! CHECK: fir.load
+! CHECK: fir.coordinate_of %[[STRUCT]], ${{[xy]}}
+! CHECK: fir.load
+! CHECK: arith.cmpi sge
+! CHECK: fir.if
+! CHECK: fir.store
+! CHECK: }
+! CHECK: omp.terminator
+! CHECK: }
+! CHECK: omp.terminator
+! CHECK: }
diff --git a/flang/test/Semantics/OpenMP/lastprivate-conditional-aggregate.f90 b/flang/test/Semantics/OpenMP/lastprivate-conditional-aggregate.f90
new file mode 100644
index 0000000000000..d224e33bd052b
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/lastprivate-conditional-aggregate.f90
@@ -0,0 +1,37 @@
+!RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=50
+
+! A LASTPRIVATE clause with the CONDITIONAL modifier must name a whole scalar
+! variable. Aggregate designators -- array elements, array sections, and
+! structure components -- are rejected by the general LASTPRIVATE object
+! diagnostics; verify they fail gracefully (no silent acceptance) when the
+! conditional modifier is present.
+
+subroutine aggregates(n)
+ integer :: n, i
+ integer :: arr(10)
+ type t
+ integer :: c
+ end type
+ type(t) :: dt
+
+!ERROR: An array element cannot appear in a LASTPRIVATE clause
+ !$omp parallel do lastprivate(conditional: arr(1))
+ do i = 1, n
+ arr(1) = i
+ end do
+ !$omp end parallel do
+
+!ERROR: An array element cannot appear in a LASTPRIVATE clause
+ !$omp parallel do lastprivate(conditional: arr(1:5))
+ do i = 1, n
+ arr(1) = i
+ end do
+ !$omp end parallel do
+
+!ERROR: A structure component cannot appear in a LASTPRIVATE clause
+ !$omp parallel do lastprivate(conditional: dt%c)
+ do i = 1, n
+ dt%c = i
+ end do
+ !$omp end parallel do
+end subroutine
diff --git a/flang/test/Semantics/OpenMP/lastprivate-conditional-scalar.f90 b/flang/test/Semantics/OpenMP/lastprivate-conditional-scalar.f90
new file mode 100644
index 0000000000000..468be41bccdeb
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/lastprivate-conditional-scalar.f90
@@ -0,0 +1,163 @@
+! RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=52
+
+! A list item that appears in a lastprivate clause with a conditional modifier
+! must be a scalar variable.
+
+subroutine foo()
+ integer :: s, i
+ character(len=8) :: c
+ character(len=:), allocatable :: dc
+ integer :: arr(10)
+ integer :: mat(3, 3)
+ type t
+ integer :: a
+ end type
+ type(t) :: dt
+ class(t), allocatable :: poly
+ integer, allocatable :: alloc
+ integer, pointer :: ptr
+ integer, target :: tgt
+ real :: r
+ complex :: z
+ logical :: lg
+ class(*), allocatable :: up
+ character(len=8), allocatable :: ca
+ character(len=8), pointer :: cp
+ type(t), allocatable :: da
+ type(t), pointer :: dp
+
+ ! Scalar intrinsic list items of every accepted category are allowed.
+ !$omp do lastprivate(conditional: s)
+ do i = 1, 100
+ if (mod(i, 2) == 0) s = i
+ enddo
+ !$omp end do
+
+ !$omp do lastprivate(conditional: r)
+ do i = 1, 100
+ if (mod(i, 2) == 0) r = i
+ enddo
+ !$omp end do
+
+ !$omp do lastprivate(conditional: z)
+ do i = 1, 100
+ if (mod(i, 2) == 0) z = cmplx(i, i)
+ enddo
+ !$omp end do
+
+ !$omp do lastprivate(conditional: lg)
+ do i = 1, 100
+ if (mod(i, 2) == 0) lg = mod(i, 3) == 0
+ enddo
+ !$omp end do
+
+!ERROR: A list item that appears in a LASTPRIVATE clause with the CONDITIONAL modifier must be a scalar variable with intrinsic type, as defined by the Fortran language, excluding character type, but 'c' is not
+ !$omp do lastprivate(conditional: c)
+ do i = 1, 100
+ if (mod(i, 2) == 0) c = 'even'
+ enddo
+ !$omp end do
+
+!ERROR: A list item that appears in a LASTPRIVATE clause with the CONDITIONAL modifier must be a scalar variable with intrinsic type, as defined by the Fortran language, excluding character type, but 'arr' is not
+ !$omp do lastprivate(conditional: arr)
+ do i = 1, 100
+ if (mod(i, 2) == 0) arr(1) = i
+ enddo
+ !$omp end do
+
+!ERROR: A list item that appears in a LASTPRIVATE clause with the CONDITIONAL modifier must be a scalar variable with intrinsic type, as defined by the Fortran language, excluding character type, but 'mat' is not
+ !$omp do lastprivate(conditional: mat)
+ do i = 1, 100
+ if (mod(i, 2) == 0) mat(1, 1) = i
+ enddo
+ !$omp end do
+
+!ERROR: A list item that appears in a LASTPRIVATE clause with the CONDITIONAL modifier must be a scalar variable with intrinsic type, as defined by the Fortran language, excluding character type, but 'dt' is not
+ !$omp do lastprivate(conditional: dt)
+ do i = 1, 100
+ if (mod(i, 2) == 0) dt%a = i
+ enddo
+ !$omp end do
+
+!ERROR: A POINTER or ALLOCATABLE list item is not yet supported by Flang in a LASTPRIVATE clause with the CONDITIONAL modifier, 'alloc'
+ !$omp do lastprivate(conditional: alloc)
+ do i = 1, 100
+ if (mod(i, 2) == 0) alloc = i
+ enddo
+ !$omp end do
+
+ ptr => tgt
+!ERROR: A POINTER or ALLOCATABLE list item is not yet supported by Flang in a LASTPRIVATE clause with the CONDITIONAL modifier, 'ptr'
+ !$omp do lastprivate(conditional: ptr)
+ do i = 1, 100
+ if (mod(i, 2) == 0) ptr = i
+ enddo
+ !$omp end do
+
+ ! A polymorphic entity is not a scalar variable of intrinsic type.
+!ERROR: A list item that appears in a LASTPRIVATE clause with the CONDITIONAL modifier must be a scalar variable with intrinsic type, as defined by the Fortran language, excluding character type, but 'poly' is not
+ !$omp do lastprivate(conditional: poly)
+ do i = 1, 100
+ if (mod(i, 2) == 0) poly = t(i)
+ enddo
+ !$omp end do
+
+ ! A deferred-length character is still character type and is excluded.
+!ERROR: A list item that appears in a LASTPRIVATE clause with the CONDITIONAL modifier must be a scalar variable with intrinsic type, as defined by the Fortran language, excluding character type, but 'dc' is not
+ !$omp do lastprivate(conditional: dc)
+ do i = 1, 100
+ if (mod(i, 2) == 0) dc = 'x'
+ enddo
+ !$omp end do
+
+ ! Unlimited polymorphic is not a scalar variable of intrinsic type.
+!ERROR: A list item that appears in a LASTPRIVATE clause with the CONDITIONAL modifier must be a scalar variable with intrinsic type, as defined by the Fortran language, excluding character type, but 'up' is not
+ !$omp do lastprivate(conditional: up)
+ do i = 1, 100
+ enddo
+ !$omp end do
+
+ ! An ALLOCATABLE character is character type; the type check fires before
+ ! the POINTER/ALLOCATABLE check.
+!ERROR: A list item that appears in a LASTPRIVATE clause with the CONDITIONAL modifier must be a scalar variable with intrinsic type, as defined by the Fortran language, excluding character type, but 'ca' is not
+ !$omp do lastprivate(conditional: ca)
+ do i = 1, 100
+ enddo
+ !$omp end do
+
+ ! A POINTER character is likewise excluded by the type check.
+!ERROR: A list item that appears in a LASTPRIVATE clause with the CONDITIONAL modifier must be a scalar variable with intrinsic type, as defined by the Fortran language, excluding character type, but 'cp' is not
+ !$omp do lastprivate(conditional: cp)
+ do i = 1, 100
+ enddo
+ !$omp end do
+
+ ! An ALLOCATABLE derived type is excluded by the type check.
+!ERROR: A list item that appears in a LASTPRIVATE clause with the CONDITIONAL modifier must be a scalar variable with intrinsic type, as defined by the Fortran language, excluding character type, but 'da' is not
+ !$omp do lastprivate(conditional: da)
+ do i = 1, 100
+ enddo
+ !$omp end do
+
+ ! A POINTER derived type is excluded by the type check.
+!ERROR: A list item that appears in a LASTPRIVATE clause with the CONDITIONAL modifier must be a scalar variable with intrinsic type, as defined by the Fortran language, excluding character type, but 'dp' is not
+ !$omp do lastprivate(conditional: dp)
+ do i = 1, 100
+ enddo
+ !$omp end do
+end
+
+
+! A common block is not a scalar variable (it has no type), so it is rejected
+! by the conditional scalar-variable restriction.
+subroutine bar()
+ integer :: i
+ integer :: gi
+ real :: gr
+ common /cb/ gi, gr
+!ERROR: A list item that appears in a LASTPRIVATE clause with the CONDITIONAL modifier must be a scalar variable with intrinsic type, as defined by the Fortran language, excluding character type, but 'cb' is not
+ !$omp do lastprivate(conditional: /cb/)
+ do i = 1, 100
+ enddo
+ !$omp end do
+end
diff --git a/flang/test/Semantics/OpenMP/test_distribute_lastprivate_semantic_restrictions.f90 b/flang/test/Semantics/OpenMP/test_distribute_lastprivate_semantic_restrictions.f90
new file mode 100644
index 0000000000000..0e0dfbdb81c9c
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/test_distribute_lastprivate_semantic_restrictions.f90
@@ -0,0 +1,43 @@
+!RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=52
+
+! The CONDITIONAL lastprivate modifier must not be specified on a distribute
+! directive, including combined and composite forms that have distribute as a
+! leaf construct.
+
+subroutine foo(n)
+ integer :: n, x, i
+ x = 1
+ !$omp teams
+!ERROR: 'CONDITIONAL' modifier on lastprivate clause with DISTRIBUTE directive is not allowed
+ !$omp distribute lastprivate(conditional: x)
+ do i = 1, n
+ if (mod(i, 2) == 0) x = i
+ enddo
+ !$omp end distribute
+ !$omp end teams
+end subroutine
+
+subroutine bar(n)
+ integer :: n, x, i
+ x = 1
+!ERROR: 'CONDITIONAL' modifier on lastprivate clause with DISTRIBUTE directive is not allowed
+ !$omp teams distribute parallel do lastprivate(conditional: x)
+ do i = 1, n
+ if (mod(i, 2) == 0) x = i
+ enddo
+end subroutine
+
+! A composite form that has a worksharing-loop/SIMD leaf is still rejected: the
+! restriction is applied conservatively to any construct with a distribute leaf.
+subroutine baz(n)
+ integer :: n, x, i
+ x = 1
+ !$omp teams
+!ERROR: 'CONDITIONAL' modifier on lastprivate clause with DISTRIBUTE directive is not allowed
+ !$omp distribute parallel do simd lastprivate(conditional: x)
+ do i = 1, n
+ if (mod(i, 2) == 0) x = i
+ enddo
+ !$omp end distribute parallel do simd
+ !$omp end teams
+end subroutine
diff --git a/flang/test/Semantics/OpenMP/test_taskloop_lastprivate_semantic_restrictions.f90 b/flang/test/Semantics/OpenMP/test_taskloop_lastprivate_semantic_restrictions.f90
index 521a9cd031fcf..90dc569862f09 100644
--- a/flang/test/Semantics/OpenMP/test_taskloop_lastprivate_semantic_restrictions.f90
+++ b/flang/test/Semantics/OpenMP/test_taskloop_lastprivate_semantic_restrictions.f90
@@ -1,12 +1,76 @@
!RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=52
+! The CONDITIONAL lastprivate modifier must not be specified on a taskloop
+! directive, including combined and composite forms that have taskloop as a
+! leaf construct.
+
subroutine foo()
integer :: x, i
x = 1
!ERROR: 'CONDITIONAL' modifier on lastprivate clause with TASKLOOP directive is not allowed
!$omp taskloop lastprivate(conditional: x)
do i = 1, 100
- x = x + 1
+ if (mod(i, 2) == 0) x = i
enddo
!$omp end taskloop
end
+
+! Composite: taskloop simd has taskloop as a leaf construct.
+subroutine foo_simd()
+ integer :: x, i
+ x = 1
+!ERROR: 'CONDITIONAL' modifier on lastprivate clause with TASKLOOP directive is not allowed
+ !$omp taskloop simd lastprivate(conditional: x)
+ do i = 1, 100
+ if (mod(i, 2) == 0) x = i
+ enddo
+ !$omp end taskloop simd
+end
+
+! Combined: masked taskloop.
+subroutine masked_tl()
+ integer :: x, i
+ x = 1
+!ERROR: 'CONDITIONAL' modifier on lastprivate clause with TASKLOOP directive is not allowed
+ !$omp masked taskloop lastprivate(conditional: x)
+ do i = 1, 100
+ if (mod(i, 2) == 0) x = i
+ enddo
+ !$omp end masked taskloop
+end
+
+! Combined/composite: masked taskloop simd.
+subroutine masked_tl_simd()
+ integer :: x, i
+ x = 1
+!ERROR: 'CONDITIONAL' modifier on lastprivate clause with TASKLOOP directive is not allowed
+ !$omp masked taskloop simd lastprivate(conditional: x)
+ do i = 1, 100
+ if (mod(i, 2) == 0) x = i
+ enddo
+ !$omp end masked taskloop simd
+end
+
+! Combined: parallel masked taskloop.
+subroutine par_masked_tl()
+ integer :: x, i
+ x = 1
+!ERROR: 'CONDITIONAL' modifier on lastprivate clause with TASKLOOP directive is not allowed
+ !$omp parallel masked taskloop lastprivate(conditional: x)
+ do i = 1, 100
+ if (mod(i, 2) == 0) x = i
+ enddo
+ !$omp end parallel masked taskloop
+end
+
+! Combined/composite: parallel masked taskloop simd.
+subroutine par_masked_tl_simd()
+ integer :: x, i
+ x = 1
+!ERROR: 'CONDITIONAL' modifier on lastprivate clause with TASKLOOP directive is not allowed
+ !$omp parallel masked taskloop simd lastprivate(conditional: x)
+ do i = 1, 100
+ if (mod(i, 2) == 0) x = i
+ enddo
+ !$omp end parallel masked taskloop simd
+end
More information about the flang-commits
mailing list