Skip to content

Commit cc2a385

Browse files
authored
[Flang][OpenMP] Make implicitly captured scalars fully firstprivatized (#147442)
Currently, we indicate to the runtime that implicit scalar captures are firstprivate (via map and capture types), enough for the runtime trace to treat it as such, but we do not CodeGen the IR in such a way that we can take full advantage of this aspect of the OpenMP specification. This patch seeks to change that by applying the correct symbol flags (firstprivate/implicit) to the implicitly captured scalars within target regions, which then triggers the delayed privitization code generation for these symbols, bringing the code generation in-line with the explicit firstpriviate clause. Currently, similarly to the delayed privitization I have sheltered this segment of code behind the EnabledDelayedPrivitization flag, as without it, we'll trigger an compiler error for firstprivate not being supported any time we implicitly capture a scalar and try to firstprivitize it, in future when this flag is removed it can also be removed here. So, for now, you need to enable this via providing the compiler the flag on compilation of any programs.
1 parent 34c2ea3 commit cc2a385

14 files changed

+411
-84
lines changed

flang/lib/Lower/OpenMP/DataSharingProcessor.cpp

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,26 @@ bool DataSharingProcessor::OMPConstructSymbolVisitor::isSymbolDefineBy(
4343
[](const auto &functionParserNode) { return false; }});
4444
}
4545

46+
static bool isConstructWithTopLevelTarget(lower::pft::Evaluation &eval) {
47+
const auto *ompEval = eval.getIf<parser::OpenMPConstruct>();
48+
if (ompEval) {
49+
auto dir = parser::omp::GetOmpDirectiveName(ompEval).v;
50+
if (llvm::omp::topTargetSet.test(dir))
51+
return true;
52+
}
53+
return false;
54+
}
55+
4656
DataSharingProcessor::DataSharingProcessor(
4757
lower::AbstractConverter &converter, semantics::SemanticsContext &semaCtx,
4858
const List<Clause> &clauses, lower::pft::Evaluation &eval,
4959
bool shouldCollectPreDeterminedSymbols, bool useDelayedPrivatization,
50-
lower::SymMap &symTable)
60+
lower::SymMap &symTable, bool isTargetPrivatization)
5161
: converter(converter), semaCtx(semaCtx),
5262
firOpBuilder(converter.getFirOpBuilder()), clauses(clauses), eval(eval),
5363
shouldCollectPreDeterminedSymbols(shouldCollectPreDeterminedSymbols),
5464
useDelayedPrivatization(useDelayedPrivatization), symTable(symTable),
55-
visitor(semaCtx) {
65+
isTargetPrivatization(isTargetPrivatization), visitor(semaCtx) {
5666
eval.visit([&](const auto &functionParserNode) {
5767
parser::Walk(functionParserNode, visitor);
5868
});
@@ -62,10 +72,12 @@ DataSharingProcessor::DataSharingProcessor(lower::AbstractConverter &converter,
6272
semantics::SemanticsContext &semaCtx,
6373
lower::pft::Evaluation &eval,
6474
bool useDelayedPrivatization,
65-
lower::SymMap &symTable)
75+
lower::SymMap &symTable,
76+
bool isTargetPrivatization)
6677
: DataSharingProcessor(converter, semaCtx, {}, eval,
6778
/*shouldCollectPreDeterminedSymols=*/false,
68-
useDelayedPrivatization, symTable) {}
79+
useDelayedPrivatization, symTable,
80+
isTargetPrivatization) {}
6981

