Skip to content

Commit 4c399c2

Browse files
committed
[Flang][OpenMP] Make implicitly captured scalars fully firstprivatized
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 3b5aff5 commit 4c399c2

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) {
@@ -552,8 +564,19 @@ void DataSharingProcessor::collectSymbols(
552564
};
553565

554566
auto shouldCollectSymbol = [&](const semantics::Symbol *sym) {
555-
if (collectImplicit)
567+
if (collectImplicit) {
568+
// If we're a combined construct with a target region, implicit
569+
// firstprivate captures, should only belong to the target region
570+
// and not be added/captured by later directives. Parallel regions
571+
// will likely want the same captures to be shared and for SIMD it's
572+
// illegal to have firstprivate clauses.
573+
if (isConstructWithTopLevelTarget(eval) && !isTargetPrivatization &&
574+
sym->test(semantics::Symbol::Flag::OmpFirstPrivate)) {
575+
return false;
576+
}
577+
556578
return sym->test(semantics::Symbol::Flag::OmpImplicit);
579+
}
557580

558581
if (collectPreDetermined)
559582
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
@@ -2473,6 +2473,33 @@ genSingleOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
24732473
queue, item, clauseOps);
24742474
}
24752475

2476+
static bool isDuplicateMappedSymbol(
2477+
const semantics::Symbol &sym,
2478+
const llvm::SetVector<const semantics::Symbol *> &privatizedSyms,
2479+
const llvm::SmallVectorImpl<const semantics::Symbol *> &hasDevSyms,
2480+
const llvm::SmallVectorImpl<const semantics::Symbol *> &mappedSyms) {
2481+
llvm::SmallVector<const semantics::Symbol *> concatSyms;
2482+
concatSyms.reserve(privatizedSyms.size() + hasDevSyms.size() +
2483+
mappedSyms.size());
2484+
concatSyms.append(privatizedSyms.begin(), privatizedSyms.end());
2485+
concatSyms.append(hasDevSyms.begin(), hasDevSyms.end());
2486+
concatSyms.append(mappedSyms.begin(), mappedSyms.end());
2487+
2488+
auto checkSymbol = [&](const semantics::Symbol &checkSym) {
2489+
return std::any_of(concatSyms.begin(), concatSyms.end(),
2490+
[&](auto v) { return v->GetUltimate() == checkSym; });
2491+
};
2492+
2493+
if (checkSymbol(sym))
2494+
return true;
2495+
2496+
const auto *hostAssoc{sym.detailsIf<semantics::HostAssocDetails>()};
2497+
if (hostAssoc && checkSymbol(hostAssoc->symbol()))
2498+
return true;
2499+
2500+
return checkSymbol(sym.GetUltimate());
2501+
}
2502+
24762503
static mlir::omp::TargetOp
24772504
genTargetOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
24782505
lower::StatementContext &stmtCtx,
@@ -2499,7 +2526,8 @@ genTargetOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
24992526
DataSharingProcessor dsp(converter, semaCtx, item->clauses, eval,
25002527
/*shouldCollectPreDeterminedSymbols=*/
25012528
lower::omp::isLastItemInQueue(item, queue),
2502-
/*useDelayedPrivatization=*/true, symTable);
2529+
/*useDelayedPrivatization=*/true, symTable,
2530+
/*isTargetPrivitization=*/true);
25032531
dsp.processStep1(&clauseOps);
25042532

