Skip to content

Commit 960e149

Browse files
SaieieiSairudra More
authored andcommitted
[flang][OpenMP] Lower target in_reduction for host fallback
Teach Flang lowering and MLIR OpenMP translation to carry in_reduction through omp.target for the host-fallback path. The translation looks up task reduction-private storage with __kmpc_task_reduction_get_th_data and binds the target region's in_reduction block argument to that private pointer, so uses inside the region do not keep referring to the original variable. The patch also preserves in_reduction operands in the TargetOp builder path and ensures target in_reduction list items are mapped into the target region when needed. The device/offload-entry path remains diagnosed as not yet implemented.
1 parent 1ca9edc commit 960e149

9 files changed

Lines changed: 412 additions & 30 deletions

File tree

flang/lib/Lower/OpenMP/OpenMP.cpp

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -433,13 +433,16 @@ static void bindEntryBlockArgs(lower::AbstractConverter &converter,
433433
.first);
434434
};
435435

436-
// Process in clause name alphabetical order to match block arguments order.
437436
// Do not bind host_eval variables because they cannot be used inside of the
438437
// corresponding region, except for very specific cases handled separately.
438+
// Bind map before in_reduction so that for target in_reduction list items
439+
// (which are also implicitly mapped), the in_reduction binding wins and
440+
// in-body references use the reduction-private block argument, not the
441+
// mapped/original address.
439442
bindMapLike(args.hasDeviceAddr.objects, op.getHasDeviceAddrBlockArgs());
443+
bindMapLike(args.map.objects, op.getMapBlockArgs());
440444
bindPrivateLike(args.inReduction.objects, args.inReduction.vars,
441445
op.getInReductionBlockArgs());
442-
bindMapLike(args.map.objects, op.getMapBlockArgs());
443446
bindPrivateLike(args.priv.objects, args.priv.vars, op.getPrivateBlockArgs());
444447
bindPrivateLike(args.reduction.objects, args.reduction.vars,
445448
op.getReductionBlockArgs());
@@ -1873,6 +1876,7 @@ genTargetClauses(lower::AbstractConverter &converter,
18731876
mlir::omp::TargetOperands &clauseOps,
18741877
DefaultMapsTy &defaultMaps,
18751878
llvm::SmallVectorImpl<Object> &hasDeviceAddrObjects,
1879+
llvm::SmallVectorImpl<Object> &inReductionObjects,
18761880
llvm::SmallVectorImpl<Object> &isDevicePtrObjects,
18771881
llvm::SmallVectorImpl<Object> &mapObjects) {
18781882
ClauseProcessor cp(converter, semaCtx, clauses);
@@ -1887,13 +1891,14 @@ genTargetClauses(lower::AbstractConverter &converter,
18871891
hostEvalInfo->collectValues(clauseOps.hostEvalVars);
18881892
}
18891893
cp.processIf(llvm::omp::Directive::OMPD_target, clauseOps);
1894+
cp.processInReduction(loc, clauseOps, inReductionObjects);
18901895
cp.processIsDevicePtr(stmtCtx, clauseOps, isDevicePtrObjects);
18911896
cp.processMap(loc, stmtCtx, clauseOps, llvm::omp::Directive::OMPD_unknown,
18921897
&mapObjects);
18931898
cp.processNowait(clauseOps);
18941899
cp.processThreadLimit(stmtCtx, clauseOps);
18951900

1896-
cp.processTODO<clause::Allocate, clause::InReduction, clause::UsesAllocators>(
1901+
cp.processTODO<clause::Allocate, clause::UsesAllocators>(
18971902
loc, llvm::omp::Directive::OMPD_target);
18981903

18991904
// `target private(..)` is only supported in delayed privatization mode.
@@ -2932,10 +2937,10 @@ genTargetOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
29322937
mlir::omp::TargetOperands clauseOps;
29332938
DefaultMapsTy defaultMaps;
29342939
llvm::SmallVector<Object> mapObjects, hasDeviceAddrObjects,
2935-
isDevicePtrObjects;
2940+
inReductionObjects, isDevicePtrObjects;
29362941
genTargetClauses(converter, semaCtx, symTable, stmtCtx, eval, item->clauses,
29372942
loc, clauseOps, defaultMaps, hasDeviceAddrObjects,
2938-
isDevicePtrObjects, mapObjects);
2943+
inReductionObjects, isDevicePtrObjects, mapObjects);
29392944

29402945
if (!isDevicePtrObjects.empty()) {
29412946
// is_device_ptr maps get duplicated so the clause and synthesized
@@ -3108,6 +3113,58 @@ genTargetOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
31083113
Object{const_cast<semantics::Symbol *>(&sym), std::nullopt});
31093114
}
31103115
};
3116+
// OpenMP requires `in_reduction` list items on `target` to be implicitly
3117+
// data-mapped. The MLIR -> LLVM IR translation passes the mapped pointer
3118+
// as the `orig` argument of `__kmpc_task_reduction_get_th_data`, so the
3119+
// map must be address-preserving regardless of the scalar default capture
3120+
// (which would otherwise be ByCopy for small scalars and break the
3121+
// runtime lookup against the enclosing taskgroup's task_reduction
3122+
// descriptor). Emit these maps before the generic implicit-map walk so
3123+
// that walk treats the symbols as already mapped via
3124+
// `isDuplicateMappedSymbol` and does not downgrade them to ByCopy.
3125+
auto captureInReductionImplicitMap = [&](const semantics::Symbol &sym) {
3126+
if (sym.owner().IsDerivedType())
3127+
return;
3128+
if (!converter.getSymbolAddress(sym))
3129+
return;
3130+
if (isDuplicateMappedSymbol(sym, dsp.getAllSymbolsToPrivatize(),
3131+
hasDeviceAddrObjects, mapObjects,
3132+
isDevicePtrObjects))
3133+
return;
3134+
if (const auto *details =
3135+
sym.template detailsIf<semantics::HostAssocDetails>())
3136+
converter.copySymbolBinding(details->symbol(), sym);
3137+
std::stringstream name;
3138+
fir::ExtendedValue dataExv = converter.getSymbolExtendedValue(sym);
3139+
name << sym.name().ToString();
3140+
fir::factory::AddrAndBoundsInfo info =
3141+
Fortran::lower::getDataOperandBaseAddr(converter, firOpBuilder,
3142+
sym.GetUltimate(),
3143+
converter.getCurrentLocation());
3144+
llvm::SmallVector<mlir::Value> bounds =
3145+
fir::factory::genImplicitBoundsOps<mlir::omp::MapBoundsOp,
3146+
mlir::omp::MapBoundsType>(
3147+
firOpBuilder, info, dataExv,
3148+
semantics::IsAssumedSizeArray(sym.GetUltimate()),
3149+
converter.getCurrentLocation());
3150+
mlir::Value baseOp = info.rawInput;
3151+
mlir::omp::ClauseMapFlags flags = mlir::omp::ClauseMapFlags::implicit |
3152+
mlir::omp::ClauseMapFlags::to |
3153+
mlir::omp::ClauseMapFlags::from;
3154+
mlir::Value mapOp = createMapInfoOp(
3155+
firOpBuilder, converter.getCurrentLocation(), baseOp,
3156+
/*varPtrPtr=*/mlir::Value{}, name.str(), bounds, /*members=*/{},
3157+
/*membersIndex=*/mlir::ArrayAttr{}, flags,
3158+
mlir::omp::VariableCaptureKind::ByRef, baseOp.getType(),
3159+
/*partialMap=*/false, /*mapperId=*/mlir::FlatSymbolRefAttr{});
3160+
clauseOps.mapVars.push_back(mapOp);
3161+
mapObjects.push_back(
3162+
Object{const_cast<semantics::Symbol *>(&sym), std::nullopt});
3163+
};
3164+
for (const Object &object : inReductionObjects)
3165+
if (const semantics::Symbol *sym = object.sym())
3166+
captureInReductionImplicitMap(*sym);
3167+
31113168
lower::pft::visitAllSymbols(eval, captureImplicitMap);
31123169