7082
void DataSharingProcessor::processStep1(
7183
mlir::omp::PrivateClauseOps *clauseOps) {
@@ -526,8 +538,19 @@ void DataSharingProcessor::collectSymbols(
526538
};
527539

528540
auto shouldCollectSymbol = [&](const semantics::Symbol *sym) {
529-
if (collectImplicit)
541+
if (collectImplicit) {
542+
// If we're a combined construct with a target region, implicit
543+
// firstprivate captures, should only belong to the target region
544+
// and not be added/captured by later directives. Parallel regions
545+
// will likely want the same captures to be shared and for SIMD it's
546+
// illegal to have firstprivate clauses.
547+
if (isConstructWithTopLevelTarget(eval) && !isTargetPrivatization &&
548+
sym->test(semantics::Symbol::Flag::OmpFirstPrivate)) {
549+
return false;
550+
}
551+
530552
return sym->test(semantics::Symbol::Flag::OmpImplicit);
553+
}
531554

532555
if (collectPreDetermined)
533556
return sym->test(semantics::Symbol::Flag::OmpPreDetermined);

flang/lib/Lower/OpenMP/DataSharingProcessor.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ class DataSharingProcessor {
9393
bool useDelayedPrivatization;
9494
llvm::SmallSet<const semantics::Symbol *, 16> mightHaveReadHostSym;
9595
lower::SymMap &symTable;
96+
bool isTargetPrivatization;
9697
OMPConstructSymbolVisitor visitor;
9798

9899
bool needBarrier();
@@ -130,12 +131,14 @@ class DataSharingProcessor {
130131
const List<Clause> &clauses,
131132
lower::pft::Evaluation &eval,
132133
bool shouldCollectPreDeterminedSymbols,
133-
bool useDelayedPrivatization, lower::SymMap &symTable);
134+
bool useDelayedPrivatization, lower::SymMap &symTable,
135+
bool isTargetPrivatization = false);
134136

135137
DataSharingProcessor(lower::AbstractConverter &converter,
136138
semantics::SemanticsContext &semaCtx,
137139
lower::pft::Evaluation &eval,
138-
bool useDelayedPrivatization, lower::SymMap &symTable);
140+
bool useDelayedPrivatization, lower::SymMap &symTable,
141+
bool isTargetPrivatization = false);
139142

140143
// Privatisation is split into two steps.
141144
// Step1 performs cloning of all privatisation clauses and copying for

flang/lib/Lower/OpenMP/OpenMP.cpp

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2466,6 +2466,33 @@ genSingleOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
24662466
queue, item, clauseOps);
24672467
}
24682468

2469+
static bool isDuplicateMappedSymbol(
2470+
const semantics::Symbol &sym,
2471+
const llvm::SetVector<const semantics::Symbol *> &privatizedSyms,
2472+
const llvm::SmallVectorImpl<const semantics::Symbol *> &hasDevSyms,
2473+
const llvm::SmallVectorImpl<const semantics::Symbol *> &mappedSyms) {
2474+
llvm::SmallVector<const semantics::Symbol *> concatSyms;
2475+
concatSyms.reserve(privatizedSyms.size() + hasDevSyms.size() +
2476+
mappedSyms.size());
2477+
concatSyms.append(privatizedSyms.begin(), privatizedSyms.end());
2478+
concatSyms.append(hasDevSyms.begin(), hasDevSyms.end());
2479+
concatSyms.append(mappedSyms.begin(), mappedSyms.end());
2480+
2481+
auto checkSymbol = [&](const semantics::Symbol &checkSym) {
2482+
return std::any_of(concatSyms.begin(), concatSyms.end(),
2483+
[&](auto v) { return v->GetUltimate() == checkSym; });
2484+
};
2485+
2486+
if (checkSymbol(sym))
2487+
return true;
2488+
2489+
const auto *hostAssoc{sym.detailsIf<semantics::HostAssocDetails>()};
2490+
if (hostAssoc && checkSymbol(hostAssoc->symbol()))
2491+
return true;
2492+
2493+
return checkSymbol(sym.GetUltimate());
2494+
}
2495+
24692496
static mlir::omp::TargetOp
24702497
genTargetOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
24712498
lower::StatementContext &stmtCtx,
@@ -2492,7 +2519,8 @@ genTargetOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
24922519
DataSharingProcessor dsp(converter, semaCtx, item->clauses, eval,
24932520
/*shouldCollectPreDeterminedSymbols=*/
24942521
lower::omp::isLastItemInQueue(item, queue),
2495-
/*useDelayedPrivatization=*/true, symTable);
2522+
/*useDelayedPrivatization=*/true, symTable,
2523+
/*isTargetPrivitization=*/true);
24962524
dsp.processStep1(&clauseOps);
24972525

