| //===-- Utils..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 "Utils.h" |
| |
| #include "ClauseFinder.h" |
| #include "flang/Evaluate/fold.h" |
| #include "flang/Evaluate/tools.h" |
| #include "flang/Optimizer/Dialect/Support/FIRContext.h" |
| #include "mlir/Dialect/OpenMP/OpenMPInterfaces.h" |
| #include <flang/Lower/AbstractConverter.h> |
| #include <flang/Lower/ConvertType.h> |
| #include <flang/Lower/DirectivesCommon.h> |
| #include <flang/Lower/OpenMP.h> |
| #include <flang/Lower/OpenMP/Clauses.h> |
| #include <flang/Lower/PFTBuilder.h> |
| #include <flang/Lower/Support/PrivateReductionUtils.h> |
| #include <flang/Optimizer/Builder/BoxValue.h> |
| #include <flang/Optimizer/Builder/FIRBuilder.h> |
| #include <flang/Optimizer/Builder/Todo.h> |
| #include <flang/Parser/openmp-utils.h> |
| #include <flang/Parser/parse-tree.h> |
| #include <flang/Parser/tools.h> |
| #include <flang/Semantics/omp-declare-variant.h> |
| #include <flang/Semantics/openmp-utils.h> |
| #include <flang/Semantics/scope.h> |
| #include <flang/Semantics/symbol.h> |
| #include <flang/Semantics/tools.h> |
| #include <flang/Semantics/type.h> |
| #include <flang/Utils/OpenMP.h> |
| #include <llvm/ADT/STLExtras.h> |
| #include <llvm/ADT/SmallPtrSet.h> |
| #include <llvm/ADT/StringRef.h> |
| #include <llvm/Support/CommandLine.h> |
| |
| #include <functional> |
| #include <iterator> |
| |
| template <typename T> |
| Fortran::semantics::MaybeIntExpr |
| EvaluateIntExpr(Fortran::semantics::SemanticsContext &context, const T &expr) { |
| if (Fortran::semantics::MaybeExpr maybeExpr{ |
| Fold(context.foldingContext(), AnalyzeExpr(context, expr))}) { |
| if (auto *intExpr{ |
| Fortran::evaluate::UnwrapExpr<Fortran::semantics::SomeIntExpr>( |
| *maybeExpr)}) { |
| return std::move(*intExpr); |
| } |
| } |
| return std::nullopt; |
| } |
| |
| template <typename T> |
| std::optional<std::int64_t> |
| EvaluateInt64(Fortran::semantics::SemanticsContext &context, const T &expr) { |
| return Fortran::evaluate::ToInt64(EvaluateIntExpr(context, expr)); |
| } |
| |
| llvm::cl::opt<bool> treatIndexAsSection( |
| "openmp-treat-index-as-section", |
| llvm::cl::desc("In the OpenMP data clauses treat `a(N)` as `a(N:N)`."), |
| llvm::cl::init(true)); |
| |
| namespace Fortran { |
| namespace lower { |
| namespace omp { |
| bool requiresImplicitDefaultDeclareMapper( |
| const semantics::DerivedTypeSpec &typeSpec) { |
| // ISO C interoperable types (e.g., c_ptr, c_funptr) must always have implicit |
| // default mappers available so that OpenMP offloading can correctly map them. |
| if (semantics::IsIsoCType(&typeSpec)) |
| return true; |
| |
| llvm::SmallPtrSet<const semantics::DerivedTypeSpec *, 8> visited; |
| |
| std::function<bool(const semantics::DerivedTypeSpec &)> requiresMapper = |
| [&](const semantics::DerivedTypeSpec &spec) -> bool { |
| if (!visited.insert(&spec).second) |
| return false; |
| |
| semantics::DirectComponentIterator directComponents{spec}; |
| for (const semantics::Symbol &component : directComponents) { |
| if (component.attrs().test(semantics::Attr::ALLOCATABLE)) |
| return true; |
| |
| if (const semantics::DeclTypeSpec *declType = component.GetType()) |
| if (const auto *nested = declType->AsDerived()) |
| if (requiresMapper(*nested)) |
| return true; |
| } |
| return false; |
| }; |
| |
| return requiresMapper(typeSpec); |
| } |
| |
| int64_t getCollapseValue(const List<Clause> &clauses) { |
| auto iter = llvm::find_if(clauses, [](const Clause &clause) { |
| return clause.id == llvm::omp::Clause::OMPC_collapse; |
| }); |
| if (iter != clauses.end()) { |
| const auto &collapse = std::get<clause::Collapse>(iter->u); |
| return evaluate::ToInt64(collapse.v).value(); |
| } |
| return 1; |
| } |
| |
| void genObjectList(const ObjectList &objects, |
| lower::AbstractConverter &converter, |
| llvm::SmallVectorImpl<mlir::Value> &operands) { |
| for (const Object &object : objects) { |
| const semantics::Symbol *sym = object.sym(); |
| assert(sym && "Expected Symbol"); |
| if (mlir::Value variable = converter.getSymbolAddress(*sym)) { |
| operands.push_back(variable); |
| } else if (const auto *details = |
| sym->detailsIf<semantics::HostAssocDetails>()) { |
| operands.push_back(converter.getSymbolAddress(details->symbol())); |
| converter.copySymbolBinding(details->symbol(), *sym); |
| } |
| } |
| } |
| |
| mlir::Type getLoopVarType(lower::AbstractConverter &converter, |
| std::size_t loopVarTypeSize) { |
| // OpenMP runtime requires 32-bit or 64-bit loop variables. |
| loopVarTypeSize = loopVarTypeSize * 8; |
| if (loopVarTypeSize < 32) { |
| loopVarTypeSize = 32; |
| } else if (loopVarTypeSize > 64) { |
| loopVarTypeSize = 64; |
| mlir::emitWarning(converter.getCurrentLocation(), |
| "OpenMP loop iteration variable cannot have more than 64 " |
| "bits size and will be narrowed into 64 bits."); |
| } |
| assert((loopVarTypeSize == 32 || loopVarTypeSize == 64) && |
| "OpenMP loop iteration variable size must be transformed into 32-bit " |
| "or 64-bit"); |
| return converter.getFirOpBuilder().getIntegerType(loopVarTypeSize); |
| } |
| |
| semantics::Symbol * |
| getIterationVariableSymbol(const lower::pft::Evaluation &eval) { |
| return eval.visit(common::visitors{ |
| [&](const parser::DoConstruct &doLoop) { |
| if (const auto &maybeCtrl = doLoop.GetLoopControl()) { |
| using LoopControl = parser::LoopControl; |
| if (auto *bounds = std::get_if<LoopControl::Bounds>(&maybeCtrl->u)) { |
| using NameType = llvm::remove_cvref_t<decltype(bounds->Name())>; |
| static_assert( |
| std::is_same_v<NameType, parser::Scalar<parser::Name>>); |
| return bounds->Name().thing.symbol; |
| } |
| } |
| return static_cast<semantics::Symbol *>(nullptr); |
| }, |
| [](auto &&) { return static_cast<semantics::Symbol *>(nullptr); }, |
| }); |
| } |
| |
| void gatherFuncAndVarSyms( |
| const ObjectList &objects, mlir::omp::DeclareTargetCaptureClause clause, |
| llvm::SmallVectorImpl<DeclareTargetCaptureInfo> &symbolAndClause, |
| bool automap) { |
| for (const Object &object : objects) |
| symbolAndClause.emplace_back(clause, *object.sym(), automap); |
| } |
| |
| // This function gathers the individual omp::Object's that make up a |
| // larger omp::Object symbol. |
| // |
| // For example, provided the larger symbol: "parent%child%member", this |
| // function breaks it up into its constituent components ("parent", |
| // "child", "member"), so we can access each individual component and |
| // introspect details. Important to note is this function breaks it up from |
| // RHS to LHS ("member" to "parent") and then we reverse it so that the |
| // returned omp::ObjectList is LHS to RHS, with the "parent" at the |
| // beginning. |
| omp::ObjectList gatherObjectsOf(omp::Object derivedTypeMember, |
| semantics::SemanticsContext &semaCtx) { |
| omp::ObjectList objList; |
| std::optional<omp::Object> baseObj = derivedTypeMember; |
| while (baseObj.has_value()) { |
| objList.push_back(baseObj.value()); |
| baseObj = getBaseObject(baseObj.value(), semaCtx); |
| } |
| return omp::ObjectList{llvm::reverse(objList)}; |
| } |
| |
| // This function generates a series of indices from a provided omp::Object, |
| // that devolves to an ArrayRef symbol, e.g. "array(2,3,4)", this function |
| // would generate a series of indices of "[1][2][3]" for the above example, |
| // offsetting by -1 to account for the non-zero fortran indexes. |
| // |
| // These indices can then be provided to a coordinate operation or other |
| // GEP-like operation to access the relevant positional member of the |
| // array. |
| // |
| // It is of note that the function only supports subscript integers currently |
| // and not Triplets i.e. Array(1:2:3). |
| static void generateArrayIndices(lower::AbstractConverter &converter, |
| fir::FirOpBuilder &firOpBuilder, |
| lower::StatementContext &stmtCtx, |
| mlir::Location clauseLocation, |
| llvm::SmallVectorImpl<mlir::Value> &indices, |
| omp::Object object) { |
| auto maybeRef = evaluate::ExtractDataRef(*object.ref()); |
| if (!maybeRef) |
| return; |
| |
| auto *arr = std::get_if<evaluate::ArrayRef>(&maybeRef->u); |
| if (!arr) |
| return; |
| |
| for (auto v : arr->subscript()) { |
| if (std::holds_alternative<Triplet>(v.u)) |
| TODO(clauseLocation, "Triplet indexing in map clause is unsupported"); |
| auto expr = std::get<Fortran::evaluate::IndirectSubscriptIntegerExpr>(v.u); |
| mlir::Value subscript = |
| fir::getBase(converter.genExprValue(toEvExpr(expr.value()), stmtCtx)); |
| indices.push_back(firOpBuilder.createConvert( |
| clauseLocation, firOpBuilder.getIndexType(), subscript)); |
| } |
| } |
| |
| /// When mapping members of derived types, there is a chance that one of the |
| /// members along the way to a mapped member is an descriptor. In which case |
| /// we have to make sure we generate a map for those along the way otherwise |
| /// we will be missing a chunk of data required to actually map the member |
| /// type to device. This function effectively generates these maps and the |
| /// appropriate data accesses required to generate these maps. It will avoid |
| /// creating duplicate maps, as duplicates are just as bad as unmapped |
| /// descriptor data in a lot of cases for the runtime (and unnecessary |
| /// data movement should be avoided where possible). |
| /// |
| /// As an example for the following mapping: |
| /// |
| /// type :: vertexes |
| /// integer(4), allocatable :: vertexx(:) |
| /// integer(4), allocatable :: vertexy(:) |
| /// end type vertexes |
| /// |
| /// type :: dtype |
| /// real(4) :: i |
| /// type(vertexes), allocatable :: vertexes(:) |
| /// end type dtype |
| /// |
| /// type(dtype), allocatable :: alloca_dtype |
| /// |
| /// !$omp target map(tofrom: alloca_dtype%vertexes(N1)%vertexx) |
| /// |
| /// The below HLFIR/FIR is generated (trimmed for conciseness): |
| /// |
| /// On the first iteration we index into the record type alloca_dtype |
| /// to access "vertexes", we then generate a map for this descriptor |
| /// alongside bounds to indicate we only need the 1 member, rather than |
| /// the whole array block in this case (In theory we could map its |
| /// entirety at the cost of data transfer bandwidth). |
| /// |
| /// %13:2 = hlfir.declare ... "alloca_dtype" ... |
| /// %39 = fir.load %13#0 : ... |
| /// %40 = fir.coordinate_of %39, %c1 : ... |
| /// %51 = omp.map.info var_ptr(%40 : ...) map_clauses(to) capture(ByRef) ... |
| /// %52 = fir.load %40 : ... |
| /// |
| /// Second iteration generating access to "vertexes(N1) utilising the N1 index |
| /// %53 = load N1 ... |
| /// %54 = fir.convert %53 : (i32) -> i64 |
| /// %55 = fir.convert %54 : (i64) -> index |
| /// %56 = arith.subi %55, %c1 : index |
| /// %57 = fir.coordinate_of %52, %56 : ... |
| /// |
| /// Still in the second iteration we access the allocatable member "vertexx", |
| /// we return %58 from the function and provide it to the final and "main" |
| /// map of processMap (generated by the record type segment of the below |
| /// function), if this were not the final symbol in the list, i.e. we accessed |
| /// a member below vertexx, we would have generated the map below as we did in |
| /// the first iteration and then continue to generate further coordinates to |
| /// access further components as required. |
| /// |
| /// %58 = fir.coordinate_of %57, %c0 : ... |
| /// %61 = omp.map.info var_ptr(%58 : ...) map_clauses(to) capture(ByRef) ... |
| /// |
| /// Parent mapping containing prior generated mapped members, generated at |
| /// a later step but here to showcase the "end" result |
| /// |
| /// omp.map.info var_ptr(%13#1 : ...) map_clauses(to) capture(ByRef) |
| /// members(%50, %61 : [0, 1, 0], [0, 1, 0] : ... |
| /// |
| /// \param objectList - The list of omp::Object symbol data for each parent |
| /// to the mapped member (also includes the mapped member), generated via |
| /// gatherObjectsOf. |
| /// \param indices - List of index data associated with the mapped member |
| /// symbol, which identifies the placement of the member in its parent, |
| /// this helps generate the appropriate member accesses. These indices |
| /// can be generated via generateMemberPlacementIndices. |
| /// \param asFortran - A string generated from the mapped variable to be |
| /// associated with the main map, generally (but not restricted to) |
| /// generated via gatherDataOperandAddrAndBounds or other |
| /// DirectiveCommons.hpp utilities. |
| /// \param mapTypeBits - The map flags that will be associated with the |
| /// generated maps, minus alterations of the TO and FROM bits for the |
| /// intermediate components to prevent accidental overwriting on device |
| /// write back. |
| mlir::Value createParentSymAndGenIntermediateMaps( |
| mlir::Location clauseLocation, lower::AbstractConverter &converter, |
| semantics::SemanticsContext &semaCtx, lower::StatementContext &stmtCtx, |
| omp::ObjectList &objectList, llvm::SmallVectorImpl<int64_t> &indices, |
| OmpMapParentAndMemberData &parentMemberIndices, llvm::StringRef asFortran, |
| mlir::omp::ClauseMapFlags mapTypeBits) { |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); |
| |
| /// Checks if an omp::Object is an array expression with a subscript, e.g. |
| /// array(1,2). |
| auto isArrayExprWithSubscript = [](omp::Object obj) { |
| if (auto maybeRef = evaluate::ExtractDataRef(obj.ref())) { |
| evaluate::DataRef ref = *maybeRef; |
| if (auto *arr = std::get_if<evaluate::ArrayRef>(&ref.u)) |
| return !arr->subscript().empty(); |
| } |
| return false; |
| }; |
| |
| // Generate the access to the original parent base address. |
| fir::factory::AddrAndBoundsInfo parentBaseAddr = |
| lower::getDataOperandBaseAddr(converter, firOpBuilder, |
| *objectList[0].sym(), clauseLocation); |
| mlir::Value curValue = parentBaseAddr.addr; |
| |
| // Iterate over all objects in the objectList, this should consist of all |
| // record types between the parent and the member being mapped (including |
| // the parent). The object list may also contain array objects as well, |
| // this can occur when specifying bounds or a specific element access |
| // within a member map, we skip these. |
| size_t currentIndicesIdx = 0; |
| for (size_t i = 0; i < objectList.size(); ++i) { |
| // If we encounter a sequence type, i.e. an array, we must generate the |
| // correct coordinate operation to index into the array to proceed further, |
| // this is only relevant in cases where we encounter subscripts currently. |
| // |
| // For example in the following case: |
| // |
| // map(tofrom: array_dtype(4)%internal_dtypes(3)%float_elements(4)) |
| // |
| // We must generate coordinate operation accesses for each subscript |
| // we encounter. |
| if (fir::SequenceType arrType = mlir::dyn_cast<fir::SequenceType>( |
| fir::unwrapPassByRefType(curValue.getType()))) { |
| if (isArrayExprWithSubscript(objectList[i])) { |
| llvm::SmallVector<mlir::Value> subscriptIndices; |
| generateArrayIndices(converter, firOpBuilder, stmtCtx, clauseLocation, |
| subscriptIndices, objectList[i]); |
| assert(!subscriptIndices.empty() && |
| "missing expected indices for map clause"); |
| if (auto boxTy = llvm::dyn_cast<fir::BaseBoxType>(curValue.getType())) { |
| // To accommodate indexing into box types of all dimensions including |
| // negative dimensions we have to take into consideration the lower |
| // bounds and extents of the data (stored in the box) and convey it |
| // to the ArrayCoorOp so that it can appropriately access the element |
| // utilising the subscript we provide and the runtime sizes stored in |
| // the Box. To do so we need to generate a ShapeShiftOp which combines |
| // both the lb (ShiftOp) and extent (ShapeOp) of the Box, giving the |
| // ArrayCoorOp the spatial information it needs to calculate the |
| // underlying address. |
| mlir::Value shapeShift = Fortran::lower::getShapeShift( |
| firOpBuilder, clauseLocation, curValue); |
| auto addrOp = |
| fir::BoxAddrOp::create(firOpBuilder, clauseLocation, curValue); |
| curValue = fir::ArrayCoorOp::create( |
| firOpBuilder, clauseLocation, |
| firOpBuilder.getRefType(arrType.getEleTy()), addrOp, shapeShift, |
| /*slice=*/mlir::Value{}, subscriptIndices, |
| /*typeparms=*/mlir::ValueRange{}); |
| } else { |
| // We're required to negate by one in the non-Box case as I believe |
| // we do not have the shape generated from the dimensions to help |
| // adjust the indexing. |
| // TODO/FIXME: This may need adjusted to support bounds of unusual |
| // dimensions, if that's the case then it is likely best to fold this |
| // branch into the above. |
| mlir::Value one = firOpBuilder.createIntegerConstant( |
| clauseLocation, firOpBuilder.getIndexType(), 1); |
| for (auto &v : subscriptIndices) |
| v = mlir::arith::SubIOp::create(firOpBuilder, clauseLocation, v, |
| one); |
| curValue = fir::CoordinateOp::create( |
| firOpBuilder, clauseLocation, |
| firOpBuilder.getRefType(arrType.getEleTy()), curValue, |
| subscriptIndices); |
| } |
| } |
| } |
| |
| // If we encounter a record type, we must access the subsequent member |
| // by indexing into it and creating a coordinate operation to do so, we |
| // utilise the index information generated previously and passed in to |
| // work out the correct member to access and the corresponding member |
| // type. |
| if (fir::RecordType recordType = mlir::dyn_cast<fir::RecordType>( |
| fir::unwrapPassByRefType(curValue.getType()))) { |
| fir::IntOrValue idxConst = mlir::IntegerAttr::get( |
| firOpBuilder.getI32Type(), indices[currentIndicesIdx]); |
| mlir::Type memberTy = recordType.getType(indices[currentIndicesIdx]); |
| curValue = fir::CoordinateOp::create( |
| firOpBuilder, clauseLocation, firOpBuilder.getRefType(memberTy), |
| curValue, llvm::SmallVector<fir::IntOrValue, 1>{idxConst}); |
| |
| // If we're a final member, the map will be generated by the processMap |
| // call that invoked this function. |
| if (currentIndicesIdx == indices.size() - 1) |
| break; |
| |
| // Skip mapping and the subsequent load if we're not |
| // a type with a descriptor such as a pointer/allocatable. If we're not a |
| // type with a descriptor then we have no need of generating an |
| // intermediate map for it, as we only need to generate a map if a member |
| // is a descriptor type (and thus obscures the members it contains via a |
| // pointer in which it's data needs mapped). |
| if (!fir::isTypeWithDescriptor(memberTy)) { |
| currentIndicesIdx++; |
| continue; |
| } |
| |
| llvm::SmallVector<int64_t> interimIndices( |
| indices.begin(), std::next(indices.begin(), currentIndicesIdx + 1)); |
| // Verify we haven't already created a map for this particular member, by |
| // checking the list of members already mapped for the current parent, |
| // stored in the parentMemberIndices structure |
| if (!parentMemberIndices.isDuplicateMemberMapInfo(interimIndices)) { |
| // Generate bounds operations using the standard lowering utility, |
| // unfortunately this currently does a bit more than just generate |
| // bounds and we discard the other bits. May be useful to extend the |
| // utility to just provide bounds in the future. |
| llvm::SmallVector<mlir::Value> interimBounds; |
| if (i + 1 < objectList.size() && |
| objectList[i + 1].sym()->IsObjectArray()) { |
| std::stringstream interimFortran; |
| Fortran::lower::gatherDataOperandAddrAndBounds< |
| mlir::omp::MapBoundsOp, mlir::omp::MapBoundsType>( |
| converter, converter.getFirOpBuilder(), semaCtx, |
| converter.getFctCtx(), *objectList[i + 1].sym(), |
| objectList[i + 1].ref(), clauseLocation, interimFortran, |
| interimBounds, treatIndexAsSection); |
| } |
| |
| // Remove all map-type bits (e.g. TO, FROM, etc.) from the intermediate |
| // allocatable maps, as we simply wish to alloc or release them. It may |
| // be safer to just pass OMP_MAP_NONE as the map type, but we may still |
| // need some of the other map types the mapped member utilises, so for |
| // now it's good to keep an eye on this. |
| mlir::omp::ClauseMapFlags interimMapType = mapTypeBits; |
| interimMapType &= ~mlir::omp::ClauseMapFlags::to; |
| interimMapType &= ~mlir::omp::ClauseMapFlags::from; |
| interimMapType &= ~mlir::omp::ClauseMapFlags::return_param; |
| // We do not want to carry over the separation of descriptor and pointer |
| // mapping of any intermediate components we emit maps for as this can |
| // result in very odd differing behaviour when either ref_ptr/ptee is |
| // specified. |
| interimMapType &= ~mlir::omp::ClauseMapFlags::ref_ptr; |
| interimMapType &= ~mlir::omp::ClauseMapFlags::ref_ptee; |
| |
| // Create a map for the intermediate member and insert it and it's |
| // indices into the parentMemberIndices list to track it. |
| mlir::omp::MapInfoOp mapOp = utils::openmp::createMapInfoOp( |
| firOpBuilder, clauseLocation, curValue, |
| /*varPtrPtr=*/mlir::Value{}, asFortran, |
| /*bounds=*/interimBounds, |
| /*members=*/{}, |
| /*membersIndex=*/mlir::ArrayAttr{}, interimMapType, |
| mlir::omp::VariableCaptureKind::ByRef, curValue.getType()); |
| |
| parentMemberIndices.memberPlacementIndices.push_back(interimIndices); |
| parentMemberIndices.memberMap.push_back(mapOp); |
| } |
| |
| // Load the currently accessed member, so we can continue to access |
| // further segments. |
| curValue = fir::LoadOp::create(firOpBuilder, clauseLocation, curValue); |
| currentIndicesIdx++; |
| } |
| } |
| return curValue; |
| } |
| |
| static int64_t |
| getComponentPlacementInParent(const semantics::Symbol *componentSym) { |
| const auto *derived = componentSym->owner() |
| .derivedTypeSpec() |
| ->typeSymbol() |
| .detailsIf<semantics::DerivedTypeDetails>(); |
| assert(derived && |
| "expected derived type details when processing component symbol"); |
| for (auto [placement, name] : llvm::enumerate(derived->componentNames())) |
| if (name == componentSym->name()) |
| return placement; |
| return -1; |
| } |
| |
| static std::optional<Object> |
| getComponentObject(std::optional<Object> object, |
| semantics::SemanticsContext &semaCtx) { |
| if (!object) |
| return std::nullopt; |
| |
| auto ref = evaluate::ExtractDataRef(object.value().ref()); |
| if (!ref) |
| return std::nullopt; |
| |
| if (std::holds_alternative<evaluate::Component>(ref->u)) |
| return object; |
| |
| auto baseObj = getBaseObject(object.value(), semaCtx); |
| if (!baseObj) |
| return std::nullopt; |
| |
| return getComponentObject(baseObj.value(), semaCtx); |
| } |
| |
| void generateMemberPlacementIndices(const Object &object, |
| llvm::SmallVectorImpl<int64_t> &indices, |
| semantics::SemanticsContext &semaCtx) { |
| assert(indices.empty() && "indices vector passed to " |
| "generateMemberPlacementIndices should be empty"); |
| auto compObj = getComponentObject(object, semaCtx); |
| |
| while (compObj) { |
| int64_t index = getComponentPlacementInParent(compObj->sym()); |
| assert( |
| index >= 0 && |
| "unexpected index value returned from getComponentPlacementInParent"); |
| indices.push_back(index); |
| compObj = |
| getComponentObject(getBaseObject(compObj.value(), semaCtx), semaCtx); |
| } |
| |
| indices = llvm::SmallVector<int64_t>{llvm::reverse(indices)}; |
| } |
| |
| void OmpMapParentAndMemberData::addChildIndexAndMapToParent( |
| const omp::Object &object, mlir::omp::MapInfoOp &mapOp, |
| semantics::SemanticsContext &semaCtx) { |
| llvm::SmallVector<int64_t> indices; |
| generateMemberPlacementIndices(object, indices, semaCtx); |
| memberPlacementIndices.push_back(indices); |
| memberMap.push_back(mapOp); |
| } |
| |
| bool isMemberOrParentAllocatableOrPointer( |
| const Object &object, semantics::SemanticsContext &semaCtx) { |
| if (semantics::IsAllocatableOrObjectPointer(object.sym())) |
| return true; |
| |
| auto compObj = getBaseObject(object, semaCtx); |
| while (compObj) { |
| if (semantics::IsAllocatableOrObjectPointer(compObj.value().sym())) |
| return true; |
| compObj = getBaseObject(compObj.value(), semaCtx); |
| } |
| |
| return false; |
| } |
| |
| void insertChildMapInfoIntoParent( |
| lower::AbstractConverter &converter, semantics::SemanticsContext &semaCtx, |
| lower::StatementContext &stmtCtx, |
| std::map<Object, OmpMapParentAndMemberData> &parentMemberIndices, |
| llvm::SmallVectorImpl<mlir::Value> &mapOperands, |
| llvm::SmallVectorImpl<Object> &mapObjects) { |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); |
| for (auto indices : parentMemberIndices) { |
| auto *parentIter = |
| llvm::find_if(mapObjects, [&indices](const Object &object) { |
| return object.sym() == indices.first.sym(); |
| }); |
| if (parentIter != mapObjects.end()) { |
| auto mapOp = llvm::cast<mlir::omp::MapInfoOp>( |
| mapOperands[std::distance(mapObjects.begin(), parentIter)] |
| .getDefiningOp()); |
| |
| // Once explicit members are attached to a parent map, do not also invoke |
| // a declare mapper on it, otherwise the mapper would remap the same |
| // components leading to duplicate mappings at runtime. |
| if (!indices.second.memberMap.empty() && mapOp.getMapperIdAttr()) |
| mapOp.setMapperIdAttr(nullptr); |
| |
| // NOTE: To maintain appropriate SSA ordering, we move the parent map |
| // which will now have references to its children after the last |
| // of its members to be generated. This is necessary when a user |
| // has defined a series of parent and children maps where the parent |
| // precedes the children. An alternative, may be to do |
| // delayed generation of map info operations from the clauses and |
| // organize them first before generation. Or to use the |
| // topologicalSort utility which will enforce a stronger SSA |
| // dominance ordering at the cost of efficiency/time. |
| mapOp->moveAfter(indices.second.memberMap.back()); |
| |
| for (mlir::omp::MapInfoOp memberMap : indices.second.memberMap) |
| mapOp.getMembersMutable().append(memberMap.getResult()); |
| |
| mapOp.setMembersIndexAttr(firOpBuilder.create2DI64ArrayAttr( |
| indices.second.memberPlacementIndices)); |
| } else { |
| // NOTE: We do not assign default mapped parents a map type, as |
| // selecting a child can result in the incorrect map type being |
| // applied to the parent and data being incorrectly moved to or |
| // from device. We make an exception currently for present. |
| mlir::omp::ClauseMapFlags mapType = mlir::omp::ClauseMapFlags::storage; |
| |
| for (mlir::omp::MapInfoOp memberMap : indices.second.memberMap) |
| if ((memberMap.getMapType() & mlir::omp::ClauseMapFlags::present) == |
| mlir::omp::ClauseMapFlags::present) |
| mapType |= mlir::omp::ClauseMapFlags::present; |
| |
| llvm::SmallVector<mlir::Value> members; |
| members.reserve(indices.second.memberMap.size()); |
| for (mlir::omp::MapInfoOp memberMap : indices.second.memberMap) |
| members.push_back(memberMap.getResult()); |
| |
| // Create parent to emplace and bind members |
| llvm::SmallVector<mlir::Value> bounds; |
| std::stringstream asFortran; |
| fir::factory::AddrAndBoundsInfo info = |
| lower::gatherDataOperandAddrAndBounds<mlir::omp::MapBoundsOp, |
| mlir::omp::MapBoundsType>( |
| converter, firOpBuilder, semaCtx, converter.getFctCtx(), |
| *indices.first.sym(), indices.first.ref(), |
| converter.getCurrentLocation(), asFortran, bounds, |
| treatIndexAsSection); |
| |
| mlir::omp::MapInfoOp mapOp = utils::openmp::createMapInfoOp( |
| firOpBuilder, info.rawInput.getLoc(), info.rawInput, |
| /*varPtrPtr=*/mlir::Value(), asFortran.str(), bounds, members, |
| firOpBuilder.create2DI64ArrayAttr( |
| indices.second.memberPlacementIndices), |
| mapType, mlir::omp::VariableCaptureKind::ByRef, |
| info.rawInput.getType(), |
| /*partialMap=*/true); |
| |
| mapOperands.push_back(mapOp); |
| mapObjects.push_back(indices.first); |
| } |
| } |
| } |
| |
| void lastprivateModifierNotSupported(const omp::clause::Lastprivate &lastp, |
| mlir::Location loc) { |
| using Lastprivate = omp::clause::Lastprivate; |
| auto &maybeMod = |
| std::get<std::optional<Lastprivate::LastprivateModifier>>(lastp.t); |
| if (maybeMod) { |
| assert(*maybeMod == Lastprivate::LastprivateModifier::Conditional && |
| "Unexpected lastprivate modifier"); |
| TODO(loc, "lastprivate clause with CONDITIONAL modifier"); |
| } |
| } |
| |
| static void convertLoopBounds(lower::AbstractConverter &converter, |
| mlir::Location loc, |
| mlir::omp::LoopRelatedClauseOps &result, |
| std::size_t loopVarTypeSize) { |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); |
| // The types of lower bound, upper bound, and step are converted into the |
| // type of the loop variable if necessary. |
| mlir::Type loopVarType = getLoopVarType(converter, loopVarTypeSize); |
| for (unsigned it = 0; it < (unsigned)result.loopLowerBounds.size(); it++) { |
| result.loopLowerBounds[it] = firOpBuilder.createConvert( |
| loc, loopVarType, result.loopLowerBounds[it]); |
| result.loopUpperBounds[it] = firOpBuilder.createConvert( |
| loc, loopVarType, result.loopUpperBounds[it]); |
| result.loopSteps[it] = |
| firOpBuilder.createConvert(loc, loopVarType, result.loopSteps[it]); |
| } |
| } |
| |
| // Helper function that finds the sizes clause in a inner OMPD_tile directive |
| // and passes the sizes clause to the callback function if found. |
| static void processTileSizesFromOpenMPConstruct( |
| const parser::OpenMPConstruct *ompCons, |
| std::function<void(const parser::OmpClause::Sizes *)> processFun) { |
| if (!ompCons) |
| return; |
| if (auto *ompLoop{std::get_if<parser::OpenMPLoopConstruct>(&ompCons->u)}) { |
| if (auto *innerConstruct = ompLoop->GetNestedConstruct()) { |
| const parser::OmpDirectiveSpecification &innerBeginSpec = |
| innerConstruct->BeginDir(); |
| if (innerBeginSpec.DirId() == llvm::omp::Directive::OMPD_tile) { |
| // Get the size values from parse tree and convert to a vector. |
| if (auto *clause = parser::omp::FindClause( |
| innerBeginSpec, llvm::omp::Clause::OMPC_sizes)) |
| processFun(&std::get<parser::OmpClause::Sizes>(clause->u)); |
| } |
| } |
| } |
| } |
| |
| // Searches the nested evaluations of \p eval for the next DO construct of a |
| // loop nest, returning nullptr when there is none left to descend into. This |
| // can happen when COLLAPSE counts loops that a transforming construct such as |
| // TILE generates from the source DO loops. getNestedDoConstruct wraps this for |
| // callers that require a DO construct and asserts when none is found. |
| static pft::Evaluation *tryGetNestedDoConstruct(pft::Evaluation &eval) { |
| for (pft::Evaluation &nested : eval.getNestedEvaluations()) { |
| // In an OpenMPConstruct there can be compiler directives: |
| // 1 <<OpenMPConstruct>> |
| // 2 CompilerDirective: !unroll |
| // <<DoConstruct>> -> 8 |
| if (nested.getIf<parser::CompilerDirective>()) |
| continue; |
| // Within a DoConstruct, there can be compiler directives, plus |
| // there is a DoStmt before the body: |
| // <<DoConstruct>> -> 8 |
| // 3 NonLabelDoStmt -> 7: do i = 1, n |
| // <<DoConstruct>> -> 7 |
| if (nested.getIf<parser::NonLabelDoStmt>()) |
| continue; |
| if (nested.getIf<parser::DoConstruct>()) |
| return &nested; |
| // Loop transformations can introduce nested OpenMP |
| // constructs between the directive and the actual do-loop nest. |
| if (nested.getIf<parser::OpenMPConstruct>()) |
| return tryGetNestedDoConstruct(nested); |
| // Skip valid intervening code in imperfect loop nests |
| continue; |
| } |
| return nullptr; |
| } |
| |
| pft::Evaluation *getNestedDoConstruct(pft::Evaluation &eval) { |
| pft::Evaluation *doConstruct{tryGetNestedDoConstruct(eval)}; |
| if (!doConstruct) |
| llvm_unreachable("Expected do loop to be in the nested evaluations"); |
| return doConstruct; |
| } |
| |
| /// Populates the sizes vector with values if the given OpenMPConstruct |
| /// contains a loop construct with an inner tiling construct. |
| void collectTileSizesFromOpenMPConstruct( |
| const parser::OpenMPConstruct *ompCons, |
| llvm::SmallVectorImpl<int64_t> &tileSizes, |
| Fortran::semantics::SemanticsContext &semaCtx) { |
| processTileSizesFromOpenMPConstruct( |
| ompCons, [&](const parser::OmpClause::Sizes *tclause) { |
| for (auto &tval : tclause->v) |
| if (const auto v{EvaluateInt64(semaCtx, tval)}) |
| tileSizes.push_back(*v); |
| }); |
| } |
| |
| int64_t collectLoopRelatedInfo( |
| lower::AbstractConverter &converter, mlir::Location currentLocation, |
| lower::pft::Evaluation &eval, lower::pft::Evaluation *nestedEval, |
| const omp::List<omp::Clause> &clauses, |
| mlir::omp::LoopRelatedClauseOps &result, |
| llvm::SmallVectorImpl<const semantics::Symbol *> &iv) { |
| int64_t numCollapse = 1; |
| |
| // Collect the loops to collapse. |
| lower::pft::Evaluation *doConstructEval = nestedEval; |
| if (doConstructEval->getIf<parser::DoConstruct>()->IsDoConcurrent()) { |
| TODO(currentLocation, "Do Concurrent in Worksharing loop construct"); |
| } |
| |
| std::int64_t collapseValue = 1l; |
| if (auto *clause = |
| ClauseFinder::findUniqueClause<omp::clause::Collapse>(clauses)) { |
| collapseValue = evaluate::ToInt64(clause->v).value(); |
| numCollapse = collapseValue; |
| } |
| |
| collectLoopRelatedInfo(converter, currentLocation, eval, nestedEval, |
| numCollapse, result, iv); |
| return numCollapse; |
| } |
| |
| void collectLoopRelatedInfo( |
| lower::AbstractConverter &converter, mlir::Location currentLocation, |
| lower::pft::Evaluation &eval, lower::pft::Evaluation *nestedEval, |
| int64_t numCollapse, mlir::omp::LoopRelatedClauseOps &result, |
| llvm::SmallVectorImpl<const semantics::Symbol *> &iv) { |
| |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); |
| |
| // Collect the loops to collapse. |
| lower::pft::Evaluation *doConstructEval = nestedEval; |
| if (doConstructEval->getIf<parser::DoConstruct>()->IsDoConcurrent()) { |
| TODO(currentLocation, "Do Concurrent in Worksharing loop construct"); |
| } |
| |
| // Collect sizes from tile directive if present. |
| std::int64_t sizesLengthValue = 0l; |
| if (auto *ompCons{eval.getIf<parser::OpenMPConstruct>()}) { |
| processTileSizesFromOpenMPConstruct( |
| ompCons, [&](const parser::OmpClause::Sizes *tclause) { |
| sizesLengthValue = tclause->v.size(); |
| }); |
| } |
| |
| std::int64_t collapseValue = std::max(numCollapse, sizesLengthValue); |
| std::size_t loopVarTypeSize = 0; |
| do { |
| lower::pft::Evaluation *doLoop = |
| &doConstructEval->getFirstNestedEvaluation(); |
| auto *doStmt = doLoop->getIf<parser::NonLabelDoStmt>(); |
| assert(doStmt && "Expected do loop to be in the nested evaluation"); |
| const auto &loopControl = |
| std::get<std::optional<parser::LoopControl>>(doStmt->t); |
| const parser::LoopControl::Bounds *bounds = |
| std::get_if<parser::LoopControl::Bounds>(&loopControl->u); |
| assert(bounds && "Expected bounds for worksharing do loop"); |
| // Check for non-rectangular loops: if any bound of this loop references |
| // an iteration variable from an enclosing collapsed loop, this is a |
| // non-rectangular loop nest which is not yet supported by the |
| // OpenMPIRBuilder's collapseLoops. |
| if (!iv.empty()) { |
| auto referencesOuterIV = [&iv](const semantics::SomeExpr &expr) -> bool { |
| for (const semantics::SymbolRef &sym : |
| evaluate::GetSymbolVector(expr)) { |
| for (const semantics::Symbol *outerIV : iv) { |
| if (&sym->GetUltimate() == &outerIV->GetUltimate()) |
| return true; |
| } |
| } |
| return false; |
| }; |
| if (const auto *lowerExpr = semantics::GetExpr(bounds->Lower())) |
| if (referencesOuterIV(*lowerExpr)) |
| TODO(currentLocation, |
| "Non-rectangular loop nests with COLLAPSE are not yet " |
| "supported"); |
| if (const auto *upperExpr = semantics::GetExpr(bounds->Upper())) |
| if (referencesOuterIV(*upperExpr)) |
| TODO(currentLocation, |
| "Non-rectangular loop nests with COLLAPSE are not yet " |
| "supported"); |
| if (auto &step = bounds->Step()) |
| if (const auto *stepExpr = semantics::GetExpr(step)) |
| if (referencesOuterIV(*stepExpr)) |
| TODO(currentLocation, |
| "Non-rectangular loop nests with COLLAPSE are not yet " |
| "supported"); |
| } |
| lower::StatementContext stmtCtx; |
| result.loopLowerBounds.push_back(fir::getBase( |
| converter.genExprValue(*semantics::GetExpr(bounds->Lower()), stmtCtx))); |
| result.loopUpperBounds.push_back(fir::getBase( |
| converter.genExprValue(*semantics::GetExpr(bounds->Upper()), stmtCtx))); |
| if (auto &step = bounds->Step()) { |
| result.loopSteps.push_back(fir::getBase( |
| converter.genExprValue(*semantics::GetExpr(step), stmtCtx))); |
| } else { // If `step` is not present, assume it as `1`. |
| result.loopSteps.push_back(firOpBuilder.createIntegerConstant( |
| currentLocation, firOpBuilder.getIntegerType(32), 1)); |
| } |
| iv.push_back(bounds->Name().thing.symbol); |
| loopVarTypeSize = std::max( |
| loopVarTypeSize, bounds->Name().thing.symbol->GetUltimate().size()); |
| if (--collapseValue) { |
| doConstructEval = tryGetNestedDoConstruct(*doConstructEval); |
| // No DO construct left to descend into: COLLAPSE (or a tile SIZES list) |
| // counts loops generated by a nested loop-transforming construct. Not |
| // yet supported in lowering. |
| if (!doConstructEval) |
| TODO(currentLocation, |
| "Collapsing a loop nest that contains a loop-transforming " |
| "construct"); |
| } |
| } while (collapseValue > 0); |
| |
| convertLoopBounds(converter, currentLocation, result, loopVarTypeSize); |
| } |
| |
| // Lower an affinity object to the raw storage address. |
| // The lowering paths feeding this helper are mixed: some produce HLFIR |
| // entities such as hlfir.designate/hlfir.declare, while others already |
| // produce raw FIR addresses such as fir.box_addr. Normalize entity-like values |
| // to a raw address, and leave already-raw addresses unchanged. |
| mlir::Value genAffinityAddr(Fortran::lower::AbstractConverter &converter, |
| const omp::Object &object, |
| Fortran::lower::StatementContext &stmtCtx, |
| mlir::Location loc) { |
| fir::FirOpBuilder &builder = converter.getFirOpBuilder(); |
| |
| auto genRawAddress = [&](mlir::Value v) -> mlir::Value { |
| // Examples seen here include hlfir.designate for a(i), hlfir.declare for |
| // whole objects like dummy/character arrays, fir.load of a pointer box, |
| // and already-raw fir.box_addr results. Only the entity-like cases can be |
| // wrapped as hlfir::Entity; the raw address cases must be returned as-is. |
| if (!hlfir::isFortranEntity(v)) |
| return v; |
| |
| hlfir::Entity entity{v}; |
| // Pointer/allocatable entities need to be dereferenced first so affinity |
| // uses the pointee storage rather than the box address. |
| entity = hlfir::derefPointersAndAllocatables(loc, builder, entity); |
| return hlfir::genVariableRawAddress(loc, builder, entity); |
| }; |
| |
| // Designators such as affinity(a(3)) or affinity(a(1:10)) lower through |
| // genExprAddr. The base may still be an HLFIR entity, or may already be a |
| // raw FIR address after earlier lowering. |
| if (auto expr = object.ref()) { |
| fir::ExtendedValue exv = |
| converter.genExprAddr(toEvExpr(*expr), stmtCtx, &loc); |
| mlir::Value baseAddr = fir::getBase(exv); |
| return genRawAddress(baseAddr); |
| } |
| |
| // Whole objects such as affinity(a) come from the symbol address directly. |
| const Fortran::semantics::Symbol *sym = object.sym(); |
| assert(sym && "expected symbol in affinity object"); |
| mlir::Value symAddr = converter.getSymbolAddress(*sym); |
| return genRawAddress(symAddr); |
| } |
| |
| // Compute the size in bytes of a single element described by an HLFIR entity. |
| // This returns the per-element byte size only; callers handle any array extent |
| // or section span separately. |
| mlir::Value genElementSizeInBytes(fir::FirOpBuilder &builder, |
| mlir::Location loc, |
| const mlir::DataLayout &dl, |
| hlfir::Entity entity) { |
| // Boxed entities carry the runtime element size in the descriptor. |
| if (entity.isBoxAddressOrValue()) |
| return fir::ConvertOp::create( |
| builder, loc, builder.getI64Type(), |
| fir::BoxEleSizeOp::create(builder, loc, builder.getIndexType(), |
| entity)); |
| |
| mlir::Type elemTy = entity.getFortranElementType(); |
| |
| if (auto charTy = mlir::dyn_cast<fir::CharacterType>(elemTy)) { |
| // Non-box character entities expose length separately; multiply it by the |
| // character kind byte width. |
| mlir::Value charLen = hlfir::genCharLength(loc, builder, entity); |
| mlir::Value charBytes = builder.createIntegerConstant( |
| loc, builder.getI64Type(), charTy.getFKind()); |
| return mlir::arith::MulIOp::create( |
| builder, loc, |
| fir::ConvertOp::create(builder, loc, builder.getI64Type(), charLen), |
| charBytes); |
| } |
| |
| // PDTs with length parameters and assumed-rank entities do not currently |
| // have a precise byte size here, so keep the existing conservative 0. |
| if (fir::isRecordWithTypeParameters(elemTy) || entity.isAssumedRank()) |
| return builder.createIntegerConstant(loc, builder.getI64Type(), 0); |
| |
| // Trivial non-box entities have a fixed element size in the data layout. |
| return builder.createIntegerConstant( |
| loc, builder.getI64Type(), static_cast<int64_t>(dl.getTypeSize(elemTy))); |
| } |
| |
| // Compute the total number of elements in a whole affinity object. |
| static mlir::Value getTotalElements(fir::FirOpBuilder &builder, |
| mlir::Location loc, hlfir::Entity entity) { |
| if (entity.isAssumedRank()) |
| return builder.createIntegerConstant(loc, builder.getI64Type(), 0); |
| |
| assert(!entity.isScalar() && |
| "expected non-scalar entity to compute total elements"); |
| |
| mlir::Value total = |
| builder.createIntegerConstant(loc, builder.getIndexType(), 1); |
| for (mlir::Value extent : hlfir::genExtentsVector(loc, builder, entity)) |
| total = mlir::arith::MulIOp::create(builder, loc, total, extent); |
| return fir::ConvertOp::create(builder, loc, builder.getI64Type(), total); |
| } |
| |
| // Compute the contiguous element span covered by an array section. |
| // This is not the number of selected elements. Instead, it is the inclusive |
| // distance from the lowest addressed element in the section to the highest |
| // addressed element, using Fortran column-major layout. genAffinityLen later |
| // multiplies this span by the element size to get the byte length. |
| // |
| // For each dimension d: |
| // delta_d = upper_d - lower_d |
| // distance_d = product(fullExtents[0..d-1]) |
| // with distance_0 = 1. |
| // |
| // Example: |
| // integer :: a(5, 7) |
| // !$omp task affinity(a(2:4, 3:5)) |
| // The section selects 9 elements, but its contiguous span runs from a(2,3) to |
| // a(4,5). In linearized column-major indices, those are 11 and 23, so the |
| // span is 23 - 11 + 1 = 13 elements. |
| // |
| // Strides in the section bounds do not change this computation: the span still |
| // covers the full contiguous address range between the first and last element. |
| static mlir::Value computeBoundsSpan(fir::FirOpBuilder &builder, |
| mlir::Location loc, |
| llvm::ArrayRef<mlir::Value> bounds, |
| hlfir::Entity entity) { |
| assert(!bounds.empty() && "expected non-empty bounds to compute span"); |
| auto fullExtents = hlfir::genExtentsVector(loc, builder, entity); |
| assert(fullExtents.size() == bounds.size() && |
| "expected bounds and full extents to have the same size"); |
| mlir::Value one = |
| builder.createIntegerConstant(loc, builder.getIndexType(), 1); |
| mlir::Value span = one; // inclusive: +1 |
| mlir::Value distance = one; // column-major linearization factor |
| for (auto [b, extent] : llvm::zip(bounds, fullExtents)) { |
| auto mb = b.getDefiningOp<mlir::omp::MapBoundsOp>(); |
| assert(mb && "expected omp.map_bounds for affinity section span"); |
| mlir::Value delta = mlir::arith::SubIOp::create( |
| builder, loc, mb.getUpperBound(), mb.getLowerBound()); |
| |
| span = mlir::arith::AddIOp::create( |
| builder, loc, span, |
| mlir::arith::MulIOp::create(builder, loc, delta, distance)); |
| |
| distance = mlir::arith::MulIOp::create(builder, loc, distance, extent); |
| } |
| // Convert from index to i64 (bounds are in index type) |
| return fir::ConvertOp::create(builder, loc, builder.getI64Type(), span); |
| } |
| |
| // Compute the byte length covered by an affinity object. |
| // For a scalar or single element, this is the element size. For a section, it |
| // is the span of the section in elements multiplied by the element size. For a |
| // whole array object, it is the total number of elements multiplied by the |
| // element size. |
| mlir::Value genAffinityLen(fir::FirOpBuilder &builder, mlir::Location loc, |
| const mlir::DataLayout &dl, hlfir::Entity entity, |
| llvm::ArrayRef<mlir::Value> bounds) { |
| mlir::Value elemBytes = genElementSizeInBytes(builder, loc, dl, entity); |
| |
| // Scalar entities and single designated elements contribute exactly one |
| // element to the affinity object. |
| if (entity.isScalar()) |
| return elemBytes; |
| |
| if (!bounds.empty()) { |
| // Array sections carry explicit bounds describing the covered span. |
| mlir::Value spanElems = computeBoundsSpan(builder, loc, bounds, entity); |
| return mlir::arith::MulIOp::create(builder, loc, spanElems, elemBytes); |
| } |
| |
| // Whole-array objects have no explicit bounds here, so use the extents of |
| // the entity itself. |
| return mlir::arith::MulIOp::create( |
| builder, loc, getTotalElements(builder, loc, entity), elemBytes); |
| } |
| |
| bool hasIteratorIVReference( |
| const omp::Object &object, |
| const llvm::SmallPtrSetImpl<const Fortran::semantics::Symbol *> &ivSyms) { |
| auto ref = object.ref(); |
| if (!ref) |
| return false; |
| |
| Fortran::lower::SomeExpr expr = toEvExpr(*ref); |
| |
| for (Fortran::evaluate::SymbolRef s : CollectSymbols(expr)) { |
| const Fortran::semantics::Symbol &ult = s->GetUltimate(); |
| if (ivSyms.contains(&ult)) |
| return true; |
| } |
| return false; |
| } |
| |
| void defaultMangler(Fortran::lower::AbstractConverter &converter, |
| std::string &mapperIdName, llvm::StringRef memberName) { |
| if (auto *sym = converter.getCurrentScope().FindSymbol(mapperIdName)) |
| mapperIdName = converter.mangleName(mapperIdName, sym->owner()); |
| else if (auto *memberSym = |
| converter.getCurrentScope().FindSymbol(memberName.str())) |
| mapperIdName = converter.mangleName(mapperIdName, memberSym->owner()); |
| } |
| |
| static const semantics::DerivedTypeSpec * |
| getSymbolDerivedType(const semantics::Symbol &symbol) { |
| const semantics::Symbol &ultimate = symbol.GetUltimate(); |
| if (const semantics::DeclTypeSpec *declType = ultimate.GetType()) |
| if (const auto *derived = declType->AsDerived()) |
| return derived; |
| return nullptr; |
| } |
| |
| static std::string |
| getDefaultMapperID(Fortran::lower::AbstractConverter &converter, |
| fir::FirOpBuilder &firOpBuilder, |
| const semantics::DerivedTypeSpec *typeSpec) { |
| // Nested derived-type components inside a declare mapper may use their own |
| // default mapper. Only suppress the mapper currently being built. |
| if (!typeSpec) |
| return {}; |
| |
| std::string mapperIdName = |
| typeSpec->name().ToString() + llvm::omp::OmpDefaultMapperName; |
| if (auto *sym = converter.getCurrentScope().FindSymbol(mapperIdName)) { |
| mapperIdName = |
| converter.mangleName(mapperIdName, sym->GetUltimate().owner()); |
| } else { |
| mapperIdName = converter.mangleName(mapperIdName, *typeSpec->GetScope()); |
| } |
| |
| // Make sure we don't return a mapper to self. |
| if (auto declMapOp = mlir::dyn_cast<mlir::omp::DeclareMapperOp>( |
| firOpBuilder.getRegion().getParentOp())) |
| if (mapperIdName == declMapOp.getSymName()) |
| return {}; |
| return mapperIdName; |
| } |
| |
| static std::string |
| findMapperIfTypeMatch(Fortran::lower::AbstractConverter &converter, |
| const semantics::DerivedTypeSpec *objectTypeSpec, |
| llvm::StringRef explicitMapperName) { |
| auto declMapperOp = |
| converter.getModuleOp().lookupSymbol<mlir::omp::DeclareMapperOp>( |
| explicitMapperName); |
| if (!declMapperOp) |
| return "__implicit_mapper"; |
| |
| // Verify if the explicit mapper provided matches the type being mapped. |
| // If it does return the mapper name, if it doesn't return null-ary. |
| mlir::Type mapperType = declMapperOp.getType(); |
| mlir::Type objectType = converter.genType(*objectTypeSpec); |
| auto mapperRecordType = mlir::dyn_cast<fir::RecordType>(mapperType); |
| auto objectRecordType = mlir::dyn_cast<fir::RecordType>(objectType); |
| if (mapperRecordType && objectRecordType && |
| mapperRecordType.getName() == objectRecordType.getName()) |
| return explicitMapperName.str(); |
| |
| return "__implicit_mapper"; |
| } |
| |
| static mlir::FlatSymbolRefAttr |
| addImplicitMapper(Fortran::lower::AbstractConverter &converter, |
| mlir::Location loc, const omp::Object &object, |
| std::string &mapperIdName, bool allowGenerate) { |
| if (!allowGenerate || mapperIdName.empty()) |
| return mlir::FlatSymbolRefAttr(); |
| |
| const semantics::DerivedTypeSpec *typeSpec = |
| getSymbolDerivedType(*object.sym()); |
| if (!typeSpec && object.sym()->owner().IsDerivedType()) |
| typeSpec = object.sym()->owner().derivedTypeSpec(); |
| |
| if (!typeSpec) |
| return mlir::FlatSymbolRefAttr(); |
| |
| mlir::Type type = converter.genType(*typeSpec); |
| auto recordType = mlir::dyn_cast<fir::RecordType>(type); |
| if (!recordType) |
| return mlir::FlatSymbolRefAttr(); |
| |
| return utils::openmp::getOrGenImplicitDefaultDeclareMapper( |
| converter.getFirOpBuilder(), loc, recordType, mapperIdName, |
| [&](std::string &mapperIdName, llvm::StringRef memberName) { |
| defaultMangler(converter, mapperIdName, memberName); |
| }); |
| } |
| |
| mlir::FlatSymbolRefAttr |
| resolveMapperId(Fortran::lower::AbstractConverter &converter, |
| mlir::Location loc, const omp::Object &object, |
| llvm::StringRef mapperIdNameRef, |
| mlir::omp::ClauseMapFlags mapTypeBits, |
| llvm::omp::Directive directive, bool hasParentObj) { |
| const semantics::DerivedTypeSpec *objectTypeSpec = |
| getSymbolDerivedType(*object.sym()); |
| if (!objectTypeSpec) |
| return mlir::FlatSymbolRefAttr(); |
| |
| fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); |
| mlir::FlatSymbolRefAttr mapperId; |
| std::string mapperIdName = mapperIdNameRef.str(); |
| // if we have an explicit mapper specified, we need to check it matches |
| // the type being mapped, if it doesn't we fallback to look for a user |
| // default mapper or generate an compiler defined default mapper if |
| // relevant. This function will return "__implicit_mapper" if we find that |
| // the map isn't relevant to the explicit declare mapper, which allows it |
| // to fallback. |
| if (!mapperIdName.empty() && mapperIdName != "__implicit_mapper") |
| mapperIdName = |
| findMapperIfTypeMatch(converter, objectTypeSpec, mapperIdName); |
| |
| if (mapperIdName == "__implicit_mapper") { |
| mapperIdName = getDefaultMapperID(converter, firOpBuilder, objectTypeSpec); |
| // Currently we do not apply implicit compiler generated delcare mappers |
| // to enter, exit or update directives. However, we will syntheize one |
| // below if we're not a target enter/exit/update and no user defined |
| // implicit declare mapper has been defined and we meet the other |
| // conditions |
| // TODO/FIXME: Loosen this restriction to comply with the OpenMP |
| // specification. |
| auto *userDefinedDefault = |
| converter.getModuleOp().lookupSymbol(mapperIdName); |
| if (!userDefinedDefault && !hasParentObj && |
| (directive != llvm::omp::Directive::OMPD_target_enter_data && |
| directive != llvm::omp::Directive::OMPD_target_exit_data && |
| directive != llvm::omp::Directive::OMPD_target_update)) { |
| bool isAllocOrPointer = |
| semantics::IsAllocatableOrObjectPointer(object.sym()); |
| bool isPointer = semantics::IsPointer(*object.sym()); |
| bool isImplicitMap = |
| (mapTypeBits & mlir::omp::ClauseMapFlags::implicit) == |
| mlir::omp::ClauseMapFlags::implicit; |
| bool needsDefaultMapper = |
| isAllocOrPointer || |
| requiresImplicitDefaultDeclareMapper(*objectTypeSpec); |
| // For implicit captures, avoid synthesizing default mappers for |
| // pointer entities (which can over-map pointer payloads) and for |
| // plain non-allocatable/non-pointer entities. Keep implicit mapper |
| // support for allocatables. |
| if (isImplicitMap && (isPointer || !isAllocOrPointer)) |
| needsDefaultMapper = false; |
| mapperId = addImplicitMapper(converter, loc, object, mapperIdName, |
| /*allowGenerate=*/needsDefaultMapper); |
| } |
| } |
| |
| // Make sure we've generated the symbol in one of our previous steps |
| // before assigning the symbol. |
| if (!mapperIdName.empty() && |
| converter.getModuleOp().lookupSymbol(mapperIdName)) |
| mapperId = |
| mlir::FlatSymbolRefAttr::get(&converter.getMLIRContext(), mapperIdName); |
| return mapperId; |
| } |
| |
| // Build the array coordinate for an object that uses iterator variables. |
| // If the object is a section, use the first element of that section |
| // as the coordinate. Currently only support top-level ArrayRef designators. |
| // |
| // Examples: |
| // a(i, j) -> coordinates for a(i, j) |
| // a(i:i+1, j+2) -> coordinates for a(i, j+2) |
| std::optional<llvm::SmallVector<mlir::Value>> getIteratorElementIndices( |
| Fortran::lower::AbstractConverter &converter, const omp::Object &object, |
| Fortran::lower::StatementContext &stmtCtx, mlir::Location loc) { |
| const std::optional<ExprTy> &ref = object.ref(); |
| assert(ref && "expected iterator-dependent object to have a reference"); |
| |
| std::optional<Fortran::evaluate::DataRef> dataRef = |
| Fortran::evaluate::ExtractDataRef(*ref); |
| if (!dataRef) |
| return std::nullopt; |
| const auto *arrayRef = std::get_if<Fortran::evaluate::ArrayRef>(&dataRef->u); |
| if (!arrayRef || arrayRef->subscript().empty()) |
| return std::nullopt; |
| |
| auto &builder = converter.getFirOpBuilder(); |
| const Fortran::semantics::Symbol *sym = object.sym(); |
| assert(sym && "expected symbol for iterator-dependent object"); |
| fir::ExtendedValue dataExv = converter.getSymbolExtendedValue(*sym); |
| mlir::Value one = |
| builder.createIntegerConstant(loc, builder.getIndexType(), 1); |
| llvm::SmallVector<mlir::Value> indices; |
| indices.reserve(arrayRef->subscript().size()); |
| |
| for (const auto &[dim, subscript] : llvm::enumerate(arrayRef->subscript())) { |
| mlir::Value idx; |
| if (const auto *triplet = |
| std::get_if<Fortran::evaluate::Triplet>(&subscript.u)) { |
| // Sections use the first element of the section as the base address, so |
| // the coordinate for this dimension comes from the triplet lower bound. |
| std::optional< |
| Fortran::evaluate::Expr<Fortran::evaluate::SubscriptInteger>> |
| lowerBound = triplet->lower(); |
| if (!lowerBound) { |
| // Get lower bound if not provided by user. |
| // For example: !$omp task affinity(iterator(i = 1:n, j = 1:m) : a(:i+1, |
| // j+2)) |
| idx = fir::factory::readLowerBound(builder, loc, dataExv, dim, one); |
| } else { |
| idx = fir::getBase( |
| converter.genExprValue(toEvExpr(*lowerBound), stmtCtx, &loc)); |
| } |
| } else { |
| // Not handling vector subscripts for now. |
| if (subscript.Rank() > 0) |
| return std::nullopt; |
| |
| const auto *indirect = |
| std::get_if<Fortran::evaluate::IndirectSubscriptIntegerExpr>( |
| &subscript.u); |
| assert(indirect && "expected non-triplet subscript"); |
| |
| // Scalar subscripts, including reordered indices and expressions like |
| // i+1 or j+2, lower directly through expression lowering. |
| idx = fir::getBase( |
| converter.genExprValue(toEvExpr(indirect->value()), stmtCtx, &loc)); |
| } |
| indices.push_back(idx); |
| } |
| |
| return indices; |
| } |
| |
| // Build the element address for an iterator-dependent affinity object from a |
| // base entity and lowered indices. |
| mlir::Value genIteratorCoordinate(Fortran::lower::AbstractConverter &converter, |
| hlfir::Entity entity, |
| llvm::ArrayRef<mlir::Value> ivs, |
| mlir::Location loc) { |
| auto &builder = converter.getFirOpBuilder(); |
| mlir::Value base = entity.getBase(); |
| |
| // If base is a reference-to-box, load it so array_coor sees the box value |
| if (auto refTy = mlir::dyn_cast<fir::ReferenceType>(base.getType())) { |
| if (mlir::isa<fir::BoxType>(refTy.getEleTy())) |
| base = fir::LoadOp::create(builder, loc, base); |
| } |
| |
| // Build shape from the entity extents |
| mlir::Value shape; |
| auto extents = hlfir::genExtentsVector(loc, builder, entity); |
| assert(extents.size() == ivs.size() && |
| "expected the number of extents and iteration variables to match for " |
| "iterator"); |
| if (entity.mayHaveNonDefaultLowerBounds()) { |
| llvm::SmallVector<mlir::Value> lowerBounds; |
| lowerBounds.reserve(ivs.size()); |
| for (unsigned dim = 0; dim < ivs.size(); ++dim) |
| lowerBounds.push_back(hlfir::genLBound(loc, builder, entity, dim)); |
| shape = builder.genShape(loc, lowerBounds, extents); |
| } else { |
| shape = fir::ShapeOp::create(builder, loc, extents); |
| } |
| |
| mlir::Type elementToRefTy = |
| fir::ReferenceType::get(entity.getFortranElementType()); |
| |
| return fir::ArrayCoorOp::create(builder, loc, elementToRefTy, |
| /*memref=*/base, |
| /*shape=*/shape, |
| /*slice=*/mlir::Value{}, |
| /*indices=*/ivs, |
| /*typeparams=*/mlir::ValueRange{}); |
| } |
| |
| // --------------------------------------------------------------------------- |
| // OpenMP variant-matching context construction from an MLIR module |
| // --------------------------------------------------------------------------- |
| |
| static llvm::Triple getOffloadTargetTriple(mlir::ModuleOp module) { |
| auto iface = |
| llvm::cast<mlir::omp::OffloadModuleInterface>(module.getOperation()); |
| auto targetTriples = iface.getTargetTriples(); |
| if (!targetTriples.empty()) |
| if (auto tripleAttr = |
| llvm::dyn_cast<mlir::StringAttr>(targetTriples.front())) |
| return llvm::Triple(tripleAttr.getValue()); |
| return llvm::Triple(); |
| } |
| |
| semantics::omp::OmpVariantMatchContext makeVariantMatchContext( |
| mlir::ModuleOp module, |
| llvm::ArrayRef<llvm::omp::TraitProperty> constructTraits) { |
| bool isDeviceCompilation = |
| llvm::cast<mlir::omp::OffloadModuleInterface>(module.getOperation()) |
| .getIsTargetDevice(); |
| mlir::LLVM::TargetFeaturesAttr features = fir::getTargetFeatures(module); |
| return semantics::omp::OmpVariantMatchContext( |
| isDeviceCompilation, fir::getTargetTriple(module), |
| getOffloadTargetTriple(module), |
| features ? features.getFeaturesString() : std::string{}, constructTraits); |
| } |
| |
| void collectEnclosingConstructTraits( |
| mlir::Operation *op, |
| llvm::SmallVectorImpl<llvm::omp::TraitProperty> &constructTraits) { |
| // Collect enclosing OpenMP operations so variants chosen by an outer |
| // metadirective are part of this metadirective's context. For example, an |
| // inner metadirective inside `target` and an outer-selected `parallel` must |
| // be able to match construct={target, parallel}. The final reverse yields |
| // outermost-to-innermost order as required by OMPContext. |
| for (; op; op = op->getParentOp()) { |
| if (mlir::isa<mlir::omp::WsloopOp>(op)) |
| constructTraits.push_back(llvm::omp::TraitProperty::construct_for_for); |
| if (mlir::isa<mlir::omp::ParallelOp>(op)) |
| constructTraits.push_back( |
| llvm::omp::TraitProperty::construct_parallel_parallel); |
| if (mlir::isa<mlir::omp::TeamsOp>(op)) |
| constructTraits.push_back( |
| llvm::omp::TraitProperty::construct_teams_teams); |
| if (mlir::isa<mlir::omp::TargetOp>(op)) |
| constructTraits.push_back( |
| llvm::omp::TraitProperty::construct_target_target); |
| } |
| std::reverse(constructTraits.begin(), constructTraits.end()); |
| } |
| |
| const semantics::Symbol * |
| resolveDeclareVariantCallee(const semantics::Symbol &base, |
| AbstractConverter &converter) { |
| const semantics::Symbol &ultimate{base.GetUltimate()}; |
| |
| const auto *details{ultimate.detailsIf<semantics::SubprogramDetails>()}; |
| assert(details && !details->ompDeclareVariants().empty() && |
| "resolveDeclareVariantCallee called on symbol with no variants"); |
| |
| semantics::SemanticsContext &semaCtx{ultimate.owner().context()}; |
| llvm::SmallVector<llvm::omp::VariantMatchInfo, 4> vmis; |
| llvm::SmallVector<const semantics::Symbol *, 4> variants; |
| for (const semantics::OmpDeclareVariantEntry &entry : |
| details->ompDeclareVariants()) { |
| // Variant selection cannot yet honour some selector features that the |
| // parser/semantics otherwise accept; reject them before building the match |
| // info (MakeVariantMatchInfo asserts none are present). This mirrors how |
| // METADIRECTIVE lowering rejects the same features. |
| if (entry.matchSelector) { |
| switch (semantics::omp::FindUnsupportedSelectorFeature( |
| *entry.matchSelector, semaCtx)) { |
| case semantics::omp::UnsupportedSelectorFeature::TargetDevice: |
| TODO(converter.getCurrentLocation(), |
| "target_device selector in DECLARE VARIANT"); |
| break; |
| case semantics::omp::UnsupportedSelectorFeature:: |
| ClauseOrExtensionProperty: |
| TODO(converter.getCurrentLocation(), |
| "clause or extension trait matching in DECLARE VARIANT"); |
| break; |
| case semantics::omp::UnsupportedSelectorFeature::None: |
| break; |
| } |
| } |
| llvm::omp::VariantMatchInfo &vmi{vmis.emplace_back()}; |
| if (entry.matchSelector) |
| semantics::omp::MakeVariantMatchInfo(vmi, *entry.matchSelector, semaCtx); |
| variants.push_back(&entry.variant.get()); |
| } |
| |
| llvm::SmallVector<llvm::omp::TraitProperty, 8> constructTraits; |
| collectEnclosingConstructTraits( |
| converter.getFirOpBuilder().getInsertionBlock()->getParentOp(), |
| constructTraits); |
| semantics::omp::OmpVariantMatchContext ompCtx = |
| makeVariantMatchContext(converter.getModuleOp(), constructTraits); |
| |
| const int bestIdx{llvm::omp::getBestVariantMatchForContext(vmis, ompCtx)}; |
| // Return nullptr when no variant matches the current context; the caller |
| // will fall back to the base symbol. |
| if (bestIdx < 0) |
| return nullptr; |
| return variants[bestIdx]; |
| } |
| |
| } // namespace omp |
| } // namespace lower |
| } // namespace Fortran |