| //===-- ClauseProcessor.cpp -------------------------------------*- C++ -*-===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // Coding style: https://mlir.llvm.org/getting_started/DeveloperGuide/ | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "ClauseProcessor.h" | |
| #include "Utils.h" | |
| #include "flang/Lower/ConvertCall.h" | |
| #include "flang/Lower/ConvertExprToHLFIR.h" | |
| #include "flang/Lower/OpenMP/Clauses.h" | |
| #include "flang/Lower/PFTBuilder.h" | |
| #include "flang/Lower/Support/ReductionProcessor.h" | |
| #include "flang/Optimizer/Builder/HLFIRTools.h" | |
| #include "flang/Optimizer/Dialect/FIRType.h" | |
| #include "flang/Optimizer/Support/InternalNames.h" | |
| #include "flang/Semantics/tools.h" | |
| #include "flang/Utils/OpenMP.h" | |
| #include "llvm/Frontend/OpenMP/OMP.h.inc" | |
| #include "llvm/Frontend/OpenMP/OMPIRBuilder.h" | |
| namespace Fortran { | |
| namespace lower { | |
| namespace omp { | |
| static void TodoLocators(mlir::Location loc, const omp::ObjectList &objects) { | |
| for (const omp::Object &object : objects) { | |
| if (auto &ref = object.ref()) { | |
| auto op = GetTopLevelOperation(*ref).first; | |
| if (op == evaluate::operation::Operator::Call) | |
| TODO(loc, "Function call locators are not supported yet"); | |
| } | |
| semantics::Symbol *symbol = object.sym(); | |
| if (symbol->test(semantics::Symbol::Flag::OmpReserved)) { | |
| std::string name = | |
| parser::ToLowerCaseLetters(object.sym()->name().ToString()); | |
| if (llvm::is_contained(llvm::omp::getReservedLocatorNames(), name)) { | |
| TODO(loc, "Reserved locators are not supported yet"); | |
| } | |
| } | |
| } | |
| } | |
| using ReductionModifier = | |
| Fortran::lower::omp::clause::Reduction::ReductionModifier; | |
| mlir::omp::ReductionModifier translateReductionModifier(ReductionModifier mod) { | |
| switch (mod) { | |
| case ReductionModifier::Default: | |
| return mlir::omp::ReductionModifier::defaultmod; | |
| case ReductionModifier::Inscan: | |
| return mlir::omp::ReductionModifier::inscan; | |
| case ReductionModifier::Task: | |
| return mlir::omp::ReductionModifier::task; | |
| } | |
| return mlir::omp::ReductionModifier::defaultmod; | |
| } | |
| static mlir::omp::ScheduleModifier | |
| translateScheduleModifier(const omp::clause::Schedule::OrderingModifier &m) { | |
| switch (m) { | |
| case omp::clause::Schedule::OrderingModifier::Monotonic: | |
| return mlir::omp::ScheduleModifier::monotonic; | |
| case omp::clause::Schedule::OrderingModifier::Nonmonotonic: | |
| return mlir::omp::ScheduleModifier::nonmonotonic; | |
| } | |
| return mlir::omp::ScheduleModifier::none; | |
| } | |
| static mlir::omp::ScheduleModifier | |
| getScheduleModifier(const omp::clause::Schedule &clause) { | |
| using Schedule = omp::clause::Schedule; | |
| const auto &modifier = | |
| std::get<std::optional<Schedule::OrderingModifier>>(clause.t); | |
| if (modifier) | |
| return translateScheduleModifier(*modifier); | |
| return mlir::omp::ScheduleModifier::none; | |
| } | |
| static mlir::omp::ScheduleModifier | |
| getSimdModifier(const omp::clause::Schedule &clause) { | |
| using Schedule = omp::clause::Schedule; | |
| const auto &modifier = | |
| std::get<std::optional<Schedule::ChunkModifier>>(clause.t); | |
| if (modifier && *modifier == Schedule::ChunkModifier::Simd) | |
| return mlir::omp::ScheduleModifier::simd; | |
| return mlir::omp::ScheduleModifier::none; | |
| } | |
| static void genAllocateClause( | |
| lower::AbstractConverter &converter, const omp::clause::Allocate &clause, | |
| llvm::SmallVectorImpl<mlir::Value> &allocatorOperands, | |
| llvm::SmallVectorImpl<mlir::Value> &allocateOperands, | |
| llvm::SmallVectorImpl<int64_t> &alignments, bool supportAlignment) { | |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); | |
| mlir::Location currentLocation = converter.getCurrentLocation(); | |
| lower::StatementContext stmtCtx; | |
| auto &objects = std::get<omp::ObjectList>(clause.t); | |
| using Allocate = omp::clause::Allocate; | |
| auto &align = std::get<std::optional<Allocate::AlignModifier>>(clause.t); | |
| if (align && !supportAlignment) | |
| TODO(currentLocation, "OmpAllocateClause ALIGN modifier"); | |
| if (align) { | |
| if (alignments.empty()) | |
| alignments.resize(allocateOperands.size(), 0); | |
| alignments.append(objects.size(), evaluate::ToInt64(align->v).value()); | |
| } else if (!alignments.empty()) { | |
| alignments.append(objects.size(), 0); | |
| } | |
| // Use a null handle to select the binding task's default allocator. | |
| using ComplexModifier = Allocate::AllocatorComplexModifier; | |
| if (auto &mod = std::get<std::optional<ComplexModifier>>(clause.t)) { | |
| mlir::Value operand = fir::getBase(converter.genExprValue(mod->v, stmtCtx)); | |
| allocatorOperands.append(objects.size(), operand); | |
| } else { | |
| mlir::Value operand = firOpBuilder.createIntegerConstant( | |
| currentLocation, firOpBuilder.getI32Type(), mlir::omp::kNullAllocator); | |
| allocatorOperands.append(objects.size(), operand); | |
| } | |
| genObjectList(objects, converter, allocateOperands); | |
| } | |
| static mlir::omp::ClauseBindKindAttr | |
| genBindKindAttr(fir::FirOpBuilder &firOpBuilder, | |
| const omp::clause::Bind &clause) { | |
| mlir::omp::ClauseBindKind bindKind; | |
| switch (clause.v) { | |
| case omp::clause::Bind::Binding::Teams: | |
| bindKind = mlir::omp::ClauseBindKind::Teams; | |
| break; | |
| case omp::clause::Bind::Binding::Parallel: | |
| bindKind = mlir::omp::ClauseBindKind::Parallel; | |
| break; | |
| case omp::clause::Bind::Binding::Thread: | |
| bindKind = mlir::omp::ClauseBindKind::Thread; | |
| break; | |
| } | |
| return mlir::omp::ClauseBindKindAttr::get(firOpBuilder.getContext(), | |
| bindKind); | |
| } | |
| static mlir::omp::ClauseProcBindKindAttr | |
| genProcBindKindAttr(fir::FirOpBuilder &firOpBuilder, | |
| const omp::clause::ProcBind &clause) { | |
| mlir::omp::ClauseProcBindKind procBindKind; | |
| switch (clause.v) { | |
| case omp::clause::ProcBind::AffinityPolicy::Master: | |
| procBindKind = mlir::omp::ClauseProcBindKind::Master; | |
| break; | |
| case omp::clause::ProcBind::AffinityPolicy::Close: | |
| procBindKind = mlir::omp::ClauseProcBindKind::Close; | |
| break; | |
| case omp::clause::ProcBind::AffinityPolicy::Spread: | |
| procBindKind = mlir::omp::ClauseProcBindKind::Spread; | |
| break; | |
| case omp::clause::ProcBind::AffinityPolicy::Primary: | |
| procBindKind = mlir::omp::ClauseProcBindKind::Primary; | |
| break; | |
| } | |
| return mlir::omp::ClauseProcBindKindAttr::get(firOpBuilder.getContext(), | |
| procBindKind); | |
| } | |
| static mlir::omp::ClauseTaskDependAttr | |
| genDependKindAttr(lower::AbstractConverter &converter, | |
| const omp::clause::DependenceType kind) { | |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); | |
| mlir::Location currentLocation = converter.getCurrentLocation(); | |
| mlir::omp::ClauseTaskDepend pbKind; | |
| switch (kind) { | |
| case omp::clause::DependenceType::In: | |
| pbKind = mlir::omp::ClauseTaskDepend::taskdependin; | |
| break; | |
| case omp::clause::DependenceType::Out: | |
| pbKind = mlir::omp::ClauseTaskDepend::taskdependout; | |
| break; | |
| case omp::clause::DependenceType::Inout: | |
| pbKind = mlir::omp::ClauseTaskDepend::taskdependinout; | |
| break; | |
| case omp::clause::DependenceType::Mutexinoutset: | |
| pbKind = mlir::omp::ClauseTaskDepend::taskdependmutexinoutset; | |
| break; | |
| case omp::clause::DependenceType::Inoutset: | |
| pbKind = mlir::omp::ClauseTaskDepend::taskdependinoutset; | |
| break; | |
| case omp::clause::DependenceType::Depobj: | |
| TODO(currentLocation, "DEPOBJ dependence-type"); | |
| break; | |
| case omp::clause::DependenceType::Sink: | |
| case omp::clause::DependenceType::Source: | |
| llvm_unreachable("unhandled parser task dependence type"); | |
| break; | |
| } | |
| return mlir::omp::ClauseTaskDependAttr::get(firOpBuilder.getContext(), | |
| pbKind); | |
| } | |
| static mlir::Value | |
| getIfClauseOperand(lower::AbstractConverter &converter, | |
| const omp::clause::If &clause, | |
| omp::clause::If::DirectiveNameModifier directiveName, | |
| mlir::Location clauseLocation) { | |
| // Only consider the clause if it's intended for the given directive. | |
| auto &directive = | |
| std::get<std::optional<omp::clause::If::DirectiveNameModifier>>(clause.t); | |
| if (directive && directive.value() != directiveName) | |
| return nullptr; | |
| lower::StatementContext stmtCtx; | |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); | |
| mlir::Value ifVal = fir::getBase( | |
| converter.genExprValue(std::get<omp::SomeExpr>(clause.t), stmtCtx)); | |
| return firOpBuilder.createConvert(clauseLocation, firOpBuilder.getI1Type(), | |
| ifVal); | |
| } | |
| template <typename SomeType, typename IteratorSpecT> | |
| static IteratorRange lowerIteratorRange( | |
| Fortran::lower::AbstractConverter &converter, const IteratorSpecT &itSpec, | |
| Fortran::lower::StatementContext &stmtCtx, mlir::Location loc) { | |
| auto &builder = converter.getFirOpBuilder(); | |
| using IdTy = | |
| Fortran::lower::omp::IdTyTemplate<Fortran::evaluate::Expr<SomeType>>; | |
| using ExprTy = Fortran::evaluate::Expr<SomeType>; | |
| using ObjTy = tomp::type::ObjectT<IdTy, ExprTy>; | |
| using RangeTy = tomp::type::RangeT<ExprTy>; | |
| const ObjTy &ivObj = std::get<1>(itSpec.t); | |
| const RangeTy &range = std::get<2>(itSpec.t); | |
| IteratorRange r; | |
| r.ivSym = ivObj.sym(); | |
| assert(r.ivSym && "expected iterator induction symbol"); | |
| const auto &lbExpr = std::get<0>(range.t); | |
| const auto &ubExpr = std::get<1>(range.t); | |
| const auto &stExpr = std::get<2>(range.t); | |
| mlir::Value lbVal = | |
| fir::getBase(converter.genExprValue(toEvExpr(lbExpr), stmtCtx)); | |
| mlir::Value ubVal = | |
| fir::getBase(converter.genExprValue(toEvExpr(ubExpr), stmtCtx)); | |
| // Fortran does not convert begin and end to the iterator's type (OpenMP 5.2 | |
| // section 3.2.6), so build the range in the widest operand type. | |
| mlir::Type ivTy = converter.genType(*r.ivSym); | |
| mlir::Value stVal = | |
| stExpr ? fir::getBase(converter.genExprValue(toEvExpr(*stExpr), stmtCtx)) | |
| : builder.createIntegerConstant(loc, ivTy, 1); | |
| unsigned width = mlir::cast<mlir::IntegerType>(ivTy).getWidth(); | |
| for (mlir::Value v : {lbVal, ubVal, stVal}) | |
| width = | |
| std::max(width, mlir::cast<mlir::IntegerType>(v.getType()).getWidth()); | |
| mlir::Type rangeTy = builder.getIntegerType(width); | |
| r.lb = builder.createConvert(loc, rangeTy, lbVal); | |
| r.ub = builder.createConvert(loc, rangeTy, ubVal); | |
| r.step = builder.createConvert(loc, rangeTy, stVal); | |
| return r; | |
| } | |
| template <typename BodyFn> | |
| static mlir::Value buildIteratorOp(Fortran::lower::AbstractConverter &converter, | |
| mlir::Location loc, mlir::Type iterTy, | |
| llvm::ArrayRef<IteratorRange> ranges, | |
| BodyFn &&bodyGen) { | |
| auto &builder = converter.getFirOpBuilder(); | |
| llvm::SmallVector<mlir::Value> lbs, ubs, steps; | |
| lbs.reserve(ranges.size()); | |
| ubs.reserve(ranges.size()); | |
| steps.reserve(ranges.size()); | |
| for (auto &r : ranges) { | |
| lbs.push_back(r.lb); | |
| ubs.push_back(r.ub); | |
| steps.push_back(r.step); | |
| } | |
| auto itOp = mlir::omp::IteratorOp::create( | |
| builder, loc, iterTy, mlir::ValueRange{lbs}, mlir::ValueRange{ubs}, | |
| mlir::ValueRange{steps}); | |
| mlir::OpBuilder::InsertionGuard guard(builder); | |
| mlir::Region ® = itOp.getRegion(); | |
| mlir::Block *body = builder.createBlock(®); | |
| Fortran::lower::SymMap &symMap = converter.getSymbolMap(); | |
| Fortran::lower::SymMapScope scope(symMap); | |
| for (const IteratorRange &r : ranges) { | |
| const Fortran::semantics::Symbol &ivSym = *r.ivSym; | |
| mlir::Value ivVal = body->addArgument(r.lb.getType(), loc); | |
| mlir::Type ivTy = converter.genType(ivSym); | |
| if (ivVal.getType() != ivTy) | |
| ivVal = fir::ConvertOp::create(builder, loc, ivTy, ivVal); | |
| // Materialize the iterator induction value in memory and declare it as an | |
| // HLFIR variable so that expressions referencing the iterator identifier | |
| // (e.g. array subscripts) can be lowered through the HLFIR expression path. | |
| // Give the temporary a name in the compiler-generated namespace | |
| // (NameUniquer::doGenerated). | |
| std::string ivName = fir::NameUniquer::doGenerated( | |
| converter.mangleName(ivSym) + ".omp.iter"); | |
| mlir::Value addr = builder.createTemporary(loc, ivTy, ivName); | |
| fir::StoreOp::create(builder, loc, ivVal, addr); | |
| fir::FortranVariableOpInterface declareOp = | |
| hlfir::genDeclare(loc, builder, fir::ExtendedValue{addr}, ivName, | |
| fir::FortranVariableFlagsAttr{}); | |
| symMap.addVariableDefinition(ivSym, declareOp, /*force=*/true); | |
| } | |
| mlir::omp::YieldOp::create(builder, loc, bodyGen()); | |
| return itOp.getResult(); | |
| } | |
| template <typename ClauseTuple> | |
| static llvm::SmallVector<IteratorRange> | |
| lowerIteratorRanges(const ClauseTuple &clause, | |
| Fortran::lower::AbstractConverter &converter, | |
| Fortran::lower::StatementContext &stmtCtx) { | |
| auto &iteratorModifier = | |
| std::get<std::optional<omp::clause::Iterator>>(clause.t); | |
| if (!iteratorModifier.has_value()) | |
| return {}; | |
| llvm::SmallVector<IteratorRange> iteratorRanges; | |
| mlir::Location clauseLocation = converter.getCurrentLocation(); | |
| const auto &iteratorModifierSpecs = *iteratorModifier; | |
| iteratorRanges.reserve(iteratorModifierSpecs.size()); | |
| for (const auto &itSpec : iteratorModifierSpecs) | |
| iteratorRanges.push_back(lowerIteratorRange<Fortran::evaluate::SomeType>( | |
| converter, itSpec, stmtCtx, clauseLocation)); | |
| return iteratorRanges; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // ClauseProcessor unique clauses | |
| //===----------------------------------------------------------------------===// | |
| bool ClauseProcessor::processAlign(mlir::omp::AlignClauseOps &result) const { | |
| if (auto *clause = findUniqueClause<omp::clause::Align>()) { | |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); | |
| const std::optional<std::int64_t> align = evaluate::ToInt64(clause->v); | |
| result.align = firOpBuilder.getI64IntegerAttr(*align); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processAllocator( | |
| lower::StatementContext &stmtCtx, | |
| mlir::omp::AllocatorClauseOps &result) const { | |
| if (auto *clause = findUniqueClause<omp::clause::Allocator>()) { | |
| result.allocator = fir::getBase(converter.genExprValue(clause->v, stmtCtx)); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processBind(mlir::omp::BindClauseOps &result) const { | |
| if (auto *clause = findUniqueClause<omp::clause::Bind>()) { | |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); | |
| result.bindKind = genBindKindAttr(firOpBuilder, *clause); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processCancelDirectiveName( | |
| mlir::omp::CancelDirectiveNameClauseOps &result) const { | |
| using ConstructType = mlir::omp::ClauseCancellationConstructType; | |
| mlir::MLIRContext *context = &converter.getMLIRContext(); | |
| ConstructType directive; | |
| if (auto *clause = findUniqueClause<omp::CancellationConstructType>()) { | |
| switch (clause->v) { | |
| case llvm::omp::OMP_CANCELLATION_CONSTRUCT_Parallel: | |
| directive = mlir::omp::ClauseCancellationConstructType::Parallel; | |
| break; | |
| case llvm::omp::OMP_CANCELLATION_CONSTRUCT_Loop: | |
| directive = mlir::omp::ClauseCancellationConstructType::Loop; | |
| break; | |
| case llvm::omp::OMP_CANCELLATION_CONSTRUCT_Sections: | |
| directive = mlir::omp::ClauseCancellationConstructType::Sections; | |
| break; | |
| case llvm::omp::OMP_CANCELLATION_CONSTRUCT_Taskgroup: | |
| directive = mlir::omp::ClauseCancellationConstructType::Taskgroup; | |
| break; | |
| case llvm::omp::OMP_CANCELLATION_CONSTRUCT_None: | |
| llvm_unreachable("OMP_CANCELLATION_CONSTRUCT_None"); | |
| break; | |
| } | |
| } else { | |
| llvm_unreachable("cancel construct missing cancellation construct type"); | |
| } | |
| result.cancelDirective = | |
| mlir::omp::ClauseCancellationConstructTypeAttr::get(context, directive); | |
| return true; | |
| } | |
| bool ClauseProcessor::processCollapse( | |
| mlir::Location currentLocation, lower::pft::Evaluation &eval, | |
| mlir::omp::LoopRelatedClauseOps &loopResult, | |
| mlir::omp::CollapseClauseOps &collapseResult, | |
| llvm::SmallVectorImpl<const semantics::Symbol *> &iv) const { | |
| int64_t numCollapse = collectLoopRelatedInfo(converter, currentLocation, eval, | |
| getNestedDoConstruct(eval), | |
| clauses, loopResult, iv); | |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); | |
| collapseResult.collapseNumLoops = firOpBuilder.getI64IntegerAttr(numCollapse); | |
| return numCollapse > 1; | |
| } | |
| bool ClauseProcessor::processDevice(lower::StatementContext &stmtCtx, | |
| mlir::omp::DeviceClauseOps &result) const { | |
| const parser::CharBlock *source = nullptr; | |
| if (auto *clause = findUniqueClause<omp::clause::Device>(&source)) { | |
| mlir::Location clauseLocation = converter.genLocation(*source); | |
| if (auto deviceModifier = | |
| std::get<std::optional<omp::clause::Device::DeviceModifier>>( | |
| clause->t)) { | |
| if (deviceModifier == omp::clause::Device::DeviceModifier::Ancestor) { | |
| TODO(clauseLocation, "OMPD_target Device Modifier Ancestor"); | |
| } | |
| } | |
| const auto &deviceExpr = std::get<omp::SomeExpr>(clause->t); | |
| result.device = fir::getBase(converter.genExprValue(deviceExpr, stmtCtx)); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processDeviceType( | |
| mlir::omp::DeviceTypeClauseOps &result) const { | |
| if (auto *clause = findUniqueClause<omp::clause::DeviceType>()) { | |
| // Case: declare target ... device_type(any | host | nohost) | |
| switch (clause->v) { | |
| case omp::clause::DeviceType::DeviceTypeDescription::Nohost: | |
| result.deviceType = mlir::omp::DeclareTargetDeviceType::nohost; | |
| break; | |
| case omp::clause::DeviceType::DeviceTypeDescription::Host: | |
| result.deviceType = mlir::omp::DeclareTargetDeviceType::host; | |
| break; | |
| case omp::clause::DeviceType::DeviceTypeDescription::Any: | |
| result.deviceType = mlir::omp::DeclareTargetDeviceType::any; | |
| break; | |
| } | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processDistSchedule( | |
| lower::StatementContext &stmtCtx, | |
| mlir::omp::DistScheduleClauseOps &result) const { | |
| if (auto *clause = findUniqueClause<omp::clause::DistSchedule>()) { | |
| result.distScheduleStatic = converter.getFirOpBuilder().getUnitAttr(); | |
| const auto &chunkSize = std::get<std::optional<ExprTy>>(clause->t); | |
| if (chunkSize) | |
| result.distScheduleChunkSize = | |
| fir::getBase(converter.genExprValue(*chunkSize, stmtCtx)); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processExclusive( | |
| mlir::Location currentLocation, | |
| mlir::omp::ExclusiveClauseOps &result) const { | |
| if (auto *clause = findUniqueClause<omp::clause::Exclusive>()) { | |
| for (const Object &object : clause->v) { | |
| const semantics::Symbol *symbol = object.sym(); | |
| mlir::Value symVal = converter.getSymbolAddress(*symbol); | |
| result.exclusiveVars.push_back(symVal); | |
| } | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processFilter(lower::StatementContext &stmtCtx, | |
| mlir::omp::FilterClauseOps &result) const { | |
| if (auto *clause = findUniqueClause<omp::clause::Filter>()) { | |
| result.filteredThreadId = | |
| fir::getBase(converter.genExprValue(clause->v, stmtCtx)); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processFinal(lower::StatementContext &stmtCtx, | |
| mlir::omp::FinalClauseOps &result) const { | |
| const parser::CharBlock *source = nullptr; | |
| if (auto *clause = findUniqueClause<omp::clause::Final>(&source)) { | |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); | |
| mlir::Location clauseLocation = converter.genLocation(*source); | |
| mlir::Value finalVal = | |
| fir::getBase(converter.genExprValue(clause->v, stmtCtx)); | |
| result.final = firOpBuilder.createConvert( | |
| clauseLocation, firOpBuilder.getI1Type(), finalVal); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processHint(mlir::omp::HintClauseOps &result) const { | |
| if (auto *clause = findUniqueClause<omp::clause::Hint>()) { | |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); | |
| int64_t hintValue = *evaluate::ToInt64(clause->v); | |
| result.hint = firOpBuilder.getI64IntegerAttr(hintValue); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processInclusive( | |
| mlir::Location currentLocation, | |
| mlir::omp::InclusiveClauseOps &result) const { | |
| if (auto *clause = findUniqueClause<omp::clause::Inclusive>()) { | |
| for (const Object &object : clause->v) { | |
| const semantics::Symbol *symbol = object.sym(); | |
| mlir::Value symVal = converter.getSymbolAddress(*symbol); | |
| result.inclusiveVars.push_back(symVal); | |
| } | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processInitializer( | |
| lower::SymMap &symMap, ReductionProcessor::GenInitValueCBTy &genInitValueCB, | |
| const parser::OmpStylizedInstance *parserInitInstance, | |
| unsigned instanceIdx) const { | |
| if (auto *clause = findUniqueClause<omp::clause::Initializer>()) { | |
| // Extract the typed assignment from the parser-level instance, if | |
| // the initializer is an assignment statement (as opposed to a call). | |
| const evaluate::Assignment *assign = nullptr; | |
| if (parserInitInstance) { | |
| const auto &instance = std::get<parser::OmpStylizedInstance::Instance>( | |
| parserInitInstance->t); | |
| if (const auto *assignStmt = | |
| std::get_if<parser::AssignmentStmt>(&instance.u)) { | |
| if (auto *wrapper = assignStmt->typedAssignment.get()) | |
| if (wrapper->v) | |
| assign = &*wrapper->v; | |
| } | |
| } | |
| // A multi-type declare reduction carries one initializer instance per | |
| // listed type (parallel to the combiner instances and typeNameList); select | |
| // the one for the type being lowered (index instanceIdx). Single-type is | |
| // index 0 of one. Capture instanceIdx by value: the callback runs later | |
| // (during createDeclareReductionHelper), after this parameter's lifetime | |
| // ends. | |
| assert(instanceIdx < clause->v.size() && | |
| "initializer instance index out of range"); | |
| genInitValueCB = [&, clause, assign, | |
| instanceIdx](fir::FirOpBuilder &builder, | |
| mlir::Location loc, mlir::Type type, | |
| mlir::Value moldArg, mlir::Value privArg) { | |
| lower::SymMapScope scope(symMap); | |
| mlir::Value ompPrivVar; | |
| const StylizedInstance &inst = clause->v[instanceIdx]; | |
| for (const Object &object : | |
| std::get<StylizedInstance::Variables>(inst.t)) { | |
| mlir::Value addr; | |
| std::string name = object.sym()->name().ToString(); | |
| mlir::Type moldArgType = moldArg.getType(); | |
| // Check for unsupported dynamic-length character reductions | |
| mlir::Type unwrappedType = fir::unwrapRefType(moldArgType); | |
| if (mlir::isa<fir::BoxCharType>(unwrappedType)) { | |
| TODO(loc, "OpenMP reduction allocation for dynamic length character"); | |
| } | |
| if (auto charTy = mlir::dyn_cast<fir::CharacterType>(unwrappedType)) { | |
| if (!charTy.hasConstantLen()) { | |
| TODO(loc, | |
| "OpenMP reduction allocation for dynamic length character"); | |
| } | |
| } | |
| // For by-ref reductions, omp_priv maps to privArg (the private | |
| // allocation) and omp_orig maps to moldArg (the original). | |
| if (name == "omp_priv" && privArg) { | |
| addr = privArg; | |
| } else if (fir::isa_ref_type(moldArgType)) { | |
| addr = moldArg; | |
| } else { | |
| addr = builder.createTemporary(loc, moldArgType); | |
| fir::StoreOp::create(builder, loc, moldArg, addr); | |
| } | |
| fir::FortranVariableFlagsEnum extraFlags = {}; | |
| fir::FortranVariableFlagsAttr attributes = | |
| Fortran::lower::translateSymbolAttributes( | |
| builder.getContext(), *object.sym(), extraFlags); | |
| // Get length parameters for types that need them (e.g., characters). | |
| // Note: DeclareOp requires exactly one type parameter for non-boxed | |
| // characters, unlike EmboxOp which doesn't allow them for constant-len. | |
| llvm::SmallVector<mlir::Value> typeParams; | |
| if (hlfir::isFortranEntity(addr)) { | |
| hlfir::genLengthParameters(loc, builder, hlfir::Entity{addr}, | |
| typeParams); | |
| } | |
| auto declareOp = hlfir::DeclareOp::create(builder, loc, addr, name, | |
| nullptr, typeParams, nullptr, | |
| nullptr, 0, attributes); | |
| if (name == "omp_priv") | |
| ompPrivVar = declareOp.getResult(0); | |
| symMap.addVariableDefinition(*object.sym(), declareOp); | |
| } | |
| // Lower the expression/function call | |
| lower::StatementContext stmtCtx; | |
| const semantics::SomeExpr &initExpr = | |
| std::get<StylizedInstance::Instance>(inst.t); | |
| mlir::Value result = common::visit( | |
| common::visitors{ | |
| [&](const evaluate::ProcedureRef &procRef) -> mlir::Value { | |
| convertCallToHLFIR(loc, converter, procRef, std::nullopt, | |
| symMap, stmtCtx); | |
| auto privVal = fir::LoadOp::create(builder, loc, ompPrivVar); | |
| return privVal; | |
| }, | |
| [&](const auto &expr) -> mlir::Value { | |
| // For by-ref reductions with a typed assignment, lower | |
| // the full assignment (both LHS and RHS) directly. | |
| // This handles both whole-variable (omp_priv = val) and | |
| // component-level (omp_priv%member = val) initializers. | |
| // Mirror the combiner pattern: dispatch on assign->u to | |
| // handle both intrinsic and user-defined assignment. | |
| if (privArg && assign) { | |
| lower::StatementContext assignCtx; | |
| hlfir::Entity rhs = lower::convertExprToHLFIR( | |
| loc, converter, assign->rhs, symMap, assignCtx); | |
| rhs = hlfir::loadTrivialScalar(loc, builder, rhs); | |
| hlfir::Entity lhs = lower::convertExprToHLFIR( | |
| loc, converter, assign->lhs, symMap, assignCtx); | |
| common::visit( | |
| common::visitors{ | |
| [&](const evaluate::Assignment::Intrinsic &) { | |
| hlfir::AssignOp::create(builder, loc, rhs, lhs); | |
| }, | |
| [&](const evaluate::ProcedureRef &procRef) { | |
| lower::convertUserDefinedAssignmentToHLFIR( | |
| loc, converter, procRef, lhs, rhs, symMap); | |
| }, | |
| [&](const auto &) { | |
| llvm_unreachable("Unexpected assignment type in " | |
| "reduction initializer"); | |
| }, | |
| }, | |
| assign->u); | |
| assignCtx.finalizeAndPop(); | |
| return mlir::Value{}; | |
| } | |
| mlir::Value exprResult = fir::getBase(convertExprToValue( | |
| loc, converter, initExpr, symMap, stmtCtx)); | |
| if (auto refType = llvm::dyn_cast<fir::ReferenceType>( | |
| exprResult.getType())) | |
| if (ompPrivVar.getType() == refType) | |
| exprResult = fir::LoadOp::create(builder, loc, exprResult); | |
| // The initializer expression may have a different but | |
| // convertible scalar type than the reduction. For example a | |
| // LOGICAL initializer (e.g. omp_priv = .false.) lowers to an | |
| // i1 while the reduction type is !fir.logical<4>. Convert so | |
| // the init region yields the reduction type, as the | |
| // omp.declare_reduction verifier requires. | |
| if (exprResult.getType() != type && | |
| fir::isa_trivial(exprResult.getType()) && | |
| fir::isa_trivial(type)) | |
| exprResult = builder.createConvert(loc, type, exprResult); | |
| return exprResult; | |
| }}, | |
| initExpr.u); | |
| stmtCtx.finalizeAndPop(); | |
| return result; | |
| }; | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processMergeable( | |
| mlir::omp::MergeableClauseOps &result) const { | |
| return markClauseOccurrence<omp::clause::Mergeable>(result.mergeable); | |
| } | |
| bool ClauseProcessor::processNogroup( | |
| mlir::omp::NogroupClauseOps &result) const { | |
| return markClauseOccurrence<omp::clause::Nogroup>(result.nogroup); | |
| } | |
| bool ClauseProcessor::processNowait(mlir::omp::NowaitClauseOps &result) const { | |
| return markClauseOccurrence<omp::clause::Nowait>(result.nowait); | |
| } | |
| bool ClauseProcessor::processNumTasks( | |
| lower::StatementContext &stmtCtx, | |
| mlir::omp::NumTasksClauseOps &result) const { | |
| using NumTasks = omp::clause::NumTasks; | |
| if (auto *clause = findUniqueClause<NumTasks>()) { | |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); | |
| mlir::MLIRContext *context = firOpBuilder.getContext(); | |
| const auto &modifier = | |
| std::get<std::optional<NumTasks::Prescriptiveness>>(clause->t); | |
| if (modifier && *modifier == NumTasks::Prescriptiveness::Strict) { | |
| result.numTasksMod = mlir::omp::ClauseNumTasksTypeAttr::get( | |
| context, mlir::omp::ClauseNumTasksType::Strict); | |
| } | |
| const auto &numtasksExpr = std::get<omp::SomeExpr>(clause->t); | |
| result.numTasks = | |
| fir::getBase(converter.genExprValue(numtasksExpr, stmtCtx)); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processSizes(StatementContext &stmtCtx, | |
| mlir::omp::SizesClauseOps &result) const { | |
| if (auto *clause = findUniqueClause<omp::clause::Sizes>()) { | |
| result.sizes.reserve(clause->v.size()); | |
| for (const ExprTy &vv : clause->v) | |
| result.sizes.push_back(fir::getBase(converter.genExprValue(vv, stmtCtx))); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processLooprange(StatementContext &stmtCtx, | |
| mlir::omp::LooprangeClauseOps &result, | |
| int64_t &count) const { | |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); | |
| if (auto *clause = findUniqueClause<omp::clause::Looprange>()) { | |
| int64_t first = evaluate::ToInt64(std::get<0>(clause->t)).value(); | |
| count = evaluate::ToInt64(std::get<1>(clause->t)).value(); | |
| result.first = firOpBuilder.getI64IntegerAttr(first); | |
| result.count = firOpBuilder.getI64IntegerAttr(count); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processNumTeams( | |
| lower::StatementContext &stmtCtx, | |
| mlir::omp::NumTeamsClauseOps &result) const { | |
| if (auto *clause = findUniqueClause<omp::clause::NumTeams>()) { | |
| // Structure: {LB?, [UB]} - single optional lower bound, list of upper | |
| // bounds | |
| auto &lowerBound = std::get<std::optional<ExprTy>>(clause->t); | |
| auto &upperBounds = | |
| std::get<omp::clause::NumTeams::UpperBoundList>(clause->t); | |
| assert(!upperBounds.empty()); | |
| // Extract optional lower bound | |
| if (lowerBound) { | |
| result.numTeamsLower = | |
| fir::getBase(converter.genExprValue(*lowerBound, stmtCtx)); | |
| } | |
| // Extract all upper bounds | |
| result.numTeamsUpperVars.reserve(upperBounds.size()); | |
| for (const auto &ub : upperBounds) { | |
| result.numTeamsUpperVars.push_back( | |
| fir::getBase(converter.genExprValue(ub, stmtCtx))); | |
| } | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processNumThreads( | |
| lower::StatementContext &stmtCtx, | |
| mlir::omp::NumThreadsClauseOps &result) const { | |
| if (auto *clause = findUniqueClause<omp::clause::NumThreads>()) { | |
| // OMPIRBuilder expects `NUM_THREADS` clause as a list of Values. | |
| for (const ExprTy &expr : clause->v) { | |
| result.numThreadsVars.push_back( | |
| fir::getBase(converter.genExprValue(expr, stmtCtx))); | |
| } | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processOrder(mlir::omp::OrderClauseOps &result) const { | |
| using Order = omp::clause::Order; | |
| if (auto *clause = findUniqueClause<Order>()) { | |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); | |
| result.order = mlir::omp::ClauseOrderKindAttr::get( | |
| firOpBuilder.getContext(), mlir::omp::ClauseOrderKind::Concurrent); | |
| const auto &modifier = | |
| std::get<std::optional<Order::OrderModifier>>(clause->t); | |
| if (modifier && *modifier == Order::OrderModifier::Unconstrained) { | |
| result.orderMod = mlir::omp::OrderModifierAttr::get( | |
| firOpBuilder.getContext(), mlir::omp::OrderModifier::unconstrained); | |
| } else { | |
| // "If order-modifier is not unconstrained, the behavior is as if the | |
| // reproducible modifier is present." | |
| result.orderMod = mlir::omp::OrderModifierAttr::get( | |
| firOpBuilder.getContext(), mlir::omp::OrderModifier::reproducible); | |
| } | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processOrdered( | |
| mlir::omp::OrderedClauseOps &result) const { | |
| if (auto *clause = findUniqueClause<omp::clause::Ordered>()) { | |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); | |
| int64_t orderedClauseValue = 0l; | |
| if (clause->v.has_value()) | |
| orderedClauseValue = *evaluate::ToInt64(*clause->v); | |
| result.ordered = firOpBuilder.getI64IntegerAttr(orderedClauseValue); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processFull() const { | |
| return findUniqueClause<omp::clause::Full>() != nullptr; | |
| } | |
| bool ClauseProcessor::processPartial(std::optional<int64_t> &result) const { | |
| if (auto *clause = findUniqueClause<omp::clause::Partial>()) { | |
| if (clause->v.has_value()) | |
| result = evaluate::ToInt64(*clause->v); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processPriority( | |
| lower::StatementContext &stmtCtx, | |
| mlir::omp::PriorityClauseOps &result) const { | |
| if (auto *clause = findUniqueClause<omp::clause::Priority>()) { | |
| result.priority = fir::getBase(converter.genExprValue(clause->v, stmtCtx)); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processDetach(mlir::omp::DetachClauseOps &result) const { | |
| if (auto *clause = findUniqueClause<omp::clause::Detach>()) { | |
| semantics::Symbol *sym = clause->v.sym(); | |
| mlir::Value symVal = converter.getSymbolAddress(*sym); | |
| result.eventHandle = symVal; | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processProcBind( | |
| mlir::omp::ProcBindClauseOps &result) const { | |
| if (auto *clause = findUniqueClause<omp::clause::ProcBind>()) { | |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); | |
| result.procBindKind = genProcBindKindAttr(firOpBuilder, *clause); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processTileSizes( | |
| lower::pft::Evaluation &eval, mlir::omp::LoopNestOperands &result) const { | |
| auto *ompCons{eval.getIf<parser::OpenMPConstruct>()}; | |
| collectTileSizesFromOpenMPConstruct(ompCons, result.tileSizes, semaCtx); | |
| return !result.tileSizes.empty(); | |
| } | |
| bool ClauseProcessor::processSafelen( | |
| mlir::omp::SafelenClauseOps &result) const { | |
| if (auto *clause = findUniqueClause<omp::clause::Safelen>()) { | |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); | |
| const std::optional<std::int64_t> safelenVal = evaluate::ToInt64(clause->v); | |
| result.safelen = firOpBuilder.getI64IntegerAttr(*safelenVal); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processSchedule( | |
| lower::StatementContext &stmtCtx, | |
| mlir::omp::ScheduleClauseOps &result) const { | |
| if (auto *clause = findUniqueClause<omp::clause::Schedule>()) { | |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); | |
| mlir::MLIRContext *context = firOpBuilder.getContext(); | |
| const auto &scheduleType = std::get<omp::clause::Schedule::Kind>(clause->t); | |
| mlir::omp::ClauseScheduleKind scheduleKind; | |
| switch (scheduleType) { | |
| case omp::clause::Schedule::Kind::Static: | |
| scheduleKind = mlir::omp::ClauseScheduleKind::Static; | |
| break; | |
| case omp::clause::Schedule::Kind::Dynamic: | |
| scheduleKind = mlir::omp::ClauseScheduleKind::Dynamic; | |
| break; | |
| case omp::clause::Schedule::Kind::Guided: | |
| scheduleKind = mlir::omp::ClauseScheduleKind::Guided; | |
| break; | |
| case omp::clause::Schedule::Kind::Auto: | |
| scheduleKind = mlir::omp::ClauseScheduleKind::Auto; | |
| break; | |
| case omp::clause::Schedule::Kind::Runtime: | |
| scheduleKind = mlir::omp::ClauseScheduleKind::Runtime; | |
| break; | |
| } | |
| result.scheduleKind = | |
| mlir::omp::ClauseScheduleKindAttr::get(context, scheduleKind); | |
| mlir::omp::ScheduleModifier scheduleMod = getScheduleModifier(*clause); | |
| if (scheduleMod != mlir::omp::ScheduleModifier::none) | |
| result.scheduleMod = | |
| mlir::omp::ScheduleModifierAttr::get(context, scheduleMod); | |
| if (getSimdModifier(*clause) != mlir::omp::ScheduleModifier::none) | |
| result.scheduleSimd = firOpBuilder.getUnitAttr(); | |
| if (const auto &chunkExpr = std::get<omp::MaybeExpr>(clause->t)) | |
| result.scheduleChunk = | |
| fir::getBase(converter.genExprValue(*chunkExpr, stmtCtx)); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processSimdlen( | |
| mlir::omp::SimdlenClauseOps &result) const { | |
| if (auto *clause = findUniqueClause<omp::clause::Simdlen>()) { | |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); | |
| const std::optional<std::int64_t> simdlenVal = evaluate::ToInt64(clause->v); | |
| result.simdlen = firOpBuilder.getI64IntegerAttr(*simdlenVal); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processSimd( | |
| mlir::omp::OrderedRegionOperands &result) const { | |
| return markClauseOccurrence<omp::clause::Simd>(result.parLevelSimd); | |
| } | |
| bool ClauseProcessor::processThreadLimit( | |
| lower::StatementContext &stmtCtx, | |
| mlir::omp::ThreadLimitClauseOps &result) const { | |
| if (auto *clause = findUniqueClause<omp::clause::ThreadLimit>()) { | |
| result.threadLimitVars.reserve(clause->v.size()); | |
| for (const ExprTy &vv : clause->v) | |
| result.threadLimitVars.push_back( | |
| fir::getBase(converter.genExprValue(vv, stmtCtx))); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processThreadset( | |
| mlir::omp::ThreadsetClauseOps &result) const { | |
| using Threadset = omp::clause::Threadset; | |
| if (auto *clause = findUniqueClause<Threadset>()) { | |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); | |
| mlir::omp::ThreadsetPolicy policy = | |
| clause->v == Threadset::ThreadsetPolicy::Omp_Pool | |
| ? mlir::omp::ThreadsetPolicy::omp_pool | |
| : mlir::omp::ThreadsetPolicy::omp_team; | |
| result.threadset = | |
| mlir::omp::ThreadsetPolicyAttr::get(firOpBuilder.getContext(), policy); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processUntied(mlir::omp::UntiedClauseOps &result) const { | |
| return markClauseOccurrence<omp::clause::Untied>(result.untied); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // ClauseProcessor repeatable clauses | |
| //===----------------------------------------------------------------------===// | |
| static llvm::StringMap<bool> getTargetFeatures(mlir::ModuleOp module) { | |
| llvm::StringMap<bool> featuresMap; | |
| llvm::SmallVector<llvm::StringRef> targetFeaturesVec; | |
| if (mlir::LLVM::TargetFeaturesAttr features = | |
| fir::getTargetFeatures(module)) { | |
| llvm::ArrayRef<mlir::StringAttr> featureAttrs = features.getFeatures(); | |
| for (auto &featureAttr : featureAttrs) { | |
| llvm::StringRef featureKeyString = featureAttr.strref(); | |
| featuresMap[featureKeyString.substr(1)] = (featureKeyString[0] == '+'); | |
| } | |
| } | |
| return featuresMap; | |
| } | |
| // Rank includes earlier array parts, but not arrays in scalar subscripts. | |
| static bool hasArrayParent(const evaluate::DataRef &dataRef) { | |
| const auto *component = std::get_if<evaluate::Component>(&dataRef.u); | |
| if (const auto *arrayRef = std::get_if<evaluate::ArrayRef>(&dataRef.u)) | |
| component = arrayRef->base().UnwrapComponent(); | |
| return component && component->base().Rank() > 0; | |
| } | |
| bool ClauseProcessor::processAffinity( | |
| mlir::omp::AffinityClauseOps &result) const { | |
| return findRepeatableClause<omp::clause::Affinity>( | |
| [&](const omp::clause::Affinity &clause, const parser::CharBlock &) { | |
| const auto &objects = std::get<omp::ObjectList>(clause.t); | |
| lower::StatementContext stmtCtx; | |
| auto &builder = converter.getFirOpBuilder(); | |
| auto &context = converter.getMLIRContext(); | |
| mlir::Location clauseLocation = converter.getCurrentLocation(); | |
| mlir::Type refI8Ty = fir::ReferenceType::get(builder.getIntegerType(8)); | |
| mlir::Type entryTy = mlir::omp::AffinityEntryType::get( | |
| &context, refI8Ty, builder.getI64Type()); | |
| mlir::Type iterTy = | |
| mlir::omp::IteratedType::get(&converter.getMLIRContext(), entryTy); | |
| auto makeAffinityEntry = [&](fir::FirOpBuilder &b, mlir::Location l, | |
| mlir::Type entryTy, mlir::Value addr, | |
| mlir::Value len) -> mlir::Value { | |
| mlir::Value addrI8 = fir::ConvertOp::create(b, l, refI8Ty, addr); | |
| return mlir::omp::AffinityEntryOp::create(b, l, entryTy, addrI8, len) | |
| .getResult(); | |
| }; | |
| auto iteratorRanges = lowerIteratorRanges(clause, converter, stmtCtx); | |
| TodoLocators(clauseLocation, objects); | |
| for (const omp::Object &object : objects) { | |
| if (!object.ref()) | |
| continue; | |
| if (evaluate::HasVectorSubscript(*object.ref())) | |
| TODO(clauseLocation, "vector subscript in AFFINITY clause"); | |
| if (evaluate::ExtractSubstring(*object.ref())) | |
| TODO(clauseLocation, "substring in AFFINITY clause"); | |
| if (evaluate::ExtractComplexPart(*object.ref())) | |
| TODO(clauseLocation, "complex part in AFFINITY clause"); | |
| if (auto dataRef = evaluate::ExtractDataRef(*object.ref())) | |
| if (hasArrayParent(*dataRef)) | |
| TODO(clauseLocation, "array-valued parent in AFFINITY clause"); | |
| } | |
| auto genEntry = [&](const omp::Object &object, | |
| lower::StatementContext &localStmtCtx) { | |
| mlir::Value addr = | |
| genAffinityAddr(converter, object, localStmtCtx, clauseLocation); | |
| llvm::SmallVector<mlir::Value> bounds; | |
| std::stringstream asFortran; | |
| fir::factory::AddrAndBoundsInfo info = | |
| lower::gatherDataOperandAddrAndBounds<mlir::omp::MapBoundsOp, | |
| mlir::omp::MapBoundsType>( | |
| converter, builder, semaCtx, localStmtCtx, *object.sym(), | |
| object.ref(), clauseLocation, asFortran, bounds, | |
| treatIndexAsSection); | |
| hlfir::Entity entity{info.addr}; | |
| mlir::Value len = | |
| object.ref() && object.ref()->Rank() == 0 | |
| ? genElementSizeInBytes(builder, clauseLocation, | |
| builder.getDataLayout(), entity) | |
| : genAffinityLen(builder, clauseLocation, | |
| builder.getDataLayout(), entity, bounds); | |
| return makeAffinityEntry(builder, clauseLocation, entryTy, addr, len); | |
| }; | |
| for (const omp::Object &object : objects) { | |
| llvm::SmallVector<IteratorRange> objectRanges = | |
| getIteratorRangesForObject(object, iteratorRanges); | |
| if (!objectRanges.empty()) { | |
| mlir::Value iterHandle = | |
| buildIteratorOp(converter, clauseLocation, iterTy, objectRanges, | |
| [&]() -> mlir::Value { | |
| lower::StatementContext iterStmtCtx; | |
| return genEntry(object, iterStmtCtx); | |
| }); | |
| result.iterated.push_back(iterHandle); | |
| } else { | |
| result.affinityVars.push_back(genEntry(object, stmtCtx)); | |
| } | |
| } | |
| return true; | |
| }); | |
| } | |
| static void | |
| addAlignedClause(lower::AbstractConverter &converter, | |
| const omp::clause::Aligned &clause, | |
| llvm::SmallVectorImpl<mlir::Value> &alignedVars, | |
| llvm::SmallVectorImpl<mlir::Attribute> &alignments) { | |
| using Aligned = omp::clause::Aligned; | |
| lower::StatementContext stmtCtx; | |
| mlir::IntegerAttr alignmentValueAttr; | |
| int64_t alignment = 0; | |
| fir::FirOpBuilder &builder = converter.getFirOpBuilder(); | |
| if (auto &alignmentValueParserExpr = | |
| std::get<std::optional<Aligned::Alignment>>(clause.t)) { | |
| mlir::Value operand = fir::getBase( | |
| converter.genExprValue(*alignmentValueParserExpr, stmtCtx)); | |
| std::optional<llvm::APInt> constantAlignment = | |
| fir::getIntIfConstant(operand); | |
| if (!constantAlignment) | |
| fir::emitFatalError(operand.getLoc(), "alignment must be a constant"); | |
| std::optional<std::int64_t> constantAlignment64 = | |
| constantAlignment->trySExtValue(); | |
| if (!constantAlignment64) | |
| fir::emitFatalError(operand.getLoc(), | |
| "alignment does not fit in a 64-bit integer"); | |
| alignment = *constantAlignment64; | |
| } else { | |
| llvm::StringMap<bool> featuresMap = getTargetFeatures(builder.getModule()); | |
| llvm::Triple triple = fir::getTargetTriple(builder.getModule()); | |
| alignment = | |
| llvm::OpenMPIRBuilder::getOpenMPDefaultSimdAlign(triple, featuresMap); | |
| } | |
| // The default alignment for some targets is equal to 0. | |
| // Do not generate alignment assumption if alignment is less than or equal to | |
| // 0 or not a power of two | |
| if (alignment > 0 && ((alignment & (alignment - 1)) == 0)) { | |
| auto &objects = std::get<omp::ObjectList>(clause.t); | |
| if (!objects.empty()) | |
| genObjectList(objects, converter, alignedVars); | |
| alignmentValueAttr = builder.getI64IntegerAttr(alignment); | |
| // All the list items in a aligned clause will have same alignment | |
| for (std::size_t i = 0; i < objects.size(); i++) | |
| alignments.push_back(alignmentValueAttr); | |
| } | |
| } | |
| bool ClauseProcessor::processAligned( | |
| mlir::omp::AlignedClauseOps &result) const { | |
| return findRepeatableClause<omp::clause::Aligned>( | |
| [&](const omp::clause::Aligned &clause, const parser::CharBlock &) { | |
| addAlignedClause(converter, clause, result.alignedVars, | |
| result.alignments); | |
| }); | |
| } | |
| bool ClauseProcessor::processAllocate(mlir::omp::AllocateClauseOps &result, | |
| bool supportAlignment) const { | |
| return findRepeatableClause<omp::clause::Allocate>( | |
| [&](const omp::clause::Allocate &clause, const parser::CharBlock &) { | |
| genAllocateClause(converter, clause, result.allocatorVars, | |
| result.allocateVars, result.allocateAlignments, | |
| supportAlignment); | |
| }); | |
| } | |
| bool ClauseProcessor::processCopyin() const { | |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); | |
| mlir::OpBuilder::InsertPoint insPt = firOpBuilder.saveInsertionPoint(); | |
| firOpBuilder.setInsertionPointToStart(firOpBuilder.getAllocaBlock()); | |
| auto checkAndCopyHostAssociateVar = | |
| [&](semantics::Symbol *sym, | |
| mlir::OpBuilder::InsertPoint *copyAssignIP = nullptr) { | |
| assert(sym->has<semantics::HostAssocDetails>() && | |
| "No host-association found"); | |
| if (converter.isPresentShallowLookup(*sym)) | |
| converter.copyHostAssociateVar(*sym, copyAssignIP); | |
| }; | |
| bool hasCopyin = findRepeatableClause<omp::clause::Copyin>( | |
| [&](const omp::clause::Copyin &clause, const parser::CharBlock &) { | |
| for (const omp::Object &object : clause.v) { | |
| semantics::Symbol *sym = object.sym(); | |
| assert(sym && "Expecting symbol"); | |
| if (const auto *commonDetails = | |
| sym->detailsIf<semantics::CommonBlockDetails>()) { | |
| for (const auto &mem : commonDetails->objects()) | |
| checkAndCopyHostAssociateVar(&*mem, &insPt); | |
| break; | |
| } | |
| assert(sym->has<semantics::HostAssocDetails>() && | |
| "No host-association found"); | |
| checkAndCopyHostAssociateVar(sym); | |
| } | |
| }); | |
| // [OMP 5.0, 2.19.6.1] The copy is done after the team is formed and prior to | |
| // the execution of the associated structured block. Emit implicit barrier to | |
| // synchronize threads and avoid data races on propagation master's thread | |
| // values of threadprivate variables to local instances of that variables of | |
| // all other implicit threads. | |
| // All copies are inserted at either "insPt" (i.e. immediately before it), | |
| // or at some earlier point (as determined by "copyHostAssociateVar"). | |
| // Unless the insertion point is given to "copyHostAssociateVar" explicitly, | |
| // it will not restore the builder's insertion point. Since the copies may be | |
| // inserted in any order (not following the execution order), make sure the | |
| // barrier is inserted following all of them. | |
| firOpBuilder.restoreInsertionPoint(insPt); | |
| if (hasCopyin) | |
| mlir::omp::BarrierOp::create(firOpBuilder, converter.getCurrentLocation()); | |
| return hasCopyin; | |
| } | |
| /// Class that extracts information from the specified type. | |
| class TypeInfo { | |
| public: | |
| TypeInfo(mlir::Type ty) { typeScan(ty); } | |
| // Returns the length of character types. | |
| std::optional<fir::CharacterType::LenType> getCharLength() const { | |
| return charLen; | |
| } | |
| // Returns the shape of array types. | |
| llvm::ArrayRef<int64_t> getShape() const { return shape; } | |
| // Is the type inside a box? | |
| bool isBox() const { return inBox; } | |
| bool isBoxChar() const { return inBoxChar; } | |
| private: | |
| void typeScan(mlir::Type type); | |
| std::optional<fir::CharacterType::LenType> charLen; | |
| llvm::SmallVector<int64_t> shape; | |
| bool inBox = false; | |
| bool inBoxChar = false; | |
| }; | |
| void TypeInfo::typeScan(mlir::Type ty) { | |
| if (auto sty = mlir::dyn_cast<fir::SequenceType>(ty)) { | |
| assert(shape.empty() && !sty.getShape().empty()); | |
| shape = llvm::SmallVector<int64_t>(sty.getShape()); | |
| typeScan(sty.getEleTy()); | |
| } else if (auto bty = mlir::dyn_cast<fir::BoxType>(ty)) { | |
| inBox = true; | |
| typeScan(bty.getEleTy()); | |
| } else if (auto cty = mlir::dyn_cast<fir::ClassType>(ty)) { | |
| inBox = true; | |
| typeScan(cty.getEleTy()); | |
| } else if (auto cty = mlir::dyn_cast<fir::CharacterType>(ty)) { | |
| charLen = cty.getLen(); | |
| } else if (auto cty = mlir::dyn_cast<fir::BoxCharType>(ty)) { | |
| inBoxChar = true; | |
| typeScan(cty.getEleTy()); | |
| } else if (auto hty = mlir::dyn_cast<fir::HeapType>(ty)) { | |
| typeScan(hty.getEleTy()); | |
| } else if (auto pty = mlir::dyn_cast<fir::PointerType>(ty)) { | |
| typeScan(pty.getEleTy()); | |
| } else { | |
| // The scan ends when reaching any built-in, record or boxproc type. | |
| // A `none` element type is reached for unlimited polymorphic entities | |
| // (e.g. `class(*)`), which are always inside a box; the copy is then | |
| // performed through the descriptor, so no scalar type info is needed. | |
| assert(ty.isIntOrIndexOrFloat() || mlir::isa<mlir::ComplexType>(ty) || | |
| mlir::isa<fir::LogicalType>(ty) || mlir::isa<fir::RecordType>(ty) || | |
| mlir::isa<fir::BoxProcType>(ty) || | |
| (inBox && mlir::isa<mlir::NoneType>(ty))); | |
| } | |
| } | |
| // Create a function that performs a copy between two variables, compatible | |
| // with their types and attributes. | |
| static mlir::func::FuncOp | |
| createCopyFunc(mlir::Location loc, lower::AbstractConverter &converter, | |
| mlir::Type varType, fir::FortranVariableFlagsEnum varAttrs) { | |
| fir::FirOpBuilder &builder = converter.getFirOpBuilder(); | |
| mlir::ModuleOp module = builder.getModule(); | |
| mlir::Type eleTy = fir::unwrapRefType(varType); | |
| TypeInfo typeInfo(eleTy); | |
| std::string copyFuncName = | |
| fir::getTypeAsString(varType, builder.getKindMap(), "_copy"); | |
| if (auto decl = module.lookupSymbol<mlir::func::FuncOp>(copyFuncName)) | |
| return decl; | |
| // create function | |
| mlir::OpBuilder::InsertionGuard guard(builder); | |
| mlir::OpBuilder modBuilder(module.getBodyRegion()); | |
| llvm::SmallVector<mlir::Type> argsTy = {varType, varType}; | |
| auto funcType = mlir::FunctionType::get(builder.getContext(), argsTy, {}); | |
| mlir::func::FuncOp funcOp = | |
| mlir::func::FuncOp::create(modBuilder, loc, copyFuncName, funcType); | |
| funcOp.setVisibility(mlir::SymbolTable::Visibility::Private); | |
| fir::factory::setInternalLinkage(funcOp); | |
| builder.createBlock(&funcOp.getRegion(), funcOp.getRegion().end(), argsTy, | |
| {loc, loc}); | |
| builder.setInsertionPointToStart(&funcOp.getRegion().back()); | |
| // generate body | |
| fir::FortranVariableFlagsAttr attrs; | |
| if (varAttrs != fir::FortranVariableFlagsEnum::None) | |
| attrs = fir::FortranVariableFlagsAttr::get(builder.getContext(), varAttrs); | |
| mlir::Value shape; | |
| if (!typeInfo.isBox() && !typeInfo.getShape().empty()) { | |
| llvm::SmallVector<mlir::Value> extents; | |
| for (auto extent : typeInfo.getShape()) | |
| extents.push_back( | |
| builder.createIntegerConstant(loc, builder.getIndexType(), extent)); | |
| shape = fir::ShapeOp::create(builder, loc, extents); | |
| } | |
| mlir::Value dst = funcOp.getArgument(0); | |
| mlir::Value src = funcOp.getArgument(1); | |
| llvm::SmallVector<mlir::Value> typeparams; | |
| if (typeInfo.isBoxChar()) { | |
| // fir.boxchar will be passed here as fir.ref<fir.boxchar> | |
| auto loadDst = fir::LoadOp::create(builder, loc, dst); | |
| auto loadSrc = fir::LoadOp::create(builder, loc, src); | |
| // get the actual fir.ref<fir.char> type | |
| mlir::Type refType = | |
| fir::ReferenceType::get(mlir::cast<fir::BoxCharType>(eleTy).getEleTy()); | |
| auto unboxedDst = fir::UnboxCharOp::create(builder, loc, refType, | |
| builder.getIndexType(), loadDst); | |
| auto unboxedSrc = fir::UnboxCharOp::create(builder, loc, refType, | |
| builder.getIndexType(), loadSrc); | |
| // Add length to type parameters | |
| typeparams.push_back(unboxedDst.getResult(1)); | |
| dst = unboxedDst.getResult(0); | |
| src = unboxedSrc.getResult(0); | |
| } else if (typeInfo.getCharLength().has_value()) { | |
| mlir::Value charLen = builder.createIntegerConstant( | |
| loc, builder.getCharacterLengthType(), *typeInfo.getCharLength()); | |
| typeparams.push_back(charLen); | |
| } | |
| auto declDst = hlfir::DeclareOp::create( | |
| builder, loc, dst, copyFuncName + "_dst", shape, typeparams, | |
| /*dummy_scope=*/nullptr, /*storage=*/nullptr, | |
| /*storage_offset=*/0, attrs); | |
| auto declSrc = hlfir::DeclareOp::create( | |
| builder, loc, src, copyFuncName + "_src", shape, typeparams, | |
| /*dummy_scope=*/nullptr, /*storage=*/nullptr, | |
| /*storage_offset=*/0, attrs); | |
| converter.copyVar(loc, declDst.getBase(), declSrc.getBase(), varAttrs); | |
| mlir::func::ReturnOp::create(builder, loc); | |
| return funcOp; | |
| } | |
| bool ClauseProcessor::processCopyprivate( | |
| mlir::Location currentLocation, | |
| mlir::omp::CopyprivateClauseOps &result) const { | |
| auto addCopyPrivateVar = [&](semantics::Symbol *sym) { | |
| mlir::Value symVal = converter.getSymbolAddress(*sym); | |
| auto declOp = symVal.getDefiningOp<hlfir::DeclareOp>(); | |
| if (!declOp) | |
| fir::emitFatalError(currentLocation, | |
| "COPYPRIVATE is supported only in HLFIR mode"); | |
| symVal = declOp.getBase(); | |
| mlir::Type symType = symVal.getType(); | |
| fir::FortranVariableFlagsEnum attrs = | |
| declOp.getFortranAttrs().has_value() | |
| ? *declOp.getFortranAttrs() | |
| : fir::FortranVariableFlagsEnum::None; | |
| mlir::Value cpVar = symVal; | |
| // CopyPrivate variables must be passed by reference. However, in the case | |
| // of assumed shapes/vla the type is not a !fir.ref, but a !fir.box. | |
| // In the case of character types, the passed in type can also be | |
| // !fir.boxchar. In these cases to retrieve the appropriate | |
| // !fir.ref<!fir.box<...>> or !fir.ref<!fir.boxchar<..>> to access the data | |
| // we need we must perform an alloca and then store to it and retrieve the | |
| // data from the new alloca. | |
| if (mlir::isa<fir::BaseBoxType>(symType) || | |
| mlir::isa<fir::BoxCharType>(symType)) { | |
| fir::FirOpBuilder &builder = converter.getFirOpBuilder(); | |
| auto alloca = fir::AllocaOp::create(builder, currentLocation, symType); | |
| fir::StoreOp::create(builder, currentLocation, symVal, alloca); | |
| cpVar = alloca; | |
| } | |
| result.copyprivateVars.push_back(cpVar); | |
| mlir::func::FuncOp funcOp = | |
| createCopyFunc(currentLocation, converter, cpVar.getType(), attrs); | |
| result.copyprivateSyms.push_back(mlir::SymbolRefAttr::get(funcOp)); | |
| }; | |
| bool hasCopyPrivate = findRepeatableClause<clause::Copyprivate>( | |
| [&](const clause::Copyprivate &clause, const parser::CharBlock &) { | |
| for (const Object &object : clause.v) { | |
| semantics::Symbol *sym = object.sym(); | |
| if (const auto *commonDetails = | |
| sym->detailsIf<semantics::CommonBlockDetails>()) { | |
| for (const auto &mem : commonDetails->objects()) | |
| addCopyPrivateVar(&*mem); | |
| break; | |
| } | |
| addCopyPrivateVar(sym); | |
| } | |
| }); | |
| return hasCopyPrivate; | |
| } | |
| template <typename T> | |
| static bool isVectorSubscript(const evaluate::Expr<T> &expr) { | |
| if (std::optional<evaluate::DataRef> dataRef{evaluate::ExtractDataRef(expr)}) | |
| if (const auto *arrayRef = std::get_if<evaluate::ArrayRef>(&dataRef->u)) | |
| for (const evaluate::Subscript &subscript : arrayRef->subscript()) | |
| if (std::holds_alternative<evaluate::IndirectSubscriptIntegerExpr>( | |
| subscript.u)) | |
| if (subscript.Rank() > 0) | |
| return true; | |
| return false; | |
| } | |
| bool ClauseProcessor::processDefaultMap(lower::StatementContext &stmtCtx, | |
| DefaultMapsTy &result) const { | |
| auto process = [&](const omp::clause::Defaultmap &clause, | |
| const parser::CharBlock &) { | |
| using Defmap = omp::clause::Defaultmap; | |
| clause::Defaultmap::VariableCategory variableCategory = | |
| Defmap::VariableCategory::All; | |
| // Variable Category is optional, if not specified defaults to all. | |
| // Multiples of the same category are illegal as are any other | |
| // defaultmaps being specified when a user specified all is in place, | |
| // however, this should be handled earlier during semantics. | |
| if (auto varCat = | |
| std::get<std::optional<Defmap::VariableCategory>>(clause.t)) | |
| variableCategory = varCat.value(); | |
| auto behaviour = std::get<Defmap::ImplicitBehavior>(clause.t); | |
| result[variableCategory] = behaviour; | |
| }; | |
| return findRepeatableClause<omp::clause::Defaultmap>(process); | |
| } | |
| bool ClauseProcessor::processDepend(lower::SymMap &symMap, | |
| lower::StatementContext &stmtCtx, | |
| mlir::omp::DependClauseOps &result) const { | |
| auto process = [&](const omp::clause::Depend &clause, | |
| const parser::CharBlock &) { | |
| auto depType = std::get<clause::DependenceType>(clause.t); | |
| auto &objects = std::get<omp::ObjectList>(clause.t); | |
| fir::FirOpBuilder &builder = converter.getFirOpBuilder(); | |
| mlir::Location clauseLocation = converter.getCurrentLocation(); | |
| mlir::omp::ClauseTaskDependAttr dependTypeOperand = | |
| genDependKindAttr(converter, depType); | |
| auto genDependVar = | |
| [&](const omp::Object &object, lower::SymMap &localSymMap, | |
| lower::StatementContext &localStmtCtx) -> mlir::Value { | |
| assert(object.ref() && "Expecting designator"); | |
| mlir::Value dependVar; | |
| SomeExpr expr = *object.ref(); | |
| if (evaluate::IsArrayElement(expr) || evaluate::ExtractSubstring(expr)) { | |
| // Array Section or character (sub)string | |
| if (isVectorSubscript(expr)) { | |
| // OpenMP needs the address of the first indexed element (required by | |
| // the standard to be the lowest index) to identify the dependency. We | |
| // don't need an accurate length for the array section because the | |
| // OpenMP standard forbids overlapping array sections. | |
| dependVar = genVectorSubscriptedDesignatorFirstElementAddress( | |
| clauseLocation, converter, expr, localSymMap, localStmtCtx); | |
| } else { | |
| // Ordinary array section e.g. A(1:512:2) | |
| hlfir::EntityWithAttributes entity = convertExprToHLFIR( | |
| clauseLocation, converter, expr, localSymMap, localStmtCtx); | |
| dependVar = entity.getBase(); | |
| } | |
| } else if (evaluate::isStructureComponent(expr) || | |
| evaluate::ExtractComplexPart(expr)) { | |
| SomeExpr expr = *object.ref(); | |
| hlfir::EntityWithAttributes entity = convertExprToHLFIR( | |
| clauseLocation, converter, expr, localSymMap, localStmtCtx); | |
| dependVar = entity.getBase(); | |
| } else { | |
| semantics::Symbol *sym = object.sym(); | |
| dependVar = converter.getSymbolAddress(*sym); | |
| } | |
| // If we pass a mutable box e.g. !fir.ref<!fir.box<!fir.heap<...>>> then | |
| // the runtime will use the address of the box not the address of the | |
| // data. Flang generates a lot of memcpys between different box | |
| // allocations so this is not a reliable way to identify the dependency. | |
| if (auto ref = mlir::dyn_cast<fir::ReferenceType>(dependVar.getType())) | |
| if (fir::isa_box_type(ref.getElementType())) | |
| dependVar = fir::LoadOp::create(builder, clauseLocation, dependVar); | |
| // The openmp dialect doesn't know what to do with boxes (and it would | |
| // break layering to teach it about them). The dependency variable can be | |
| // a box because it was an array section or because the original symbol | |
| // was mapped to a box. | |
| // Getting the address of the box data is okay because all the runtime | |
| // ultimately cares about is the base address of the array. | |
| if (fir::isa_box_type(dependVar.getType())) | |
| dependVar = fir::BoxAddrOp::create(builder, clauseLocation, dependVar); | |
| return dependVar; | |
| }; | |
| auto iteratorRanges = lowerIteratorRanges(clause, converter, stmtCtx); | |
| mlir::Type ptrTy = | |
| mlir::LLVM::LLVMPointerType::get(&converter.getMLIRContext()); | |
| mlir::Type iterTy = | |
| mlir::omp::IteratedType::get(&converter.getMLIRContext(), ptrTy); | |
| for (const omp::Object &object : objects) { | |
| llvm::SmallVector<IteratorRange> objectRanges = | |
| getIteratorRangesForObject(object, iteratorRanges); | |
| if (!objectRanges.empty()) { | |
| mlir::Value iterHandle = buildIteratorOp( | |
| converter, clauseLocation, iterTy, objectRanges, | |
| [&]() -> mlir::Value { | |
| lower::StatementContext iterStmtCtx; | |
| mlir::Value addr = | |
| genDependVar(object, converter.getSymbolMap(), iterStmtCtx); | |
| return fir::ConvertOp::create(builder, clauseLocation, ptrTy, | |
| addr); | |
| }); | |
| result.dependIterated.push_back(iterHandle); | |
| result.dependIteratedKinds.push_back(dependTypeOperand); | |
| } else { | |
| result.dependVars.push_back(genDependVar(object, symMap, stmtCtx)); | |
| result.dependKinds.push_back(dependTypeOperand); | |
| } | |
| } | |
| }; | |
| return findRepeatableClause<omp::clause::Depend>(process); | |
| } | |
| bool ClauseProcessor::processDynGroupprivate( | |
| lower::StatementContext &stmtCtx, | |
| mlir::omp::DynGroupprivateClauseOps &result) const { | |
| using DynGroupprivate = omp::clause::DynGroupprivate; | |
| // OpenMP 6.1 allows the `dyn_groupprivate` clause to appear more than once | |
| // on the same construct (with distinct access-group modifiers). Semantics | |
| // already rejects two clauses sharing the same access-group, but multiple | |
| // clauses with different access-groups are spec-legal. The current MLIR | |
| // representation (`mlir::omp::DynGroupprivateClauseOps`) and the OMPIRBuilder | |
| // only support a single set of modifiers + size, so reject the multi-clause | |
| // form up-front. | |
| unsigned count = 0; | |
| parser::CharBlock duplicateSource; | |
| findRepeatableClause<DynGroupprivate>( | |
| [&](const DynGroupprivate &, const parser::CharBlock &source) { | |
| if (++count == 2) | |
| duplicateSource = source; | |
| }); | |
| if (count > 1) { | |
| TODO(converter.genLocation(duplicateSource), | |
| "multiple dyn_groupprivate clauses on the same construct"); | |
| } | |
| if (auto *clause = findUniqueClause<DynGroupprivate>()) { | |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); | |
| mlir::MLIRContext *context = firOpBuilder.getContext(); | |
| // Process AccessGroup modifier (cgroup). | |
| if (auto accessGroup = | |
| std::get<std::optional<DynGroupprivate::AccessGroup>>(clause->t)) { | |
| switch (*accessGroup) { | |
| case DynGroupprivate::AccessGroup::Cgroup: | |
| result.dynGroupprivateAccessGroup = | |
| mlir::omp::AccessGroupModifierAttr::get( | |
| context, mlir::omp::AccessGroupModifier::cgroup); | |
| break; | |
| } | |
| } | |
| // Process Fallback modifier (abort, default_mem, null). | |
| if (auto fallback = | |
| std::get<std::optional<DynGroupprivate::Fallback>>(clause->t)) { | |
| switch (*fallback) { | |
| case DynGroupprivate::Fallback::Abort: | |
| result.dynGroupprivateFallback = mlir::omp::FallbackModifierAttr::get( | |
| context, mlir::omp::FallbackModifier::abort); | |
| break; | |
| case DynGroupprivate::Fallback::Default_Mem: | |
| result.dynGroupprivateFallback = mlir::omp::FallbackModifierAttr::get( | |
| context, mlir::omp::FallbackModifier::default_mem); | |
| break; | |
| case DynGroupprivate::Fallback::Null: | |
| result.dynGroupprivateFallback = mlir::omp::FallbackModifierAttr::get( | |
| context, mlir::omp::FallbackModifier::null); | |
| break; | |
| } | |
| } | |
| // Process size expression. | |
| const auto &sizeExpr = std::get<SomeExpr>(clause->t); | |
| result.dynGroupprivateSize = | |
| fir::getBase(converter.genExprValue(sizeExpr, stmtCtx)); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processGrainsize( | |
| lower::StatementContext &stmtCtx, | |
| mlir::omp::GrainsizeClauseOps &result) const { | |
| using Grainsize = omp::clause::Grainsize; | |
| if (auto *clause = findUniqueClause<Grainsize>()) { | |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); | |
| mlir::MLIRContext *context = firOpBuilder.getContext(); | |
| const auto &modifier = | |
| std::get<std::optional<Grainsize::Prescriptiveness>>(clause->t); | |
| if (modifier && *modifier == Grainsize::Prescriptiveness::Strict) { | |
| result.grainsizeMod = mlir::omp::ClauseGrainsizeTypeAttr::get( | |
| context, mlir::omp::ClauseGrainsizeType::Strict); | |
| } | |
| const auto &grainsizeExpr = std::get<omp::SomeExpr>(clause->t); | |
| result.grainsize = | |
| fir::getBase(converter.genExprValue(grainsizeExpr, stmtCtx)); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processHasDeviceAddr( | |
| lower::StatementContext &stmtCtx, mlir::omp::HasDeviceAddrClauseOps &result, | |
| llvm::SmallVectorImpl<Object> &hasDeviceObjects) const { | |
| // For HAS_DEVICE_ADDR objects, implicitly map the top-level entities. | |
| // Their address (or the whole descriptor, if the entity had one) will be | |
| // passed to the target region. | |
| std::map<Object, OmpMapParentAndMemberData> parentMemberIndices; | |
| bool clauseFound = findRepeatableClause<omp::clause::HasDeviceAddr>( | |
| [&](const omp::clause::HasDeviceAddr &clause, | |
| const parser::CharBlock &source) { | |
| mlir::Location location = converter.genLocation(source); | |
| mlir::omp::ClauseMapFlags mapTypeBits = | |
| mlir::omp::ClauseMapFlags::to | mlir::omp::ClauseMapFlags::implicit; | |
| omp::ObjectList baseObjects; | |
| llvm::transform(clause.v, std::back_inserter(baseObjects), | |
| [&](const omp::Object &object) { | |
| if (auto maybeBase = getBaseObject(object, semaCtx)) | |
| return *maybeBase; | |
| return object; | |
| }); | |
| processMapObjects(stmtCtx, location, baseObjects, mapTypeBits, | |
| parentMemberIndices, result.hasDeviceAddrVars, | |
| hasDeviceObjects); | |
| }); | |
| insertChildMapInfoIntoParent(converter, semaCtx, stmtCtx, parentMemberIndices, | |
| result.hasDeviceAddrVars, hasDeviceObjects); | |
| return clauseFound; | |
| } | |
| bool ClauseProcessor::processIf( | |
| omp::clause::If::DirectiveNameModifier directiveName, | |
| mlir::omp::IfClauseOps &result) const { | |
| bool found = false; | |
| findRepeatableClause<omp::clause::If>([&](const omp::clause::If &clause, | |
| const parser::CharBlock &source) { | |
| mlir::Location clauseLocation = converter.genLocation(source); | |
| mlir::Value operand = | |
| getIfClauseOperand(converter, clause, directiveName, clauseLocation); | |
| // Assume that, at most, a single 'if' clause will be applicable to the | |
| // given directive. | |
| if (operand) { | |
| result.ifExpr = operand; | |
| found = true; | |
| } | |
| }); | |
| return found; | |
| } | |
| template <typename T> | |
| void collectReductionObjects(const T &reduction, | |
| llvm::SmallVectorImpl<Object> &reductionObjects) { | |
| const omp::ObjectList &objectList{std::get<omp::ObjectList>(reduction.t)}; | |
| reductionObjects.reserve(objectList.size()); | |
| llvm::copy(objectList, std::back_inserter(reductionObjects)); | |
| } | |
| static llvm::SmallVector<const semantics::Symbol *> | |
| getObjectsSyms(llvm::ArrayRef<Object> objects) { | |
| llvm::SmallVector<const semantics::Symbol *> syms; | |
| syms.reserve(objects.size()); | |
| llvm::transform( | |
| objects, std::back_inserter(syms), | |
| [](const Fortran::lower::omp::Object &object) { return object.sym(); }); | |
| return syms; | |
| } | |
| bool ClauseProcessor::processInReduction( | |
| mlir::Location currentLocation, mlir::omp::InReductionClauseOps &result, | |
| llvm::SmallVectorImpl<Object> &outReductionObjects) const { | |
| return findRepeatableClause<omp::clause::InReduction>( | |
| [&](const omp::clause::InReduction &clause, const parser::CharBlock &) { | |
| llvm::SmallVector<mlir::Value> inReductionVars; | |
| llvm::SmallVector<bool> inReduceVarByRef; | |
| llvm::SmallVector<mlir::Attribute> inReductionDeclSymbols; | |
| llvm::SmallVector<Object> inReductionObjects; | |
| collectReductionObjects(clause, inReductionObjects); | |
| llvm::SmallVector<const semantics::Symbol *> inReductionSyms = | |
| getObjectsSyms(inReductionObjects); | |
| ReductionProcessor rp; | |
| if (!rp.processReductionArguments<mlir::omp::DeclareReductionOp>( | |
| currentLocation, converter, | |
| std::get<typename omp::clause::ReductionOperatorList>(clause.t), | |
| inReductionVars, inReduceVarByRef, inReductionDeclSymbols, | |
| inReductionSyms, &semaCtx)) | |
| TODO(currentLocation, "Lowering unrecognised reduction type"); | |
| // Copy local lists into the output. | |
| llvm::copy(inReductionVars, std::back_inserter(result.inReductionVars)); | |
| llvm::copy(inReduceVarByRef, | |
| std::back_inserter(result.inReductionByref)); | |
| llvm::copy(inReductionDeclSymbols, | |
| std::back_inserter(result.inReductionSyms)); | |
| llvm::copy(inReductionObjects, std::back_inserter(outReductionObjects)); | |
| }); | |
| } | |
| bool ClauseProcessor::processIsDevicePtr( | |
| lower::StatementContext &stmtCtx, mlir::omp::IsDevicePtrClauseOps &result, | |
| llvm::SmallVectorImpl<Object> &isDeviceObjects) const { | |
| std::map<Object, OmpMapParentAndMemberData> parentMemberIndices; | |
| bool clauseFound = findRepeatableClause<omp::clause::IsDevicePtr>( | |
| [&](const omp::clause::IsDevicePtr &clause, | |
| const parser::CharBlock &source) { | |
| mlir::Location location = converter.genLocation(source); | |
| // Force a map so the descriptor is materialized on the device with the | |
| // device address inside. | |
| mlir::omp::ClauseMapFlags mapTypeBits = | |
| mlir::omp::ClauseMapFlags::is_device_ptr | | |
| mlir::omp::ClauseMapFlags::to; | |
| processMapObjects(stmtCtx, location, clause.v, mapTypeBits, | |
| parentMemberIndices, result.isDevicePtrVars, | |
| isDeviceObjects); | |
| }); | |
| insertChildMapInfoIntoParent(converter, semaCtx, stmtCtx, parentMemberIndices, | |
| result.isDevicePtrVars, isDeviceObjects); | |
| return clauseFound; | |
| } | |
| bool ClauseProcessor::processLinear( | |
| mlir::omp::LinearClauseOps &result, bool isDeclareSimd, | |
| llvm::SmallVectorImpl<const semantics::Symbol *> *linearSyms) const { | |
| lower::StatementContext stmtCtx; | |
| std::vector<mlir::Attribute> typeAttrs; | |
| std::vector<mlir::Attribute> linearModAttrs; | |
| return findRepeatableClause< | |
| omp::clause::Linear>([&](const omp::clause::Linear &clause, | |
| const parser::CharBlock &) { | |
| auto &objects = std::get<omp::ObjectList>(clause.t); | |
| std::optional<mlir::omp::LinearModifier> explicitLinearMod; | |
| if (auto &linearModifier = | |
| std::get<std::optional<omp::clause::Linear::LinearModifier>>( | |
| clause.t)) { | |
| switch (*linearModifier) { | |
| case omp::clause::Linear::LinearModifier::Val: | |
| explicitLinearMod = mlir::omp::LinearModifier::val; | |
| break; | |
| case omp::clause::Linear::LinearModifier::Ref: | |
| explicitLinearMod = mlir::omp::LinearModifier::ref; | |
| break; | |
| case omp::clause::Linear::LinearModifier::Uval: | |
| explicitLinearMod = mlir::omp::LinearModifier::uval; | |
| break; | |
| } | |
| } | |
| for (const omp::Object &object : objects) { | |
| semantics::Symbol *sym = object.sym(); | |
| const mlir::Value variable = converter.getSymbolAddress(*sym); | |
| result.linearVars.push_back(variable); | |
| if (linearSyms) | |
| linearSyms->push_back(sym); | |
| mlir::Type ty = converter.genType(*sym); | |
| typeAttrs.push_back(mlir::TypeAttr::get(ty)); | |
| if (auto &mod = | |
| std::get<std::optional<omp::clause::Linear::StepComplexModifier>>( | |
| clause.t)) { | |
| mlir::Value operand = | |
| fir::getBase(converter.genExprValue(toEvExpr(*mod), stmtCtx)); | |
| result.linearStepVars.append(objects.size(), operand); | |
| } else { | |
| // If nothing is present, add the default step of 1. | |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); | |
| mlir::Location currentLocation = converter.getCurrentLocation(); | |
| mlir::Type integerTy = ty.isInteger() ? ty : firOpBuilder.getI32Type(); | |
| mlir::Value operand = | |
| firOpBuilder.createIntegerConstant(currentLocation, integerTy, 1); | |
| result.linearStepVars.append(objects.size(), operand); | |
| } | |
| // Determine the linear modifier: | |
| // 1. Use explicit modifier if provided. | |
| // 2. For OpenMP >= 5.2 (Section 5.4.6: "the default linear-modifier | |
| // is val"): | |
| // - declare simd: "ref" for POINTER or non-VALUE dummy args, | |
| // "val" otherwise. | |
| // - do/simd: always "val". | |
| // 3. Otherwise, leave unset (UnitAttr placeholder). | |
| auto getDeclareSimdDefaultMod = [](const semantics::Symbol &sym) { | |
| const auto &ultimate = sym.GetUltimate(); | |
| if (semantics::IsPointer(ultimate)) | |
| return mlir::omp::LinearModifier::ref; | |
| if (const auto *obj = | |
| ultimate.detailsIf<semantics::ObjectEntityDetails>()) | |
| if (obj->isDummy() && !semantics::IsValue(ultimate)) | |
| return mlir::omp::LinearModifier::ref; | |
| return mlir::omp::LinearModifier::val; | |
| }; | |
| std::optional<mlir::omp::LinearModifier> linearMod; | |
| if (explicitLinearMod) | |
| linearMod = *explicitLinearMod; | |
| else if (semaCtx.langOptions().getOpenMPVersion() >= 52) | |
| linearMod = isDeclareSimd ? getDeclareSimdDefaultMod(*sym) | |
| : mlir::omp::LinearModifier::val; | |
| if (linearMod) | |
| linearModAttrs.push_back(mlir::omp::LinearModifierAttr::get( | |
| &converter.getMLIRContext(), *linearMod)); | |
| else | |
| linearModAttrs.push_back( | |
| mlir::UnitAttr::get(&converter.getMLIRContext())); | |
| } | |
| result.linearVarTypes = | |
| mlir::ArrayAttr::get(&converter.getMLIRContext(), typeAttrs); | |
| result.linearModifiers = | |
| mlir::ArrayAttr::get(&converter.getMLIRContext(), linearModAttrs); | |
| }); | |
| } | |
| bool ClauseProcessor::processLink( | |
| llvm::SmallVectorImpl<DeclareTargetCaptureInfo> &result) const { | |
| return findRepeatableClause<omp::clause::Link>( | |
| [&](const omp::clause::Link &clause, const parser::CharBlock &) { | |
| // Case: declare target link(var1, var2)... | |
| gatherFuncAndVarSyms( | |
| clause.v, mlir::omp::DeclareTargetCaptureClause::link, result, | |
| /*automap=*/false); | |
| }); | |
| } | |
| void ClauseProcessor::processMapObjects( | |
| lower::StatementContext &stmtCtx, mlir::Location clauseLocation, | |
| const omp::ObjectList &objects, mlir::omp::ClauseMapFlags mapTypeBits, | |
| std::map<Object, OmpMapParentAndMemberData> &parentMemberIndices, | |
| llvm::SmallVectorImpl<mlir::Value> &mapVars, | |
| llvm::SmallVectorImpl<Object> &mapObjects, llvm::StringRef mapperIdNameRef, | |
| bool isMotionModifier, llvm::omp::Directive directive) const { | |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); | |
| for (const omp::Object &object : objects) { | |
| llvm::SmallVector<mlir::Value> bounds; | |
| std::stringstream asFortran; | |
| std::optional<omp::Object> parentObj; | |
| fir::factory::AddrAndBoundsInfo info = | |
| lower::gatherDataOperandAddrAndBounds<mlir::omp::MapBoundsOp, | |
| mlir::omp::MapBoundsType>( | |
| converter, firOpBuilder, semaCtx, stmtCtx, *object.sym(), | |
| object.ref(), clauseLocation, asFortran, bounds, | |
| treatIndexAsSection); | |
| mlir::Value baseOp = info.rawInput; | |
| if (object.sym()->owner().IsDerivedType() && !isMotionModifier) { | |
| omp::ObjectList objectList = gatherObjectsOf(object, semaCtx); | |
| assert(!objectList.empty() && | |
| "could not find parent objects of derived type member"); | |
| parentObj = objectList[0]; | |
| parentMemberIndices.emplace(parentObj.value(), | |
| OmpMapParentAndMemberData{}); | |
| if (isMemberOrParentAllocatableOrPointer(object, semaCtx)) { | |
| llvm::SmallVector<int64_t> indices; | |
| generateMemberPlacementIndices(object, indices, semaCtx); | |
| baseOp = createParentSymAndGenIntermediateMaps( | |
| clauseLocation, converter, semaCtx, stmtCtx, objectList, indices, | |
| parentMemberIndices[parentObj.value()], asFortran.str(), | |
| mapTypeBits); | |
| } | |
| } | |
| mlir::FlatSymbolRefAttr mapperId = | |
| resolveMapperId(converter, clauseLocation, object, mapperIdNameRef, | |
| mapTypeBits, directive); | |
| // Explicit map captures are captured ByRef by default, | |
| // optimisation passes may alter this to ByCopy or other capture | |
| // types to optimise | |
| auto location = mlir::NameLoc::get( | |
| mlir::StringAttr::get(firOpBuilder.getContext(), asFortran.str()), | |
| baseOp.getLoc()); | |
| mlir::omp::MapInfoOp mapOp = utils::openmp::createMapInfoOp( | |
| firOpBuilder, location, baseOp, | |
| /*varPtrPtr=*/mlir::Value{}, asFortran.str(), bounds, | |
| /*members=*/{}, /*membersIndex=*/mlir::ArrayAttr{}, mapTypeBits, | |
| mlir::omp::VariableCaptureKind::ByRef, baseOp.getType(), | |
| /*partialMap=*/false, mapperId); | |
| if (parentObj.has_value()) { | |
| parentMemberIndices[parentObj.value()].addChildIndexAndMapToParent( | |
| object, mapOp, semaCtx); | |
| } else { | |
| mapVars.push_back(mapOp); | |
| mapObjects.push_back(object); | |
| } | |
| } | |
| } | |
| /// Extract and mangle the mapper identifier name from a mapper clause. | |
| /// Returns "__implicit_mapper" if no mapper is specified, or "default" if | |
| /// the default mapper is specified, otherwise returns the mangled mapper name. | |
| /// This handles both the Map clause (which uses a vector of mappers) and | |
| /// To/From clauses (which use a DefinedOperator). | |
| template <typename MapperType> | |
| static std::string | |
| getMapperIdentifier(lower::AbstractConverter &converter, | |
| const std::optional<MapperType> &mapper) { | |
| if (!mapper) | |
| return "__implicit_mapper"; | |
| // Handle mapper types (both have the same structure) | |
| assert(mapper->size() == 1 && "more than one mapper"); | |
| const semantics::Symbol *mapperSym = mapper->front().v.id().symbol; | |
| std::string mapperIdName = mapperSym->name().ToString(); | |
| if (mapperIdName != "default") { | |
| // Mangle with the ultimate owner so that use-associated mapper | |
| // identifiers resolve to the same symbol as their defining scope. | |
| const semantics::Symbol &ultimate = mapperSym->GetUltimate(); | |
| mapperIdName = converter.mangleName(mapperIdName, ultimate.owner()); | |
| } | |
| return mapperIdName; | |
| } | |
| bool ClauseProcessor::processMap( | |
| mlir::Location currentLocation, lower::StatementContext &stmtCtx, | |
| mlir::omp::MapClauseOps &result, llvm::omp::Directive directive, | |
| llvm::SmallVectorImpl<Object> *mapObjects) const { | |
| // We always require tracking of objects, even if the caller does not, | |
| // so we create an optionally used local set of objects when the mapObjects | |
| // argument is not present. | |
| llvm::SmallVector<Object> localMapObjects; | |
| llvm::SmallVectorImpl<Object> *ptrMapObjects = | |
| mapObjects ? mapObjects : &localMapObjects; | |
| std::map<Object, OmpMapParentAndMemberData> parentMemberIndices; | |
| auto process = [&](const omp::clause::Map &clause, | |
| const parser::CharBlock &source) { | |
| using Map = omp::clause::Map; | |
| mlir::Location clauseLocation = converter.genLocation(source); | |
| const auto &[mapType, typeMods, attachMod, refMod, mappers, iterator, | |
| objects] = clause.t; | |
| mlir::omp::ClauseMapFlags mapTypeBits = mlir::omp::ClauseMapFlags::none; | |
| std::string mapperIdName = getMapperIdentifier(converter, mappers); | |
| // If the map type is specified, then process it else set the appropriate | |
| // default value | |
| Map::MapType type; | |
| if (directive == llvm::omp::Directive::OMPD_target_enter_data && | |
| semaCtx.langOptions().getOpenMPVersion() >= 52) | |
| type = mapType.value_or(Map::MapType::To); | |
| else if (directive == llvm::omp::Directive::OMPD_target_exit_data && | |
| semaCtx.langOptions().getOpenMPVersion() >= 52) | |
| type = mapType.value_or(Map::MapType::From); | |
| else | |
| type = mapType.value_or(Map::MapType::Tofrom); | |
| switch (type) { | |
| case Map::MapType::To: | |
| mapTypeBits |= mlir::omp::ClauseMapFlags::to; | |
| break; | |
| case Map::MapType::From: | |
| mapTypeBits |= mlir::omp::ClauseMapFlags::from; | |
| break; | |
| case Map::MapType::Tofrom: | |
| mapTypeBits |= | |
| mlir::omp::ClauseMapFlags::to | mlir::omp::ClauseMapFlags::from; | |
| break; | |
| case Map::MapType::Storage: | |
| mapTypeBits |= mlir::omp::ClauseMapFlags::storage; | |
| break; | |
| } | |
| if (typeMods) { | |
| // TODO: Still requires "self" modifier, an OpenMP 6.0+ feature | |
| if (llvm::is_contained(*typeMods, Map::MapTypeModifier::Always)) | |
| mapTypeBits |= mlir::omp::ClauseMapFlags::always; | |
| if (llvm::is_contained(*typeMods, Map::MapTypeModifier::Present)) | |
| mapTypeBits |= mlir::omp::ClauseMapFlags::present; | |
| if (llvm::is_contained(*typeMods, Map::MapTypeModifier::Close)) | |
| mapTypeBits |= mlir::omp::ClauseMapFlags::close; | |
| if (llvm::is_contained(*typeMods, Map::MapTypeModifier::Delete)) | |
| mapTypeBits |= mlir::omp::ClauseMapFlags::del; | |
| if (llvm::is_contained(*typeMods, Map::MapTypeModifier::OmpxHold)) | |
| mapTypeBits |= mlir::omp::ClauseMapFlags::ompx_hold; | |
| } | |
| if (refMod) { | |
| switch (*refMod) { | |
| case Map::RefModifier::RefPtee: | |
| mapTypeBits |= mlir::omp::ClauseMapFlags::ref_ptee; | |
| break; | |
| case Map::RefModifier::RefPtr: | |
| mapTypeBits |= mlir::omp::ClauseMapFlags::ref_ptr; | |
| break; | |
| case Map::RefModifier::RefPtrPtee: | |
| mapTypeBits |= mlir::omp::ClauseMapFlags::ref_ptr | | |
| mlir::omp::ClauseMapFlags::ref_ptee; | |
| break; | |
| } | |
| } | |
| if (attachMod) { | |
| switch (*attachMod) { | |
| case Map::AttachModifier::Always: | |
| mapTypeBits |= mlir::omp::ClauseMapFlags::attach_always; | |
| break; | |
| case Map::AttachModifier::Never: | |
| mapTypeBits |= mlir::omp::ClauseMapFlags::attach_never; | |
| break; | |
| case Map::AttachModifier::Auto: | |
| mapTypeBits |= mlir::omp::ClauseMapFlags::attach_auto; | |
| break; | |
| } | |
| } | |
| if (iterator) | |
| TODO(currentLocation, | |
| "Support for iterator modifiers is not implemented yet"); | |
| TodoLocators(currentLocation, objects); | |
| processMapObjects(stmtCtx, clauseLocation, | |
| std::get<omp::ObjectList>(clause.t), mapTypeBits, | |
| parentMemberIndices, result.mapVars, *ptrMapObjects, | |
| mapperIdName, /*isMotionModifier=*/false, directive); | |
| }; | |
| bool clauseFound = findRepeatableClause<omp::clause::Map>(process); | |
| insertChildMapInfoIntoParent(converter, semaCtx, stmtCtx, parentMemberIndices, | |
| result.mapVars, *ptrMapObjects); | |
| return clauseFound; | |
| } | |
| bool ClauseProcessor::processMotionClauses(lower::StatementContext &stmtCtx, | |
| mlir::omp::MapClauseOps &result) { | |
| std::map<Object, OmpMapParentAndMemberData> parentMemberIndices; | |
| llvm::SmallVector<Object> mapObjects; | |
| auto callbackFn = [&](const auto &clause, const parser::CharBlock &source) { | |
| mlir::Location clauseLocation = converter.genLocation(source); | |
| const auto &[expectation, mapper, iterator, objects] = clause.t; | |
| mlir::omp::ClauseMapFlags mapTypeBits = | |
| std::is_same_v<llvm::remove_cvref_t<decltype(clause)>, omp::clause::To> | |
| ? mlir::omp::ClauseMapFlags::to | |
| : mlir::omp::ClauseMapFlags::from; | |
| if (expectation && *expectation == omp::clause::To::Expectation::Present) | |
| mapTypeBits |= mlir::omp::ClauseMapFlags::present; | |
| // Support motion modifiers: iterator. | |
| std::string mapperIdName = getMapperIdentifier(converter, mapper); | |
| if (iterator) | |
| TODO(clauseLocation, "Iterator modifier is not supported yet"); | |
| TodoLocators(clauseLocation, objects); | |
| processMapObjects(stmtCtx, clauseLocation, objects, mapTypeBits, | |
| parentMemberIndices, result.mapVars, mapObjects, | |
| mapperIdName, /*isMotionModifier=*/true); | |
| }; | |
| bool clauseFound = findRepeatableClause<omp::clause::To>(callbackFn); | |
| clauseFound = | |
| findRepeatableClause<omp::clause::From>(callbackFn) || clauseFound; | |
| insertChildMapInfoIntoParent(converter, semaCtx, stmtCtx, parentMemberIndices, | |
| result.mapVars, mapObjects); | |
| return clauseFound; | |
| } | |
| bool ClauseProcessor::processNontemporal( | |
| mlir::omp::NontemporalClauseOps &result) const { | |
| return findRepeatableClause<omp::clause::Nontemporal>( | |
| [&](const omp::clause::Nontemporal &clause, const parser::CharBlock &) { | |
| for (const Object &object : clause.v) { | |
| semantics::Symbol *sym = object.sym(); | |
| mlir::Value symVal = converter.getSymbolAddress(*sym); | |
| result.nontemporalVars.push_back(symVal); | |
| } | |
| }); | |
| } | |
| bool ClauseProcessor::processReduction( | |
| mlir::Location currentLocation, mlir::omp::ReductionClauseOps &result, | |
| llvm::SmallVectorImpl<Object> &outReductionObjects, | |
| llvm::DenseMap<const semantics::Symbol *, mlir::Value> *reductionVarCache) | |
| const { | |
| return findRepeatableClause<omp::clause::Reduction>( | |
| [&](const omp::clause::Reduction &clause, const parser::CharBlock &) { | |
| llvm::SmallVector<mlir::Value> reductionVars; | |
| llvm::SmallVector<bool> reduceVarByRef; | |
| llvm::SmallVector<mlir::Attribute> reductionDeclSymbols; | |
| llvm::SmallVector<Object> reductionObjects; | |
| collectReductionObjects(clause, reductionObjects); | |
| llvm::SmallVector<const semantics::Symbol *> reductionSyms = | |
| getObjectsSyms(reductionObjects); | |
| auto mod = std::get<std::optional<ReductionModifier>>(clause.t); | |
| if (mod.has_value()) { | |
| result.reductionMod = mlir::omp::ReductionModifierAttr::get( | |
| converter.getFirOpBuilder().getContext(), | |
| translateReductionModifier(mod.value())); | |
| } | |
| ReductionProcessor rp; | |
| if (!rp.processReductionArguments<mlir::omp::DeclareReductionOp>( | |
| currentLocation, converter, | |
| std::get<typename omp::clause::ReductionOperatorList>(clause.t), | |
| reductionVars, reduceVarByRef, reductionDeclSymbols, | |
| reductionSyms, &semaCtx, reductionVarCache)) | |
| TODO(currentLocation, "Lowering unrecognised reduction type"); | |
| // Copy local lists into the output. | |
| llvm::copy(reductionVars, std::back_inserter(result.reductionVars)); | |
| llvm::copy(reduceVarByRef, std::back_inserter(result.reductionByref)); | |
| llvm::copy(reductionDeclSymbols, | |
| std::back_inserter(result.reductionSyms)); | |
| llvm::copy(reductionObjects, std::back_inserter(outReductionObjects)); | |
| }); | |
| } | |
| bool ClauseProcessor::processTaskReduction( | |
| mlir::Location currentLocation, mlir::omp::TaskReductionClauseOps &result, | |
| llvm::SmallVectorImpl<Object> &outReductionObjects) const { | |
| return findRepeatableClause<omp::clause::TaskReduction>( | |
| [&](const omp::clause::TaskReduction &clause, const parser::CharBlock &) { | |
| llvm::SmallVector<mlir::Value> taskReductionVars; | |
| llvm::SmallVector<bool> taskReduceVarByRef; | |
| llvm::SmallVector<mlir::Attribute> taskReductionDeclSymbols; | |
| llvm::SmallVector<Object> taskReductionObjects; | |
| collectReductionObjects(clause, taskReductionObjects); | |
| llvm::SmallVector<const semantics::Symbol *> taskReductionSyms = | |
| getObjectsSyms(taskReductionObjects); | |
| ReductionProcessor rp; | |
| if (!rp.processReductionArguments<mlir::omp::DeclareReductionOp>( | |
| currentLocation, converter, | |
| std::get<typename omp::clause::ReductionOperatorList>(clause.t), | |
| taskReductionVars, taskReduceVarByRef, taskReductionDeclSymbols, | |
| taskReductionSyms, &semaCtx)) | |
| TODO(currentLocation, "Lowering unrecognised reduction type"); | |
| // Copy local lists into the output. | |
| llvm::copy(taskReductionVars, | |
| std::back_inserter(result.taskReductionVars)); | |
| llvm::copy(taskReduceVarByRef, | |
| std::back_inserter(result.taskReductionByref)); | |
| llvm::copy(taskReductionDeclSymbols, | |
| std::back_inserter(result.taskReductionSyms)); | |
| llvm::copy(taskReductionObjects, | |
| std::back_inserter(outReductionObjects)); | |
| }); | |
| } | |
| bool ClauseProcessor::processTo( | |
| llvm::SmallVectorImpl<DeclareTargetCaptureInfo> &result) const { | |
| return findRepeatableClause<omp::clause::To>( | |
| [&](const omp::clause::To &clause, const parser::CharBlock &) { | |
| // Case: declare target to(func, var1, var2)... | |
| gatherFuncAndVarSyms(std::get<ObjectList>(clause.t), | |
| mlir::omp::DeclareTargetCaptureClause::to, result, | |
| /*automap=*/false); | |
| }); | |
| } | |
| bool ClauseProcessor::processEnter( | |
| llvm::SmallVectorImpl<DeclareTargetCaptureInfo> &result) const { | |
| return findRepeatableClause<omp::clause::Enter>( | |
| [&](const omp::clause::Enter &clause, const parser::CharBlock &source) { | |
| bool automap = | |
| std::get<std::optional<omp::clause::Enter::Modifier>>(clause.t) | |
| .has_value(); | |
| // Case: declare target enter(func, var1, var2)... | |
| gatherFuncAndVarSyms(std::get<ObjectList>(clause.t), | |
| mlir::omp::DeclareTargetCaptureClause::enter, | |
| result, automap); | |
| }); | |
| } | |
| bool ClauseProcessor::processUseDeviceAddr( | |
| lower::StatementContext &stmtCtx, mlir::omp::UseDeviceAddrClauseOps &result, | |
| llvm::SmallVectorImpl<Object> &useDeviceObjects) const { | |
| llvm::SmallPtrSet<const semantics::Symbol *, 4> mappedCommonBlocks; | |
| findRepeatableClause<omp::clause::Map>( | |
| [&](const omp::clause::Map &clause, const parser::CharBlock &) { | |
| for (const Object &object : std::get<ObjectList>(clause.t)) { | |
| if (object.sym()->has<semantics::CommonBlockDetails>()) | |
| mappedCommonBlocks.insert(&object.sym()->GetUltimate()); | |
| } | |
| }); | |
| std::map<Object, OmpMapParentAndMemberData> parentMemberIndices; | |
| bool clauseFound = findRepeatableClause<omp::clause::UseDeviceAddr>( | |
| [&](const omp::clause::UseDeviceAddr &clause, | |
| const parser::CharBlock &source) { | |
| mlir::Location location = converter.genLocation(source); | |
| mlir::omp::ClauseMapFlags mapTypeBits = | |
| mlir::omp::ClauseMapFlags::return_param; | |
| ObjectList objects; | |
| // Keep the aggregate only when it pairs with a whole-COMMON map on | |
| // this directive; otherwise preserve named-COMMON member equivalence. | |
| for (const Object &object : clause.v) { | |
| auto *details = | |
| object.sym()->detailsIf<semantics::CommonBlockDetails>(); | |
| if (!details || | |
| mappedCommonBlocks.contains(&object.sym()->GetUltimate())) { | |
| objects.push_back(object); | |
| continue; | |
| } | |
| for (const auto &member : details->objects()) | |
| objects.push_back(Object{&*member, std::nullopt}); | |
| } | |
| processMapObjects(stmtCtx, location, objects, mapTypeBits, | |
| parentMemberIndices, result.useDeviceAddrVars, | |
| useDeviceObjects); | |
| }); | |
| insertChildMapInfoIntoParent(converter, semaCtx, stmtCtx, parentMemberIndices, | |
| result.useDeviceAddrVars, useDeviceObjects); | |
| return clauseFound; | |
| } | |
| bool ClauseProcessor::processUseDevicePtr( | |
| lower::StatementContext &stmtCtx, mlir::omp::UseDevicePtrClauseOps &result, | |
| llvm::SmallVectorImpl<Object> &useDeviceObjects) const { | |
| std::map<Object, OmpMapParentAndMemberData> parentMemberIndices; | |
| bool clauseFound = findRepeatableClause<omp::clause::UseDevicePtr>( | |
| [&](const omp::clause::UseDevicePtr &clause, | |
| const parser::CharBlock &source) { | |
| mlir::Location location = converter.genLocation(source); | |
| mlir::omp::ClauseMapFlags mapTypeBits = | |
| mlir::omp::ClauseMapFlags::return_param; | |
| processMapObjects(stmtCtx, location, clause.v, mapTypeBits, | |
| parentMemberIndices, result.useDevicePtrVars, | |
| useDeviceObjects); | |
| }); | |
| insertChildMapInfoIntoParent(converter, semaCtx, stmtCtx, parentMemberIndices, | |
| result.useDevicePtrVars, useDeviceObjects); | |
| return clauseFound; | |
| } | |
| bool ClauseProcessor::processUniform( | |
| mlir::omp::UniformClauseOps &result) const { | |
| return findRepeatableClause<omp::clause::Uniform>( | |
| [&](const omp::clause::Uniform &clause, const parser::CharBlock &) { | |
| const auto &objects = clause.v; | |
| if (!objects.empty()) | |
| genObjectList(objects, converter, result.uniformVars); | |
| }); | |
| } | |
| bool ClauseProcessor::processInbranch( | |
| mlir::omp::InbranchClauseOps &result) const { | |
| if (findUniqueClause<omp::clause::Inbranch>()) { | |
| result.inbranch = converter.getFirOpBuilder().getUnitAttr(); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool ClauseProcessor::processNotinbranch( | |
| mlir::omp::NotinbranchClauseOps &result) const { | |
| if (findUniqueClause<omp::clause::Notinbranch>()) { | |
| result.notinbranch = converter.getFirOpBuilder().getUnitAttr(); | |
| return true; | |
| } | |
| return false; | |
| } | |
| } // namespace omp | |
| } // namespace lower | |
| } // namespace Fortran |