24982526
// 5.8.1 Implicit Data-Mapping Attribute Rules
@@ -2501,17 +2529,6 @@ genTargetOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
25012529
// attribute clauses (neither data-sharing; e.g. `private`, nor `map`
25022530
// clauses).
25032531
auto captureImplicitMap = [&](const semantics::Symbol &sym) {
2504-
if (dsp.getAllSymbolsToPrivatize().contains(&sym))
2505-
return;
2506-
2507-
// Skip parameters/constants as they do not need to be mapped.
2508-
if (semantics::IsNamedConstant(sym))
2509-
return;
2510-
2511-
// These symbols are mapped individually in processHasDeviceAddr.
2512-
if (llvm::is_contained(hasDeviceAddrSyms, &sym))
2513-
return;
2514-
25152532
// Structure component symbols don't have bindings, and can only be
25162533
// explicitly mapped individually. If a member is captured implicitly
25172534
// we map the entirety of the derived type when we find its symbol.
@@ -2533,7 +2550,12 @@ genTargetOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
25332550
if (!converter.getSymbolAddress(sym))
25342551
return;
25352552

2536-
if (!llvm::is_contained(mapSyms, &sym)) {
2553+
// Skip parameters/constants as they do not need to be mapped.
2554+
if (semantics::IsNamedConstant(sym))
2555+
return;
2556+
2557+
if (!isDuplicateMappedSymbol(sym, dsp.getAllSymbolsToPrivatize(),
2558+
hasDeviceAddrSyms, mapSyms)) {
25372559
if (const auto *details =
25382560
sym.template detailsIf<semantics::HostAssocDetails>())
25392561
converter.copySymbolBinding(details->symbol(), sym);

flang/lib/Optimizer/OpenMP/MapsForPrivatizedSymbols.cpp

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,20 @@ class MapsForPrivatizedSymbolsPass
5555

5656
omp::MapInfoOp createMapInfo(Location loc, Value var,
5757
fir::FirOpBuilder &builder) {
58+
// Check if a value of type `type` can be passed to the kernel by value.
59+
// All kernel parameters are of pointer type, so if the value can be
60+
// represented inside of a pointer, then it can be passed by value.
61+
auto canPassByValue = [&](mlir::Type type) {
62+
const mlir::DataLayout &dl = builder.getDataLayout();
63+
mlir::Type ptrTy = mlir::LLVM::LLVMPointerType::get(builder.getContext());
64+
uint64_t ptrSize = dl.getTypeSize(ptrTy);
65+
uint64_t ptrAlign = dl.getTypePreferredAlignment(ptrTy);
66+
67+
auto [size, align] = fir::getTypeSizeAndAlignmentOrCrash(
68+
loc, type, dl, builder.getKindMap());
69+
return size <= ptrSize && align <= ptrAlign;
70+
};
71+
5872
uint64_t mapTypeTo = static_cast<
5973
std::underlying_type_t<llvm::omp::OpenMPOffloadMappingFlags>>(
6074
llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TO);
@@ -94,14 +108,22 @@ class MapsForPrivatizedSymbolsPass
94108
if (needsBoundsOps(varPtr))
95109
genBoundsOps(builder, varPtr, boundsOps);
96110

111+
mlir::omp::VariableCaptureKind captureKind =
112+
mlir::omp::VariableCaptureKind::ByRef;
113+
if (fir::isa_trivial(fir::unwrapRefType(varPtr.getType())) ||
114+
fir::isa_char(fir::unwrapRefType(varPtr.getType()))) {
115+
if (canPassByValue(fir::unwrapRefType(varPtr.getType()))) {
116+
captureKind = mlir::omp::VariableCaptureKind::ByCopy;
117+
}
118+
}
119+
97120
return omp::MapInfoOp::create(
98121
builder, loc, varPtr.getType(), varPtr,
99122
TypeAttr::get(llvm::cast<omp::PointerLikeType>(varPtr.getType())
100123
.getElementType()),
101124
builder.getIntegerAttr(builder.getIntegerType(64, /*isSigned=*/false),
102125
mapTypeTo),
103-
builder.getAttr<omp::VariableCaptureKindAttr>(
104-
omp::VariableCaptureKind::ByRef),
126+
builder.getAttr<omp::VariableCaptureKindAttr>(captureKind),
105127
/*varPtrPtr=*/Value{},
106128
/*members=*/SmallVector<Value>{},
107129
/*member_index=*/mlir::ArrayAttr{},

flang/lib/Semantics/resolve-directives.cpp

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "flang/Semantics/openmp-modifiers.h"
2525
#include "flang/Semantics/symbol.h"
2626
#include "flang/Semantics/tools.h"
27+
#include "flang/Support/Flags.h"
2728
#include "llvm/Frontend/OpenMP/OMP.h.inc"
2829
#include "llvm/Support/Debug.h"
2930
#include <list>
@@ -56,6 +57,10 @@ template <typename T> class DirectiveAttributeVisitor {
5657
Scope &scope;
5758
Symbol::Flag defaultDSA{Symbol::Flag::AccShared}; // TODOACC
5859
std::map<const Symbol *, Symbol::Flag> objectWithDSA;
60+
std::map<parser::OmpVariableCategory::Value,
61+
parser::OmpDefaultmapClause::ImplicitBehavior>
62+
defaultMap;
63+
5964
bool withinConstruct{false};
6065
std::int64_t associatedLoopLevel{0};
6166
};
@@ -80,6 +85,10 @@ template <typename T> class DirectiveAttributeVisitor {
8085
GetContext().directiveSource = dir;
8186
}
8287
Scope &currScope() { return GetContext().scope; }
88+
void AddContextDefaultmapBehaviour(parser::OmpVariableCategory::Value VarCat,
89+
parser::OmpDefaultmapClause::ImplicitBehavior ImpBehav) {
90+
GetContext().defaultMap[VarCat] = ImpBehav;
91+
}
8392
void SetContextDefaultDSA(Symbol::Flag flag) {
8493
GetContext().defaultDSA = flag;
8594
}
@@ -560,6 +569,7 @@ class OmpAttributeVisitor : DirectiveAttributeVisitor<llvm::omp::Directive> {
560569
ResolveOmpObjectList(x.v, Symbol::Flag::OmpExclusiveScan);
561570
return false;
562571
}
572+
void Post(const parser::OmpClause::Defaultmap &);
563573
void Post(const parser::OmpDefaultClause &);
564574
bool Pre(const parser::OmpClause::Shared &x) {
565575
ResolveOmpObjectList(x.v, Symbol::Flag::OmpShared);
@@ -813,6 +823,11 @@ class OmpAttributeVisitor : DirectiveAttributeVisitor<llvm::omp::Directive> {
813823
Symbol::Flag::OmpLastPrivate, Symbol::Flag::OmpReduction,
814824
Symbol::Flag::OmpLinear};
815825

826+
Symbol::Flags dataMappingAttributeFlags{Symbol::Flag::OmpMapTo,
827+
Symbol::Flag::OmpMapFrom, Symbol::Flag::OmpMapToFrom,
828+
Symbol::Flag::OmpMapStorage, Symbol::Flag::OmpMapDelete,
829+
Symbol::Flag::OmpIsDevicePtr, Symbol::Flag::OmpHasDeviceAddr};
830+
816831
Symbol::Flags privateDataSharingAttributeFlags{Symbol::Flag::OmpPrivate,
817832
Symbol::Flag::OmpFirstPrivate, Symbol::Flag::OmpLastPrivate};
818833

@@ -2223,6 +2238,28 @@ bool OmpAttributeVisitor::Pre(const parser::OpenMPAllocatorsConstruct &x) {
22232238
return true;
22242239
}
22252240

2241+
void OmpAttributeVisitor::Post(const parser::OmpClause::Defaultmap &x) {
2242+
using ImplicitBehavior = parser::OmpDefaultmapClause::ImplicitBehavior;
2243+
using VariableCategory = parser::OmpVariableCategory;
2244+
2245+
VariableCategory::Value varCategory;
2246+
ImplicitBehavior impBehavior;
2247+
2248+
if (!dirContext_.empty()) {
2249+
impBehavior = std::get<ImplicitBehavior>(x.v.t);
2250+
2251+
auto &modifiers{OmpGetModifiers(x.v)};
2252+
auto *maybeCategory{
2253+
OmpGetUniqueModifier<parser::OmpVariableCategory>(modifiers)};
2254+
if (maybeCategory)
2255+
varCategory = maybeCategory->v;
2256+
else
2257+
varCategory = VariableCategory::Value::All;
2258+
2259+
AddContextDefaultmapBehaviour(varCategory, impBehavior);
2260+
}
2261+
}
2262+
22262263
void OmpAttributeVisitor::Post(const parser::OmpDefaultClause &x) {
22272264
// The DEFAULT clause may also be used on METADIRECTIVE. In that case
22282265
// there is nothing to do.
@@ -2374,6 +2411,70 @@ static bool IsSymbolStaticStorageDuration(const Symbol &symbol) {
23742411
(ultSym.flags().test(Symbol::Flag::InCommonBlock));
23752412
}
23762413

2414+
static bool IsTargetCaptureImplicitlyFirstprivatizeable(const Symbol &symbol,
2415+
const Symbol::Flags &dsa, const Symbol::Flags &dataSharingAttributeFlags,
2416+
const Symbol::Flags &dataMappingAttributeFlags,
2417+
std::map<parser::OmpVariableCategory::Value,
2418+
parser::OmpDefaultmapClause::ImplicitBehavior>
2419+
defaultMap) {
2420+
// If a Defaultmap clause is present for the current target scope, and it has
2421+
// specified behaviour other than Firstprivate for scalars then we exit early,
2422+
// as it overrides the implicit Firstprivatization of scalars OpenMP rule.
2423+
if (!defaultMap.empty()) {
2424+
if (llvm::is_contained(
2425+
defaultMap, parser::OmpVariableCategory::Value::All) &&
2426+
defaultMap[parser::OmpVariableCategory::Value::All] !=
2427+
parser::OmpDefaultmapClause::ImplicitBehavior::Firstprivate) {
2428+
return false;
2429+
}
2430+
2431+
if (llvm::is_contained(
2432+
defaultMap, parser::OmpVariableCategory::Value::Scalar) &&
2433+
defaultMap[parser::OmpVariableCategory::Value::Scalar] !=
2434+
parser::OmpDefaultmapClause::ImplicitBehavior::Firstprivate) {
2435+
return false;
2436+
}
2437+
}
2438+
2439+
auto checkSymbol = [&](const Symbol &checkSym) {
2440+
// if we're associated with any other flags we skip implicit privitization
2441+
// for now. If we're an allocatable, pointer or declare target, we're not
2442+
// implicitly firstprivitizeable under OpenMP restrictions.
2443+
// TODO: Relax restriction as we progress privitization and further
2444+
// investigate the flags we can intermix with.
2445+
if (!(dsa & (dataSharingAttributeFlags | dataMappingAttributeFlags))
2446+
.none() ||
2447+
!checkSym.flags().none() || semantics::IsAssumedShape(checkSym) ||
2448+
semantics::IsAllocatableOrPointer(checkSym)) {
2449+
return false;
2450+
}
2451+
2452+
// It is default firstprivatizeable as far as the OpenMP specification is
2453+
// concerned if it is a non-array scalar type that has been implicitly
2454+
// captured in a target region
2455+
const auto *type{checkSym.GetType()};
2456+
if ((!checkSym.GetShape() || checkSym.GetShape()->empty()) &&
2457+
(type->category() ==
2458+
Fortran::semantics::DeclTypeSpec::Category::Numeric ||
2459+
type->category() ==
2460+
Fortran::semantics::DeclTypeSpec::Category::Logical ||
2461+
type->category() ==
2462+
Fortran::semantics::DeclTypeSpec::Category::Character)) {
2463+
return true;
2464+
}
2465+
return false;
2466+
};
2467+
2468+
if (checkSymbol(symbol)) {
2469+
const auto *hostAssoc{symbol.detailsIf<HostAssocDetails>()};
2470+
if (hostAssoc) {
2471+
return checkSymbol(hostAssoc->symbol());
2472+
}
2473+
return true;
2474+
}
2475+
return false;
2476+
}
2477+
23772478
void OmpAttributeVisitor::CreateImplicitSymbols(const Symbol *symbol) {
23782479
if (!IsPrivatizable(symbol)) {
23792480
return;
@@ -2465,7 +2566,7 @@ void OmpAttributeVisitor::CreateImplicitSymbols(const Symbol *symbol) {
24652566

24662567
bool taskGenDir = llvm::omp::taskGeneratingSet.test(dirContext.directive);
24672568
bool targetDir = llvm::omp::allTargetSet.test(dirContext.directive);
2468-
bool parallelDir = llvm::omp::allParallelSet.test(dirContext.directive);
2569+
bool parallelDir = llvm::omp::topParallelSet.test(dirContext.directive);
24692570
bool teamsDir = llvm::omp::allTeamsSet.test(dirContext.directive);
24702571
bool isStaticStorageDuration = IsSymbolStaticStorageDuration(*symbol);
24712572

@@ -2521,8 +2622,19 @@ void OmpAttributeVisitor::CreateImplicitSymbols(const Symbol *symbol) {
25212622
useLastDeclSymbol();
25222623
PRINT_IMPLICIT_RULE("3) enclosing context");
25232624
} else if (targetDir) {
2524-
// TODO 4) not mapped target variable -> firstprivate
2625+
// 4) not mapped target variable -> firstprivate
2626+
// - i.e. implicit, but meets OpenMP specification rules for
2627+
// firstprivate "promotion"
2628+
if (enableDelayedPrivatizationStaging &&
2629+
IsTargetCaptureImplicitlyFirstprivatizeable(*symbol, prevDSA,
2630+
dataSharingAttributeFlags, dataMappingAttributeFlags,
2631+
dirContext.defaultMap)) {
2632+
prevDSA.set(Symbol::Flag::OmpImplicit);
2633+
prevDSA.set(Symbol::Flag::OmpFirstPrivate);
2634+
makeSymbol(prevDSA);
2635+
}
25252636
dsa = prevDSA;
2637+
PRINT_IMPLICIT_RULE("4) not mapped target variable -> firstprivate");
25262638
} else if (taskGenDir) {
25272639
// TODO 5) dummy arg in orphaned taskgen construct -> firstprivate
25282640
if (prevDSA.test(Symbol::Flag::OmpShared) ||

0 commit comments

Comments
 (0)