31133170
auto targetOp = mlir::omp::TargetOp::create(firOpBuilder, loc, clauseOps);
@@ -3120,7 +3177,8 @@ genTargetOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
31203177
args.hasDeviceAddr.objects = hasDeviceAddrObjects;
31213178
args.hasDeviceAddr.vars = hasDeviceAddrBaseValues;
31223179
args.hostEvalVars = clauseOps.hostEvalVars;
3123-
// TODO: Add in_reduction syms and vars.
3180+
args.inReduction.objects = inReductionObjects;
3181+
args.inReduction.vars = clauseOps.inReductionVars;
31243182
args.map.objects = mapObjects;
31253183
args.map.vars = mapBaseValues;
31263184
args.priv.objects = makeObjects(dsp.getDelayedPrivSymbols());

flang/test/Lower/OpenMP/Todo/target-inreduction.f90

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
! RUN: bbc -emit-hlfir -fopenmp -fopenmp-version=50 -o - %s 2>&1 | FileCheck %s
2+
! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %s 2>&1 | FileCheck %s
3+
4+
! Per the OpenMP spec, an in_reduction list item on a target construct is
5+
! implicitly data-mapped. The lowering must not rely on the variable being
6+
! referenced inside the target body to discover that map: here `i` only
7+
! appears in the in_reduction clause and is never read or written inside
8+
! the region. Verify that an omp.map.info for `i` is still emitted and
9+
! flows into the omp.target's map_entries.
10+
11+
!CHECK-LABEL: func.func @_QPomp_target_in_reduction_unused()
12+
!CHECK: %[[IDECL:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFomp_target_in_reduction_unusedEi"}
13+
!CHECK: %[[IMAP:.*]] = omp.map.info var_ptr(%[[IDECL]]#1 : !fir.ref<i32>, i32) map_clauses(implicit, tofrom) capture(ByRef) -> !fir.ref<i32> {name = "i"}
14+
!CHECK: omp.target in_reduction(@{{[^ ]+}} %[[IDECL]]#0 -> %{{[^ ]+}} : !fir.ref<i32>)
15+
!CHECK-SAME: map_entries(%[[IMAP]] -> %{{[^ ]+}} : !fir.ref<i32>)
16+
17+
subroutine omp_target_in_reduction_unused()
18+
interface
19+
subroutine sub()
20+
end subroutine
21+
end interface
22+
integer i
23+
i = 0
24+
!$omp target in_reduction(+:i)
25+
call sub()
26+
!$omp end target
27+
end subroutine omp_target_in_reduction_unused
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
! RUN: bbc -emit-hlfir -fopenmp -fopenmp-version=50 -o - %s 2>&1 | FileCheck %s
2+
! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %s 2>&1 | FileCheck %s
3+
4+
! Verify that in_reduction on a target construct is lowered to an
5+
! omp.target with both an in_reduction clause and an implicit map_entries
6+
! entry for the same variable. The implicit map captures the original
7+
! pointer into the target region so the MLIR -> LLVM IR translation can
8+
! pass it to __kmpc_task_reduction_get_th_data.
9+
10+
!CHECK-LABEL: omp.declare_reduction
11+
!CHECK-SAME: @[[RED_I32_NAME:.*]] : i32 init {
12+
13+
!CHECK-LABEL: func.func @_QPomp_target_in_reduction()
14+
!CHECK: %[[IDECL:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFomp_target_in_reductionEi"}
15+
!CHECK: %[[IMAP:.*]] = omp.map.info var_ptr(%[[IDECL]]#1 : !fir.ref<i32>, i32) map_clauses(implicit, tofrom) capture(ByRef) -> !fir.ref<i32> {name = "i"}
16+
!CHECK: omp.target in_reduction(@[[RED_I32_NAME]] %[[IDECL]]#0 -> %[[INARG:[^ ]+]] : !fir.ref<i32>)
17+
!CHECK-SAME: map_entries(%[[IMAP]] -> %{{[^ ]+}} : !fir.ref<i32>)
18+
!CHECK: hlfir.declare %[[INARG]]
19+
!CHECK: omp.terminator
20+
!CHECK: }
21+
22+
subroutine omp_target_in_reduction()
23+
integer i
24+
i = 0
25+
!$omp target in_reduction(+:i)
26+
i = i + 1
27+
!$omp end target
28+
end subroutine omp_target_in_reduction

mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2545,18 +2545,18 @@ LogicalResult TargetUpdateOp::verify() {
25452545
void TargetOp::build(OpBuilder &builder, OperationState &state,
25462546
const TargetOperands &clauses) {
25472547
MLIRContext *ctx = builder.getContext();
2548-
// TODO Store clauses in op: allocateVars, allocatorVars, inReductionVars,
2549-
// inReductionByref, inReductionSyms.
2548+
// TODO Store clauses in op: allocateVars, allocatorVars.
25502549
TargetOp::build(
25512550
builder, state, /*allocate_vars=*/{}, /*allocator_vars=*/{}, clauses.bare,
25522551
makeArrayAttr(ctx, clauses.dependKinds), clauses.dependVars,
25532552
makeArrayAttr(ctx, clauses.dependIteratedKinds), clauses.dependIterated,
25542553
clauses.device, clauses.dynGroupprivateAccessGroup,
25552554
clauses.dynGroupprivateFallback, clauses.dynGroupprivateSize,
25562555
clauses.hasDeviceAddrVars, clauses.hostEvalVars, clauses.ifExpr,
2557-
/*in_reduction_vars=*/{}, /*in_reduction_byref=*/nullptr,
2558-
/*in_reduction_syms=*/nullptr, clauses.isDevicePtrVars, clauses.mapVars,
2559-
clauses.nowait, clauses.privateVars,
2556+
clauses.inReductionVars,
2557+
makeDenseBoolArrayAttr(ctx, clauses.inReductionByref),
2558+
makeArrayAttr(ctx, clauses.inReductionSyms), clauses.isDevicePtrVars,
2559+
clauses.mapVars, clauses.nowait, clauses.privateVars,
25602560
makeArrayAttr(ctx, clauses.privateSyms), clauses.privateNeedsBarrier,
25612561
clauses.threadLimitVars,
25622562
/*private_maps=*/nullptr);
@@ -2583,6 +2583,11 @@ LogicalResult TargetOp::verify() {
25832583
if (failed(verifyPrivateVarList(*this)))
25842584
return failure();
25852585

2586+
if (failed(verifyReductionVarList(*this, getInReductionSyms(),
2587+
getInReductionVars(),
2588+
getInReductionByref())))
2589+
return failure();
2590+
25862591
return verifyPrivateVarsMapping(*this);
25872592
}
25882593

mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,11 @@ static LogicalResult checkImplementationStatus(Operation &op) {
490490
.Case([&](omp::TargetOp op) {
491491
checkAllocate(op, result);
492492
checkBare(op, result);
493-
checkInReduction(op, result);
493+
// in_reduction(byref(...)) on target is not implemented yet. Other
494+
// unsupported in_reduction shapes (cleanup region, two-argument
495+
// initializer, missing combiner) and the device-side / offload-entry
496+
// cases are diagnosed inline in convertOmpTarget.
497+
checkInReductionByref(op, result);
494498
checkThreadLimit(op, result);
495499
})
496500
.Default([](Operation &) {
@@ -8208,6 +8212,61 @@ convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder,
82088212
bool isOffloadEntry =
82098213
isTargetDevice || !ompBuilder->Config.TargetTriples.empty();
82108214

8215+
// Validate and resolve in_reduction clauses on omp.target. We currently
8216+
// only support the non-offload host-fallback path: the per-task private
8217+
// pointer is obtained by calling __kmpc_task_reduction_get_th_data inside
8218+
// the to-be-outlined target task body. Threading that pointer through the
8219+
// device kernel argument list is left as follow-up work.
8220+
SmallVector<llvm::Value *> inRedOrigPtrs;
8221+
if (!targetOp.getInReductionVars().empty()) {
8222+
if (isTargetDevice || isOffloadEntry)
8223+
return opInst.emitError(
8224+
"not yet implemented: in_reduction clause on omp.target with "
8225+
"offload / target-device compilation");
8226+
if (auto inRedSyms = targetOp.getInReductionSyms()) {
8227+
for (auto sym : inRedSyms->getAsRange<SymbolRefAttr>()) {
8228+
auto decl =
8229+
SymbolTable::lookupNearestSymbolFrom<omp::DeclareReductionOp>(
8230+
targetOp, sym);
8231+
if (!decl)
8232+
return targetOp.emitError()
8233+
<< "failed to resolve in_reduction declare_reduction symbol "
8234+
<< sym.getRootReference() << " on omp.target";
8235+
if (decl.getInitializerRegion().front().getNumArguments() != 1)
8236+
return targetOp.emitError()
8237+
<< "not yet implemented: in_reduction with two-argument "
8238+
"initializer on omp.target";
8239+
if (!decl.getCleanupRegion().empty())
8240+
return targetOp.emitError()
8241+
<< "not yet implemented: in_reduction with cleanup region "
8242+
"on omp.target";
8243+
// The reduction combiner region is intentionally not required here:
8244+
// the in_reduction lowering on omp.target only locates the per-task
8245+
// private storage via __kmpc_task_reduction_get_th_data. The combiner
8246+
// is owned by the enclosing taskgroup's task_reduction registration.
8247+
}
8248+
}
8249+
// Each in_reduction variable must also be captured by the target via a
8250+
// map_entries entry referring to the same outer SSA value. OMPIRBuilder
8251+
// outlines the target body and only rewires uses of values that enter
8252+
// the kernel through the map-derived input set. The runtime call below
8253+
// uses that same outer SSA value as its `orig` argument, so without a
8254+
// matching map entry the outlined kernel would reference a value defined
8255+
// in the host function and fail IR verification.
8256+
llvm::SmallPtrSet<Value, 4> mappedVarPtrs;
8257+
for (Value mapV : targetOp.getMapVars())
8258+
if (auto mapInfo = mapV.getDefiningOp<omp::MapInfoOp>())
8259+
mappedVarPtrs.insert(mapInfo.getVarPtr());
8260+
inRedOrigPtrs.reserve(targetOp.getInReductionVars().size());
8261+
for (Value v : targetOp.getInReductionVars()) {
8262+
if (!mappedVarPtrs.contains(v))
8263+
return targetOp.emitError()
8264+
<< "not yet implemented: in_reduction variable on omp.target "
8265+
"must also be captured by a matching map_entries entry";
8266+
inRedOrigPtrs.push_back(moduleTranslation.lookupValue(v));
8267+
}
8268+
}
8269+
82118270
// For some private variables, the MapsForPrivatizedVariablesPass
82128271
// creates MapInfoOp instances. Go through the private variables and
82138272
// the mapped variables so that during codegeneration we are able
@@ -8320,6 +8379,36 @@ convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder,
83208379
targetOp.getPrivateNeedsBarrier(), &mappedPrivateVars)))
83218380
return llvm::make_error<PreviouslyReportedError>();
83228381

8382+
// Map in_reduction block arguments to the per-task private storage
8383+
// returned by __kmpc_task_reduction_get_th_data. The lookup must run
8384+
// inside the target task body so the gtid corresponds to the executing
8385+
// thread. The descriptor argument is NULL: the runtime walks enclosing
8386+
// taskgroups to locate the matching task_reduction registration for
8387+
// `origPtr`. Mirrors the in_reduction handling on omp.taskloop.context.
8388+
ArrayRef<BlockArgument> inRedBlockArgs = argIface.getInReductionBlockArgs();
8389+
if (!inRedBlockArgs.empty()) {
8390+
llvm::OpenMPIRBuilder &ompB = *moduleTranslation.getOpenMPBuilder();
8391+
llvm::Module *m = moduleTranslation.getLLVMModule();
8392+
llvm::LLVMContext &llvmCtx = m->getContext();
8393+
uint32_t srcLocSize;
8394+
llvm::Constant *srcLocStr = ompB.getOrCreateDefaultSrcLocStr(srcLocSize);
8395+
llvm::Value *bodyIdent = ompB.getOrCreateIdent(srcLocStr, srcLocSize);
8396+
llvm::Function *gtidFn = ompB.getOrCreateRuntimeFunctionPtr(
8397+
llvm::omp::OMPRTL___kmpc_global_thread_num);
8398+
llvm::Value *bodyGtid =
8399+
builder.CreateCall(gtidFn, {bodyIdent}, "omp_global_thread_num");
8400+
llvm::FunctionCallee getThData = ompB.getOrCreateRuntimeFunction(
8401+
*m, llvm::omp::OMPRTL___kmpc_task_reduction_get_th_data);
8402+
llvm::Type *ptrTy = llvm::PointerType::getUnqual(llvmCtx);
8403+
llvm::Value *nullDesc = llvm::ConstantPointerNull::get(ptrTy);
8404+
for (auto [blockArg, origPtr] :
8405+
llvm::zip_equal(inRedBlockArgs, inRedOrigPtrs)) {
8406+
llvm::Value *priv = builder.CreateCall(
8407+
getThData, {bodyGtid, nullDesc, origPtr}, "omp.inred.priv");
8408+
moduleTranslation.mapValue(blockArg, priv);
8409+
}
8410+
}
8411+
83238412
LLVM::ModuleTranslation::SaveStack<OpenMPAllocStackFrame> frame(
83248413
moduleTranslation, allocaIP, deallocBlocks);
83258414
llvm::Expected<llvm::BasicBlock *> exitBlock = convertOmpOpRegions(

0 commit comments

Comments
 (0)