25052533
// 5.8.1 Implicit Data-Mapping Attribute Rules
@@ -2508,17 +2536,6 @@ genTargetOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
25082536
// attribute clauses (neither data-sharing; e.g. `private`, nor `map`
25092537
// clauses).
25102538
auto captureImplicitMap = [&](const semantics::Symbol &sym) {
2511-
if (dsp.getAllSymbolsToPrivatize().contains(&sym))
2512-
return;
2513-
2514-
// Skip parameters/constants as they do not need to be mapped.
2515-
if (semantics::IsNamedConstant(sym))
2516-
return;
2517-
2518-
// These symbols are mapped individually in processHasDeviceAddr.
2519-
if (llvm::is_contained(hasDeviceAddrSyms, &sym))
2520-
return;
2521-
25222539
// Structure component symbols don't have bindings, and can only be
25232540
// explicitly mapped individually. If a member is captured implicitly
25242541
// we map the entirety of the derived type when we find its symbol.
@@ -2540,7 +2557,12 @@ genTargetOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
25402557
if (!converter.getSymbolAddress(sym))
25412558
return;
25422559

2543-
if (!llvm::is_contained(mapSyms, &sym)) {
2560+
// Skip parameters/constants as they do not need to be mapped.
2561+
if (semantics::IsNamedConstant(sym))
2562+
return;
2563+
2564+
if (!isDuplicateMappedSymbol(sym, dsp.getAllSymbolsToPrivatize(),
2565+
hasDeviceAddrSyms, mapSyms)) {
25442566
if (const auto *details =
25452567
sym.template detailsIf<semantics::HostAssocDetails>())
25462568
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
}
@@ -557,6 +566,7 @@ class OmpAttributeVisitor : DirectiveAttributeVisitor<llvm::omp::Directive> {
557566
ResolveOmpObjectList(x.v, Symbol::Flag::OmpExclusiveScan);
558567
return false;
559568
}
569+
void Post(const parser::OmpClause::Defaultmap &);
560570
void Post(const parser::OmpDefaultClause &);
561571
bool Pre(const parser::OmpClause::Shared &x) {
562572
ResolveOmpObjectList(x.v, Symbol::Flag::OmpShared);
@@ -810,6 +820,11 @@ class OmpAttributeVisitor : DirectiveAttributeVisitor<llvm::omp::Directive> {
810820
Symbol::Flag::OmpLastPrivate, Symbol::Flag::OmpReduction,
811821
Symbol::Flag::OmpLinear};
812822

823+
Symbol::Flags dataMappingAttributeFlags{Symbol::Flag::OmpMapTo,
824+
Symbol::Flag::OmpMapFrom, Symbol::Flag::OmpMapToFrom,
825+
Symbol::Flag::OmpMapStorage, Symbol::Flag::OmpMapDelete,
826+
Symbol::Flag::OmpIsDevicePtr, Symbol::Flag::OmpHasDeviceAddr};
827+
813828
Symbol::Flags privateDataSharingAttributeFlags{Symbol::Flag::OmpPrivate,
814829
Symbol::Flag::OmpFirstPrivate, Symbol::Flag::OmpLastPrivate};
815830

@@ -2214,6 +2229,28 @@ bool OmpAttributeVisitor::Pre(const parser::OpenMPAllocatorsConstruct &x) {
22142229
return true;
22152230
}
22162231

2232+
void OmpAttributeVisitor::Post(const parser::OmpClause::Defaultmap &x) {
2233+
using ImplicitBehavior = parser::OmpDefaultmapClause::ImplicitBehavior;
2234+
using VariableCategory = parser::OmpVariableCategory;
2235+
2236+
VariableCategory::Value varCategory;
2237+
ImplicitBehavior impBehavior;
2238+
2239+
if (!dirContext_.empty()) {
2240+
impBehavior = std::get<ImplicitBehavior>(x.v.t);
2241+
2242+
auto &modifiers{OmpGetModifiers(x.v)};
2243+
auto *maybeCategory{
2244+
OmpGetUniqueModifier<parser::OmpVariableCategory>(modifiers)};
2245+
if (maybeCategory)
2246+
varCategory = maybeCategory->v;
2247+
else
2248+
varCategory = VariableCategory::Value::All;
2249+
2250+
AddContextDefaultmapBehaviour(varCategory, impBehavior);
2251+
}
2252+
}
2253+
22172254
void OmpAttributeVisitor::Post(const parser::OmpDefaultClause &x) {
22182255
// The DEFAULT clause may also be used on METADIRECTIVE. In that case
22192256
// there is nothing to do.
@@ -2365,6 +2402,70 @@ static bool IsSymbolStaticStorageDuration(const Symbol &symbol) {
23652402
(ultSym.flags().test(Symbol::Flag::InCommonBlock));
23662403
}
23672404

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

24572558
bool taskGenDir = llvm::omp::taskGeneratingSet.test(dirContext.directive);
24582559
bool targetDir = llvm::omp::allTargetSet.test(dirContext.directive);
2459-
bool parallelDir = llvm::omp::allParallelSet.test(dirContext.directive);
2560+
bool parallelDir = llvm::omp::topParallelSet.test(dirContext.directive);
24602561
bool teamsDir = llvm::omp::allTeamsSet.test(dirContext.directive);
24612562
bool isStaticStorageDuration = IsSymbolStaticStorageDuration(*symbol);
24622563

@@ -2512,8 +2613,19 @@ void OmpAttributeVisitor::CreateImplicitSymbols(const Symbol *symbol) {
25122613
useLastDeclSymbol();
25132614
PRINT_IMPLICIT_RULE("3) enclosing context");
25142615
} else if (targetDir) {
2515-
// TODO 4) not mapped target variable -> firstprivate
2616+
// 4) not mapped target variable -> firstprivate
2617+
// - i.e. implicit, but meets OpenMP specification rules for
2618+
// firstprivate "promotion"
2619+
if (enableDelayedPrivatizationStaging &&
2620+
IsTargetCaptureImplicitlyFirstprivatizeable(*symbol, prevDSA,
2621+
dataSharingAttributeFlags, dataMappingAttributeFlags,
2622+
dirContext.defaultMap)) {
2623+
prevDSA.set(Symbol::Flag::OmpImplicit);
2624+
prevDSA.set(Symbol::Flag::OmpFirstPrivate);
2625+
makeSymbol(prevDSA);
2626+
}
25162627
dsa = prevDSA;
2628+
PRINT_IMPLICIT_RULE("4) not mapped target variable -> firstprivate");
25172629
} else if (taskGenDir) {
25182630
// TODO 5) dummy arg in orphaned taskgen construct -> firstprivate
25192631
if (prevDSA.test(Symbol::Flag::OmpShared) ||

0 commit comments

Comments
 (0)