[flang-commits] [flang] [Flang][OpenMP] Support conditional lastprivate on GPU targets (PR #224380)

Sairudra More via flang-commits flang-commits at lists.llvm.org
Mon Sep 21 03:19:34 PDT 2026


================
@@ -4982,6 +5097,90 @@ getOrCreateConditionalLpGlobal(lower::AbstractConverter &converter,
                                global.getSymbol());
 }
 
+/// Return the innermost enclosing omp.target, or a null op if the current
+/// insertion point is not inside a target region (i.e. host lowering).
+static mlir::omp::TargetOp findEnclosingTargetOp(fir::FirOpBuilder &builder) {
+  for (auto *op = builder.getInsertionBlock()->getParentOp(); op;
+       op = op->getParentOp()) {
+    if (auto targetOp = mlir::dyn_cast<mlir::omp::TargetOp>(op))
+      return targetOp;
+  }
+  return {};
+}
+
+/// Return the innermost enclosing omp.teams, or a null op if there is none.
+static mlir::omp::TeamsOp findEnclosingTeamsOp(fir::FirOpBuilder &builder) {
+  for (auto *op = builder.getInsertionBlock()->getParentOp(); op;
+       op = op->getParentOp()) {
+    if (auto teamsOp = mlir::dyn_cast<mlir::omp::TeamsOp>(op))
+      return teamsOp;
+  }
+  return {};
+}
+
+/// Host placement of the conditional-lastprivate reduction struct for a
+/// worksharing loop.  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.
+static void placeConditionalLpStructOnHost(lower::AbstractConverter &converter,
+                                           mlir::Location loc,
+                                           fir::RecordType lpType,
+                                           mlir::Value &lpAlloca) {
+  fir::FirOpBuilder &builder = converter.getFirOpBuilder();
+  mlir::omp::ParallelOp 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);
+  }
+}
+
+/// Device (GPU offload) placement of the conditional-lastprivate reduction
+/// struct for a worksharing loop nested inside an omp.target region.
+///
+/// Each team runs an independent wsloop reduction, so the struct is allocated
+/// at the start of the enclosing omp.teams body (or the omp.target body when
+/// there is no teams) to give every team its own reduction target rather than
+/// racing on a single shared alloca.  The copy-back is likewise deferred until
+/// after the parallel region (see genStandaloneDo).
+static void placeConditionalLpStructInTarget(fir::FirOpBuilder &builder,
+                                             mlir::Location loc,
+                                             fir::RecordType lpType,
+                                             mlir::omp::TargetOp targetOp,
+                                             mlir::Value &lpAlloca) {
+  mlir::OpBuilder::InsertionGuard guard(builder);
+  if (mlir::omp::TeamsOp teamsOp = findEnclosingTeamsOp(builder))
+    builder.setInsertionPointToStart(&teamsOp.getRegion().front());
+  else
+    builder.setInsertionPointToStart(&targetOp.getRegion().front());
+  lpAlloca = builder.createTemporary(loc, lpType);
+  initConditionalLpStructDefault(builder, loc, lpType, lpAlloca);
----------------
Saieiei wrote:

Could we reset this state for each dynamic encounter of the worksharing loop?

On b37b2eb8e401, I can reproduce this with a `target if(.false.)` containing `do j = 1, 2` around the same `parallel do lastprivate(conditional:x)`, assigning `x = j` at the same final iteration each time. The result is `1, 1` instead of `1, 2`.

It looks like the accumulator initialized here survives both encounters, so the equal-index winner from the second encounter does not replace the first. A repeated-encounter regression would cover this.


https://github.com/llvm/llvm-project/pull/224380


More information about the flang-commits mailing list