[flang-commits] [flang] [Flang][OpenMP] Fix crash and IR errors for user-defined reduction on allocatable variables (PR #186765)
via flang-commits
flang-commits at lists.llvm.org
Wed Jul 22 12:48:35 PDT 2026
================
@@ -734,6 +735,150 @@ bool ReductionProcessor::doReductionByRef(mlir::Value reductionVar) {
return doReductionByRef(reductionVar.getType());
}
+static bool baseInitReadsOrig(mlir::Block &initBlock) {
+ bool reads = false;
+ initBlock.walk([&](hlfir::DeclareOp declOp) {
+ std::optional<llvm::StringRef> name = declOp.getUniqName();
+ if (name && *name == "omp_orig" &&
+ (!declOp.getResult(0).use_empty() || !declOp.getResult(1).use_empty()))
+ reads = true;
+ });
+ return reads;
+}
+
+template <typename OpType>
+static OpType getOrCreateBoxedUserReduction(
+ lower::AbstractConverter &converter, semantics::SemanticsContext *semaCtx,
+ const semantics::Symbol &redSym, mlir::Type redType, mlir::Location loc) {
+ fir::FirOpBuilder &builder = converter.getFirOpBuilder();
+ mlir::ModuleOp module = builder.getModule();
+
+ // Boxed op name encodes the full boxed type (..._byref_box_heap_i32),
+ // distinct from the base element op.
+ std::string boxedName = ReductionProcessor::getScopedUserReductionName(
+ converter, redSym, redType, /*isByRef=*/true);
+ if (auto existing = module.lookupSymbol<OpType>(boxedName))
+ return existing;
+
+ // Base op is named from the scalar element type (identical for scalar and
+ // array); materialize on demand for separate compilation.
+ mlir::Type elementType = unwrapSeqOrBoxedType(fir::unwrapRefType(redType));
+ const bool baseByRef = ReductionProcessor::doReductionByRef(elementType);
+ std::string baseName = ReductionProcessor::getScopedUserReductionName(
+ converter, redSym, elementType, baseByRef);
+ auto baseDecl = module.lookupSymbol<OpType>(baseName);
+ if (!baseDecl && semaCtx && redSym.owner().symbol() &&
+ redSym.owner().symbol()->test(semantics::Symbol::Flag::ModFile)) {
+ Fortran::lower::materializeUserReduction(converter, *semaCtx, redSym,
+ baseName, elementType, baseByRef);
+ baseDecl = module.lookupSymbol<OpType>(baseName);
+ }
+ if (!baseDecl)
+ return {};
+
+ // Only a by-value base op can be wrapped (its init/combiner yield scalars).
+ mlir::Region &baseInitRegion = baseDecl.getInitializerRegion();
+ mlir::Region &baseCombinerRegion = baseDecl.getReductionRegion();
+ mlir::Block &baseInitBlock = baseInitRegion.front();
+ if (fir::isa_ref_type(baseInitBlock.getArgument(0).getType()) ||
+ fir::isa_ref_type(baseCombinerRegion.front().getArgument(0).getType()))
+ return {};
+ const bool initReadsOrig = baseInitReadsOrig(baseInitBlock);
+
+ // Clone the region's entry block (up to its terminator) mapping block args to
+ // \p args; return the yielded value.
+ auto cloneBody = [](fir::FirOpBuilder &b, mlir::Region ®ion,
+ mlir::ValueRange args) -> mlir::Value {
+ mlir::Block &block = region.front();
+ mlir::IRMapping mapper;
+ for (auto [blockArg, val] : llvm::zip(block.getArguments(), args))
+ mapper.map(blockArg, val);
+ for (mlir::Operation &op : block.without_terminator())
+ b.clone(op, mapper);
+ mlir::Operation *term = block.getTerminator();
+ if (term && term->getNumOperands() > 0)
+ return mapper.lookupOrDefault(term->getOperand(0));
+ return {};
+ };
+
+ auto genInitValueCB = [&](fir::FirOpBuilder &b, mlir::Location loc,
+ mlir::Type ty, mlir::Value moldArg,
+ mlir::Value /*privArg*/) -> mlir::Value {
+ mlir::Type scalarTy = unwrapSeqOrBoxedType(ty);
+ llvm::SmallVector<mlir::Value> initArgs;
+ if (initReadsOrig) {
+ auto boxTy = mlir::dyn_cast<fir::BaseBoxType>(fir::unwrapRefType(ty));
+ auto seqTy = boxTy ? mlir::dyn_cast_or_null<fir::SequenceType>(
+ fir::unwrapRefType(boxTy.getEleTy()))
+ : nullptr;
+ if (seqTy)
+ TODO(loc, "OpenMP user-defined reduction on an allocatable/pointer "
+ "array whose initializer reads omp_orig");
+ // Scalar: feed the original element to the cloned initializer's omp_orig.
+ mlir::Value box = fir::LoadOp::create(b, loc, moldArg);
+ mlir::Value addr = fir::BoxAddrOp::create(b, loc, box);
+ initArgs.push_back(fir::LoadOp::create(b, loc, addr));
+ } else {
+ // orig-independent: a placeholder feeds the base's dead temporary stores.
+ initArgs.push_back(fir::UndefOp::create(b, loc, scalarTy));
+ }
+ return cloneBody(b, baseInitRegion, initArgs);
----------------
MattPD wrote:
Non-blocking. For an allocatable array, this returns a single scalar init value, which the shared boxed-array init then broadcasts to every element with `hlfir.assign`. For a constant identity like `omp_priv = 0` that is correct and matches the intrinsic reduction path. An initializer with observable per-element evaluation cannot safely be broadcast, though: you already gate an array `omp_priv = omp_orig` behind a clean TODO in this callback, but a non-`omp_orig`, non-constant initializer such as `omp_priv = f()` for some function `f` falls through to a single call broadcast across the whole array. I checked, and `f` is invoked once, not once per element. Is broadcasting intended here (treating the value as the reduction identity), or would routing these initializers through the same TODO be more consistent?
https://github.com/llvm/llvm-project/pull/186765
More information about the flang-commits
mailing list