brintos

brintos / llvm-project-archived public Read only

0
0
Text · 55.8 KiB · 3fe133d Raw
1230 lines · cpp
1//===- MapInfoFinalization.cpp -----------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9//===----------------------------------------------------------------------===//10/// \file11/// An OpenMP dialect related pass for FIR/HLFIR which performs some12/// pre-processing of MapInfoOp's after the module has been lowered to13/// finalize them.14///15/// For example, it expands MapInfoOp's containing descriptor related16/// types (fir::BoxType's) into multiple MapInfoOp's containing the parent17/// descriptor and pointer member components for individual mapping,18/// treating the descriptor type as a record type for later lowering in the19/// OpenMP dialect.20///21/// The pass also adds MapInfoOp's that are members of a parent object but are22/// not directly used in the body of a target region to its BlockArgument list23/// to maintain consistency across all MapInfoOp's tied to a region directly or24/// indirectly via a parent object.25//===----------------------------------------------------------------------===//26 27#include "flang/Optimizer/Builder/DirectivesCommon.h"28#include "flang/Optimizer/Builder/FIRBuilder.h"29#include "flang/Optimizer/Builder/HLFIRTools.h"30#include "flang/Optimizer/Dialect/FIRType.h"31#include "flang/Optimizer/Dialect/Support/KindMapping.h"32#include "flang/Optimizer/HLFIR/HLFIROps.h"33#include "flang/Optimizer/OpenMP/Passes.h"34#include "mlir/Analysis/SliceAnalysis.h"35#include "mlir/Dialect/Func/IR/FuncOps.h"36#include "mlir/Dialect/OpenMP/OpenMPDialect.h"37#include "mlir/IR/BuiltinDialect.h"38#include "mlir/IR/BuiltinOps.h"39#include "mlir/IR/Operation.h"40#include "mlir/IR/SymbolTable.h"41#include "mlir/Pass/Pass.h"42#include "mlir/Support/LLVM.h"43#include "llvm/ADT/BitmaskEnum.h"44#include "llvm/ADT/SmallPtrSet.h"45#include "llvm/ADT/StringSet.h"46#include "llvm/Support/raw_ostream.h"47#include <algorithm>48#include <cstddef>49#include <iterator>50#include <numeric>51 52#define DEBUG_TYPE "omp-map-info-finalization"53 54namespace flangomp {55#define GEN_PASS_DEF_MAPINFOFINALIZATIONPASS56#include "flang/Optimizer/OpenMP/Passes.h.inc"57} // namespace flangomp58 59namespace {60class MapInfoFinalizationPass61    : public flangomp::impl::MapInfoFinalizationPassBase<62          MapInfoFinalizationPass> {63  /// Helper class tracking a members parent and its64  /// placement in the parents member list65  struct ParentAndPlacement {66    mlir::omp::MapInfoOp parent;67    size_t index;68  };69 70  /// Tracks any intermediate function/subroutine local allocations we71  /// generate for the descriptors of box type dummy arguments, so that72  /// we can retrieve it for subsequent reuses within the functions73  /// scope.74  ///75  ///      descriptor defining op76  ///      |                  corresponding local alloca77  ///      |                  |78  std::map<mlir::Operation *, mlir::Value> localBoxAllocas;79 80  // List of deferrable descriptors to process at the end of81  // the pass.82  llvm::SmallVector<mlir::Operation *> deferrableDesc;83 84  /// Return true if the given path exists in a list of paths.85  static bool86  containsPath(const llvm::SmallVectorImpl<llvm::SmallVector<int64_t>> &paths,87               llvm::ArrayRef<int64_t> path) {88    return llvm::any_of(paths, [&](const llvm::SmallVector<int64_t> &p) {89      return p.size() == path.size() &&90             std::equal(p.begin(), p.end(), path.begin());91    });92  }93 94  /// Return true if the given path is already present in95  /// op.getMembersIndexAttr().96  static bool mappedIndexPathExists(mlir::omp::MapInfoOp op,97                                    llvm::ArrayRef<int64_t> indexPath) {98    if (mlir::ArrayAttr attr = op.getMembersIndexAttr()) {99      for (mlir::Attribute list : attr) {100        auto listAttr = mlir::cast<mlir::ArrayAttr>(list);101        if (listAttr.size() != indexPath.size())102          continue;103        bool allEq = true;104        for (auto [i, val] : llvm::enumerate(listAttr)) {105          if (mlir::cast<mlir::IntegerAttr>(val).getInt() != indexPath[i]) {106            allEq = false;107            break;108          }109        }110        if (allEq)111          return true;112      }113    }114    return false;115  }116 117  /// Build a compact string key for an index path for set-based118  /// deduplication. Format: "N:v0,v1,..." where N is the length.119  static void buildPathKey(llvm::ArrayRef<int64_t> path,120                           llvm::SmallString<64> &outKey) {121    outKey.clear();122    llvm::raw_svector_ostream os(outKey);123    os << path.size() << ':';124    for (size_t i = 0; i < path.size(); ++i) {125      if (i)126        os << ',';127      os << path[i];128    }129  }130 131  /// Return true if the module has an OpenMP requires clause that includes132  /// unified_shared_memory.133  static bool moduleRequiresUSM(mlir::ModuleOp module) {134    assert(module && "invalid module");135    if (auto req = module->getAttrOfType<mlir::omp::ClauseRequiresAttr>(136            "omp.requires"))137      return mlir::omp::bitEnumContainsAll(138          req.getValue(), mlir::omp::ClauseRequires::unified_shared_memory);139    return false;140  }141 142  /// Create the member map for coordRef and append it (and its index143  /// path) to the provided new* vectors, if it is not already present.144  void appendMemberMapIfNew(145      mlir::omp::MapInfoOp op, fir::FirOpBuilder &builder, mlir::Location loc,146      mlir::Value coordRef, llvm::ArrayRef<int64_t> indexPath,147      llvm::StringRef memberName,148      llvm::SmallVectorImpl<mlir::Value> &newMapOpsForFields,149      llvm::SmallVectorImpl<llvm::SmallVector<int64_t>> &newMemberIndexPaths) {150    // Local de-dup within this op invocation.151    if (containsPath(newMemberIndexPaths, indexPath))152      return;153    // Global de-dup against already present member indices.154    if (mappedIndexPathExists(op, indexPath))155      return;156 157    if (op.getMapperId()) {158      mlir::omp::DeclareMapperOp symbol =159          mlir::SymbolTable::lookupNearestSymbolFrom<160              mlir::omp::DeclareMapperOp>(op, op.getMapperIdAttr());161      assert(symbol && "missing symbol for declare mapper identifier");162      mlir::omp::DeclareMapperInfoOp mapperInfo = symbol.getDeclareMapperInfo();163      // TODO: Probably a way to cache these keys in someway so we don't164      // constantly go through the process of rebuilding them on every check, to165      // save some cycles, but it can wait for a subsequent patch.166      for (auto v : mapperInfo.getMapVars()) {167        mlir::omp::MapInfoOp map =168            mlir::cast<mlir::omp::MapInfoOp>(v.getDefiningOp());169        if (!map.getMembers().empty() && mappedIndexPathExists(map, indexPath))170          return;171      }172    }173 174    builder.setInsertionPoint(op);175    fir::factory::AddrAndBoundsInfo info = fir::factory::getDataOperandBaseAddr(176        builder, coordRef, /*isOptional=*/false, loc);177    llvm::SmallVector<mlir::Value> bounds = fir::factory::genImplicitBoundsOps<178        mlir::omp::MapBoundsOp, mlir::omp::MapBoundsType>(179        builder, info,180        hlfir::translateToExtendedValue(loc, builder, hlfir::Entity{coordRef})181            .first,182        /*dataExvIsAssumedSize=*/false, loc);183 184    mlir::omp::MapInfoOp fieldMapOp = mlir::omp::MapInfoOp::create(185        builder, loc, coordRef.getType(), coordRef,186        mlir::TypeAttr::get(fir::unwrapRefType(coordRef.getType())),187        op.getMapTypeAttr(),188        builder.getAttr<mlir::omp::VariableCaptureKindAttr>(189            mlir::omp::VariableCaptureKind::ByRef),190        /*varPtrPtr=*/mlir::Value{}, /*members=*/mlir::ValueRange{},191        /*members_index=*/mlir::ArrayAttr{}, bounds,192        /*mapperId=*/mlir::FlatSymbolRefAttr(),193        builder.getStringAttr(op.getNameAttr().strref() + "." + memberName +194                              ".implicit_map"),195        /*partial_map=*/builder.getBoolAttr(false));196 197    newMapOpsForFields.emplace_back(fieldMapOp);198    newMemberIndexPaths.emplace_back(indexPath.begin(), indexPath.end());199  }200 201  // Check if the declaration operation we have refers to a dummy202  // function argument.203  bool isDummyArgument(mlir::Value mappedValue) {204    if (auto declareOp = mlir::dyn_cast_if_present<hlfir::DeclareOp>(205            mappedValue.getDefiningOp()))206      if (auto dummyScope = declareOp.getDummyScope())207        return true;208    return false;209  }210 211  // Relevant for OpenMP < 5.2, where attach semantics and rules don't exist.212  // As descriptors were an unspoken implementation detail in these versions213  // there's certain cases where the user (and the compiler implementation)214  // can create data mapping errors by having temporary descriptors stuck215  // in memory. The main example is calling an 'target enter data map'216  // without a corresponding exit on an assumed shape or size dummy217  // argument, a local stack descriptor is generated, gets mapped and218  // is then left on device. A user doesn't realize what they've done as219  // the OpenMP specification isn't explicit on descriptor handling in220  // earlier versions and as far as Fortran is concerned this si something221  // hidden from a user. To avoid this we can defer the descriptor mapping222  // in these cases until target or target data regions, when we can be223  // sure they have a clear limited scope on device.224  bool canDeferDescriptorMapping(mlir::Value descriptor) {225    if (fir::isAllocatableType(descriptor.getType()) ||226        fir::isPointerType(descriptor.getType()))227      return false;228    if (isDummyArgument(descriptor) &&229        (fir::isAssumedType(descriptor.getType()) ||230         fir::isAssumedShape(descriptor.getType())))231      return true;232    return false;233  }234 235  /// getMemberUserList gathers all users of a particular MapInfoOp that are236  /// other MapInfoOp's and places them into the mapMemberUsers list, which237  /// records the map that the current argument MapInfoOp "op" is part of238  /// alongside the placement of "op" in the recorded users members list. The239  /// intent of the generated list is to find all MapInfoOp's that may be240  /// considered parents of the passed in "op" and in which it shows up in the241  /// member list, alongside collecting the placement information of "op" in its242  /// parents member list.243  void244  getMemberUserList(mlir::omp::MapInfoOp op,245                    llvm::SmallVectorImpl<ParentAndPlacement> &mapMemberUsers) {246    for (auto *user : op->getUsers())247      if (auto map = mlir::dyn_cast_if_present<mlir::omp::MapInfoOp>(user))248        for (auto [i, mapMember] : llvm::enumerate(map.getMembers()))249          if (mapMember.getDefiningOp() == op)250            mapMemberUsers.push_back({map, i});251  }252 253  void getAsIntegers(llvm::ArrayRef<mlir::Attribute> values,254                     llvm::SmallVectorImpl<int64_t> &ints) {255    ints.reserve(values.size());256    llvm::transform(values, std::back_inserter(ints),257                    [](mlir::Attribute value) {258                      return mlir::cast<mlir::IntegerAttr>(value).getInt();259                    });260  }261 262  /// This function will expand a MapInfoOp's member indices back into a vector263  /// so that they can be trivially modified as unfortunately the attribute type264  /// that's used does not have modifiable fields at the moment (generally265  /// awkward to work with)266  void getMemberIndicesAsVectors(267      mlir::omp::MapInfoOp mapInfo,268      llvm::SmallVectorImpl<llvm::SmallVector<int64_t>> &indices) {269    indices.reserve(mapInfo.getMembersIndexAttr().getValue().size());270    llvm::transform(mapInfo.getMembersIndexAttr().getValue(),271                    std::back_inserter(indices), [this](mlir::Attribute value) {272                      auto memberIndex = mlir::cast<mlir::ArrayAttr>(value);273                      llvm::SmallVector<int64_t> indexes;274                      getAsIntegers(memberIndex.getValue(), indexes);275                      return indexes;276                    });277  }278 279  /// When provided a MapInfoOp containing a descriptor type that280  /// we must expand into multiple maps this function will extract281  /// the value from it and return it, in certain cases we must282  /// generate a new allocation to store into so that the283  /// fir::BoxOffsetOp we utilise to access the descriptor datas284  /// base address can be utilised.285  mlir::Value getDescriptorFromBoxMap(mlir::omp::MapInfoOp boxMap,286                                      fir::FirOpBuilder &builder,287                                      bool &canDescBeDeferred) {288    mlir::Value descriptor = boxMap.getVarPtr();289    if (!fir::isTypeWithDescriptor(boxMap.getVarType()))290      if (auto addrOp = mlir::dyn_cast_if_present<fir::BoxAddrOp>(291              boxMap.getVarPtr().getDefiningOp()))292        descriptor = addrOp.getVal();293 294    canDescBeDeferred = canDeferDescriptorMapping(descriptor);295 296    if (!mlir::isa<fir::BaseBoxType>(descriptor.getType()) &&297        !fir::factory::isOptionalArgument(descriptor.getDefiningOp()))298      return descriptor;299 300    mlir::Value &alloca = localBoxAllocas[descriptor.getDefiningOp()];301    mlir::Location loc = boxMap->getLoc();302 303    if (!alloca) {304      // The fir::BoxOffsetOp only works with !fir.ref<!fir.box<...>> types, as305      // allowing it to access non-reference box operations can cause some306      // problematic SSA IR. However, in the case of assumed shape's the type307      // is not a !fir.ref, in these cases to retrieve the appropriate308      // !fir.ref<!fir.box<...>> to access the data we need to map we must309      // perform an alloca and then store to it and retrieve the data from the310      // new alloca.311      mlir::OpBuilder::InsertPoint insPt = builder.saveInsertionPoint();312      mlir::Block *allocaBlock = builder.getAllocaBlock();313      assert(allocaBlock && "No alloca block found for this top level op");314      builder.setInsertionPointToStart(allocaBlock);315 316      mlir::Type allocaType = descriptor.getType();317      if (fir::isBoxAddress(allocaType))318        allocaType = fir::unwrapRefType(allocaType);319      alloca = fir::AllocaOp::create(builder, loc, allocaType);320      builder.restoreInsertionPoint(insPt);321    }322 323    // We should only emit a store if the passed in data is present, it is324    // possible a user passes in no argument to an optional parameter, in which325    // case we cannot store or we'll segfault on the emitted memcpy.326    // TODO: We currently emit a present -> load/store every time we use a327    // mapped value that requires a local allocation, this isn't the most328    // efficient, although, it is more correct in a lot of situations. One329    // such situation is emitting a this series of instructions in separate330    // segments of a branch (e.g. two target regions in separate else/if branch331    // mapping the same function argument), however, it would be nice to be able332    // to optimize these situations e.g. raising the load/store out of the333    // branch if possible. But perhaps this is best left to lower level334    // optimisation passes.335    auto isPresent =336        fir::IsPresentOp::create(builder, loc, builder.getI1Type(), descriptor);337    builder.genIfOp(loc, {}, isPresent, false)338        .genThen([&]() {339          descriptor = builder.loadIfRef(loc, descriptor);340          fir::StoreOp::create(builder, loc, descriptor, alloca);341        })342        .end();343    return alloca;344  }345 346  /// Function that generates a FIR operation accessing the descriptor's347  /// base address (BoxOffsetOp) and a MapInfoOp for it. The most348  /// important thing to note is that we normally move the bounds from349  /// the descriptor map onto the base address map.350  mlir::omp::MapInfoOp351  genBaseAddrMap(mlir::Value descriptor, mlir::OperandRange bounds,352                 mlir::omp::ClauseMapFlags mapType, fir::FirOpBuilder &builder,353                 mlir::FlatSymbolRefAttr mapperId = mlir::FlatSymbolRefAttr()) {354    mlir::Location loc = descriptor.getLoc();355    mlir::Value baseAddrAddr = fir::BoxOffsetOp::create(356        builder, loc, descriptor, fir::BoxFieldAttr::base_addr);357 358    mlir::Type underlyingVarType =359        llvm::cast<mlir::omp::PointerLikeType>(360            fir::unwrapRefType(baseAddrAddr.getType()))361            .getElementType();362    if (auto seqType = llvm::dyn_cast<fir::SequenceType>(underlyingVarType))363      if (seqType.hasDynamicExtents())364        underlyingVarType = seqType.getEleTy();365 366    // Member of the descriptor pointing at the allocated data367    return mlir::omp::MapInfoOp::create(368        builder, loc, baseAddrAddr.getType(), descriptor,369        mlir::TypeAttr::get(underlyingVarType),370        builder.getAttr<mlir::omp::ClauseMapFlagsAttr>(mapType),371        builder.getAttr<mlir::omp::VariableCaptureKindAttr>(372            mlir::omp::VariableCaptureKind::ByRef),373        baseAddrAddr, /*members=*/mlir::SmallVector<mlir::Value>{},374        /*membersIndex=*/mlir::ArrayAttr{}, bounds,375        /*mapperId=*/mapperId,376        /*name=*/builder.getStringAttr(""),377        /*partial_map=*/builder.getBoolAttr(false));378  }379 380  /// This function adjusts the member indices vector to include a new381  /// base address member. We take the position of the descriptor in382  /// the member indices list, which is the index data that the base383  /// addresses index will be based off of, as the base address is384  /// a member of the descriptor. We must also alter other members385  /// that are members of this descriptor to account for the addition386  /// of the base address index.387  void adjustMemberIndices(388      llvm::SmallVectorImpl<llvm::SmallVector<int64_t>> &memberIndices,389      size_t memberIndex) {390    llvm::SmallVector<int64_t> baseAddrIndex = memberIndices[memberIndex];391 392    // If we find another member that is "derived/a member of" the descriptor393    // that is not the descriptor itself, we must insert a 0 for the new base394    // address we have just added for the descriptor into the list at the395    // appropriate position to maintain correctness of the positional/index data396    // for that member.397    for (llvm::SmallVector<int64_t> &member : memberIndices)398      if (member.size() > baseAddrIndex.size() &&399          std::equal(baseAddrIndex.begin(), baseAddrIndex.end(),400                     member.begin()))401        member.insert(std::next(member.begin(), baseAddrIndex.size()), 0);402 403    // Add the base address index to the main base address member data404    baseAddrIndex.push_back(0);405 406    // Insert our newly created baseAddrIndex into the larger list of indices at407    // the correct location.408    memberIndices.insert(std::next(memberIndices.begin(), memberIndex + 1),409                         baseAddrIndex);410  }411 412  /// Adjusts the descriptor's map type. The main alteration that is done413  /// currently is transforming the map type to `OMP_MAP_TO` where possible.414  /// This is because we will always need to map the descriptor to device415  /// (or at the very least it seems to be the case currently with the416  /// current lowered kernel IR), as without the appropriate descriptor417  /// information on the device there is a risk of the kernel IR418  /// requesting for various data that will not have been copied to419  /// perform things like indexing. This can cause segfaults and420  /// memory access errors. However, we do not need this data mapped421  /// back to the host from the device, as per the OpenMP spec we cannot alter422  /// the data via resizing or deletion on the device. Discarding any423  /// descriptor alterations via no map back is reasonable (and required424  /// for certain segments of descriptor data like the type descriptor that are425  /// global constants). This alteration is only inapplicable to `target exit`426  /// and `target update` currently, and that's due to `target exit` not427  /// allowing `to` mappings, and `target update` not allowing both `to` and428  /// `from` simultaneously. We currently try to maintain the `implicit` flag429  /// where necessary, although it does not seem strictly required.430  mlir::omp::ClauseMapFlags431  getDescriptorMapType(mlir::omp::ClauseMapFlags mapTypeFlag,432                       mlir::Operation *target) {433    using mapFlags = mlir::omp::ClauseMapFlags;434    if (llvm::isa_and_nonnull<mlir::omp::TargetExitDataOp,435                              mlir::omp::TargetUpdateOp>(target))436      return mapTypeFlag;437 438    mapFlags flags =439        mapFlags::to | (mapTypeFlag & (mapFlags::implicit | mapFlags::always));440 441    // Descriptors for objects will always be copied. This is because the442    // descriptor can be rematerialized by the compiler, and so the address443    // of the descriptor for a given object at one place in the code may444    // differ from that address in another place. The contents of the445    // descriptor (the base address in particular) will remain unchanged446    // though.447    // TODO/FIXME: We currently cannot have MAP_CLOSE and MAP_ALWAYS on448    // the descriptor at once, these are mutually exclusive and when449    // both are applied the runtime will fail to map.450    flags |= ((mapFlags(mapTypeFlag) & mapFlags::close) == mapFlags::close)451                 ? mapFlags::close452                 : mapFlags::always;453 454    // For unified_shared_memory, we additionally add `CLOSE` on the descriptor455    // to ensure device-local placement where required by tests relying on USM +456    // close semantics.457    if (moduleRequiresUSM(target->getParentOfType<mlir::ModuleOp>()))458      flags |= mapFlags::close;459    return flags;460  }461 462  /// Check if the mapOp is present in the HasDeviceAddr clause on463  /// the userOp. Only applies to TargetOp.464  bool isHasDeviceAddr(mlir::omp::MapInfoOp mapOp, mlir::Operation &userOp) {465    if (auto targetOp = llvm::dyn_cast<mlir::omp::TargetOp>(userOp)) {466      for (mlir::Value hda : targetOp.getHasDeviceAddrVars()) {467        if (hda.getDefiningOp() == mapOp)468          return true;469      }470    }471    return false;472  }473 474  bool isUseDeviceAddr(mlir::omp::MapInfoOp mapOp, mlir::Operation &userOp) {475    if (auto targetDataOp = llvm::dyn_cast<mlir::omp::TargetDataOp>(userOp)) {476      for (mlir::Value uda : targetDataOp.getUseDeviceAddrVars()) {477        if (uda.getDefiningOp() == mapOp)478          return true;479      }480    }481    return false;482  }483 484  bool isUseDevicePtr(mlir::omp::MapInfoOp mapOp, mlir::Operation &userOp) {485    if (auto targetDataOp = llvm::dyn_cast<mlir::omp::TargetDataOp>(userOp)) {486      for (mlir::Value udp : targetDataOp.getUseDevicePtrVars()) {487        if (udp.getDefiningOp() == mapOp)488          return true;489      }490    }491    return false;492  }493 494  // Expand mappings of type(C_PTR) to map their `__address` field explicitly495  // as a single pointer-sized member (USM-gated at callsite). This helps in496  // USM scenarios to ensure the pointer-sized mapping is used.497  mlir::omp::MapInfoOp genCptrMemberMap(mlir::omp::MapInfoOp op,498                                        fir::FirOpBuilder &builder) {499    if (!op.getMembers().empty())500      return op;501 502    mlir::Type varTy = fir::unwrapRefType(op.getVarPtr().getType());503    if (!mlir::isa<fir::RecordType>(varTy))504      return op;505    auto recTy = mlir::cast<fir::RecordType>(varTy);506    // If not a builtin C_PTR record, skip.507    if (!recTy.getName().ends_with("__builtin_c_ptr"))508      return op;509 510    // Find the index of the c_ptr address component named "__address".511    int32_t fieldIdx = recTy.getFieldIndex("__address");512    if (fieldIdx < 0)513      return op;514 515    mlir::Location loc = op.getVarPtr().getLoc();516    mlir::Type memTy = recTy.getType(fieldIdx);517    fir::IntOrValue idxConst =518        mlir::IntegerAttr::get(builder.getI32Type(), fieldIdx);519    mlir::Value coord = fir::CoordinateOp::create(520        builder, loc, builder.getRefType(memTy), op.getVarPtr(),521        llvm::SmallVector<fir::IntOrValue, 1>{idxConst});522 523    // Child for the `__address` member.524    llvm::SmallVector<llvm::SmallVector<int64_t>> memberIdx = {{0}};525    mlir::ArrayAttr newMembersAttr = builder.create2DI64ArrayAttr(memberIdx);526    // Force CLOSE in USM paths so the pointer gets device-local placement527    // when required by tests relying on USM + close semantics.528    mlir::omp::ClauseMapFlagsAttr mapTypeAttr =529        builder.getAttr<mlir::omp::ClauseMapFlagsAttr>(530            op.getMapType() | mlir::omp::ClauseMapFlags::close);531 532    mlir::omp::MapInfoOp memberMap = mlir::omp::MapInfoOp::create(533        builder, loc, coord.getType(), coord,534        mlir::TypeAttr::get(fir::unwrapRefType(coord.getType())), mapTypeAttr,535        builder.getAttr<mlir::omp::VariableCaptureKindAttr>(536            mlir::omp::VariableCaptureKind::ByRef),537        /*varPtrPtr=*/mlir::Value{},538        /*members=*/llvm::SmallVector<mlir::Value>{},539        /*member_index=*/mlir::ArrayAttr{},540        /*bounds=*/op.getBounds(),541        /*mapperId=*/mlir::FlatSymbolRefAttr(),542        /*name=*/op.getNameAttr(),543        /*partial_map=*/builder.getBoolAttr(false));544 545    // Rebuild the parent as a container with the `__address` member.546    mlir::omp::MapInfoOp newParent = mlir::omp::MapInfoOp::create(547        builder, op.getLoc(), op.getResult().getType(), op.getVarPtr(),548        op.getVarTypeAttr(), mapTypeAttr, op.getMapCaptureTypeAttr(),549        /*varPtrPtr=*/mlir::Value{},550        /*members=*/llvm::SmallVector<mlir::Value>{memberMap},551        /*member_index=*/newMembersAttr,552        /*bounds=*/llvm::SmallVector<mlir::Value>{},553        /*mapperId=*/mlir::FlatSymbolRefAttr(), op.getNameAttr(),554        /*partial_map=*/builder.getBoolAttr(false));555    op.replaceAllUsesWith(newParent.getResult());556    op->erase();557    return newParent;558  }559 560  mlir::omp::MapInfoOp genDescriptorMemberMaps(mlir::omp::MapInfoOp op,561                                               fir::FirOpBuilder &builder,562                                               mlir::Operation *target) {563    llvm::SmallVector<ParentAndPlacement> mapMemberUsers;564    getMemberUserList(op, mapMemberUsers);565 566    // TODO: map the addendum segment of the descriptor, similarly to the567    // base address/data pointer member.568    bool descCanBeDeferred = false;569    mlir::Value descriptor =570        getDescriptorFromBoxMap(op, builder, descCanBeDeferred);571 572    mlir::ArrayAttr newMembersAttr;573    mlir::SmallVector<mlir::Value> newMembers;574    llvm::SmallVector<llvm::SmallVector<int64_t>> memberIndices;575    bool isHasDeviceAddrFlag = isHasDeviceAddr(op, *target);576 577    if (!mapMemberUsers.empty() || !op.getMembers().empty())578      getMemberIndicesAsVectors(579          !mapMemberUsers.empty() ? mapMemberUsers[0].parent : op,580          memberIndices);581 582    // If the operation that we are expanding with a descriptor has a user583    // (parent), then we have to expand the parent's member indices to reflect584    // the adjusted member indices for the base address insertion. However, if585    // it does not then we are expanding a MapInfoOp without any pre-existing586    // member information to now have one new member for the base address, or587    // we are expanding a parent that is a descriptor and we have to adjust588    // all of its members to reflect the insertion of the base address.589    //590    // If we're expanding a top-level descriptor for a map operation that591    // resulted from "has_device_addr" clause, then we want the base pointer592    // from the descriptor to be used verbatim, i.e. without additional593    // remapping. To avoid this remapping, simply don't generate any map594    // information for the descriptor members.595    mlir::FlatSymbolRefAttr mapperId = op.getMapperIdAttr();596    if (!mapMemberUsers.empty()) {597      // Currently, there should only be one user per map when this pass598      // is executed. Either a parent map, holding the current map in its599      // member list, or a target operation that holds a map clause. This600      // may change in the future if we aim to refactor the MLIR for map601      // clauses to allow sharing of duplicate maps across target602      // operations.603      assert(mapMemberUsers.size() == 1 &&604             "OMPMapInfoFinalization currently only supports single users of a "605             "MapInfoOp");606      auto baseAddr = genBaseAddrMap(descriptor, op.getBounds(),607                                     op.getMapType(), builder, mapperId);608      ParentAndPlacement mapUser = mapMemberUsers[0];609      adjustMemberIndices(memberIndices, mapUser.index);610      llvm::SmallVector<mlir::Value> newMemberOps;611      for (auto v : mapUser.parent.getMembers()) {612        newMemberOps.push_back(v);613        if (v == op)614          newMemberOps.push_back(baseAddr);615      }616      mapUser.parent.getMembersMutable().assign(newMemberOps);617      mapUser.parent.setMembersIndexAttr(618          builder.create2DI64ArrayAttr(memberIndices));619    } else if (!isHasDeviceAddrFlag) {620      auto baseAddr = genBaseAddrMap(descriptor, op.getBounds(),621                                     op.getMapType(), builder, mapperId);622      newMembers.push_back(baseAddr);623      if (!op.getMembers().empty()) {624        for (auto &indices : memberIndices)625          indices.insert(indices.begin(), 0);626        memberIndices.insert(memberIndices.begin(), {0});627        newMembersAttr = builder.create2DI64ArrayAttr(memberIndices);628        newMembers.append(op.getMembers().begin(), op.getMembers().end());629      } else {630        llvm::SmallVector<llvm::SmallVector<int64_t>> memberIdx = {{0}};631        newMembersAttr = builder.create2DI64ArrayAttr(memberIdx);632      }633    }634 635    // Descriptors for objects listed on the `has_device_addr` will always636    // be copied. This is because the descriptor can be rematerialized by the637    // compiler, and so the address of the descriptor for a given object at638    // one place in the code may differ from that address in another place.639    // The contents of the descriptor (the base address in particular) will640    // remain unchanged though.641    mlir::omp::ClauseMapFlags mapType = op.getMapType();642    if (isHasDeviceAddrFlag) {643      mapType |= mlir::omp::ClauseMapFlags::always;644    }645 646    mlir::omp::MapInfoOp newDescParentMapOp = mlir::omp::MapInfoOp::create(647        builder, op->getLoc(), op.getResult().getType(), descriptor,648        mlir::TypeAttr::get(fir::unwrapRefType(descriptor.getType())),649        builder.getAttr<mlir::omp::ClauseMapFlagsAttr>(650            getDescriptorMapType(mapType, target)),651        op.getMapCaptureTypeAttr(), /*varPtrPtr=*/mlir::Value{}, newMembers,652        newMembersAttr, /*bounds=*/mlir::SmallVector<mlir::Value>{},653        /*mapperId=*/mlir::FlatSymbolRefAttr(), op.getNameAttr(),654        /*partial_map=*/builder.getBoolAttr(false));655    op.replaceAllUsesWith(newDescParentMapOp.getResult());656    op->erase();657 658    if (descCanBeDeferred)659      deferrableDesc.push_back(newDescParentMapOp);660 661    return newDescParentMapOp;662  }663 664  // We add all mapped record members not directly used in the target region665  // to the block arguments in front of their parent and we place them into666  // the map operands list for consistency.667  //668  // These indirect uses (via accesses to their parent) will still be669  // mapped individually in most cases, and a parent mapping doesn't670  // guarantee the parent will be mapped in its totality, partial671  // mapping is common.672  //673  // For example:674  //    map(tofrom: x%y)675  //676  // Will generate a mapping for "x" (the parent) and "y" (the member).677  // The parent "x" will not be mapped, but the member "y" will.678  // However, we must have the parent as a BlockArg and MapOperand679  // in these cases, to maintain the correct uses within the region and680  // to help tracking that the member is part of a larger object.681  //682  // In the case of:683  //    map(tofrom: x%y, x%z)684  //685  // The parent member becomes more critical, as we perform a partial686  // structure mapping where we link the mapping of the members y687  // and z together via the parent x. We do this at a kernel argument688  // level in LLVM IR and not just MLIR, which is important to maintain689  // similarity to Clang and for the runtime to do the correct thing.690  // However, we still do not map the structure in its totality but691  // rather we generate an un-sized "binding" map entry for it.692  //693  // In the case of:694  //    map(tofrom: x, x%y, x%z)695  //696  // We do actually map the entirety of "x", so the explicit mapping of697  // x%y, x%z becomes unnecessary. It is redundant to write this from a698  // Fortran OpenMP perspective (although it is legal), as even if the699  // members were allocatables or pointers, we are mandated by the700  // specification to map these (and any recursive components) in their701  // entirety, which is different to the C++ equivalent, which requires702  // explicit mapping of these segments.703  void addImplicitMembersToTarget(mlir::omp::MapInfoOp op,704                                  fir::FirOpBuilder &builder,705                                  mlir::Operation *target) {706    auto mapClauseOwner =707        llvm::dyn_cast_if_present<mlir::omp::MapClauseOwningOpInterface>(708            target);709    // TargetDataOp is technically a MapClauseOwningOpInterface, so we710    // do not need to explicitly check for the extra cases here for use_device711    // addr/ptr712    if (!mapClauseOwner)713      return;714 715    auto addOperands = [&](mlir::MutableOperandRange &mutableOpRange,716                           mlir::Operation *directiveOp,717                           unsigned blockArgInsertIndex = 0) {718      if (!llvm::is_contained(mutableOpRange.getAsOperandRange(),719                              op.getResult()))720        return;721 722      // There doesn't appear to be a simple way to convert MutableOperandRange723      // to a vector currently, so we instead use a for_each to populate our724      // vector.725      llvm::SmallVector<mlir::Value> newMapOps;726      newMapOps.reserve(mutableOpRange.size());727      llvm::for_each(728          mutableOpRange.getAsOperandRange(),729          [&newMapOps](mlir::Value oper) { newMapOps.push_back(oper); });730 731      for (auto mapMember : op.getMembers()) {732        if (llvm::is_contained(mutableOpRange.getAsOperandRange(), mapMember))733          continue;734        newMapOps.push_back(mapMember);735        if (directiveOp) {736          directiveOp->getRegion(0).insertArgument(737              blockArgInsertIndex, mapMember.getType(), mapMember.getLoc());738          blockArgInsertIndex++;739        }740      }741 742      mutableOpRange.assign(newMapOps);743    };744 745    auto argIface =746        llvm::dyn_cast<mlir::omp::BlockArgOpenMPOpInterface>(target);747 748    if (auto mapClauseOwner =749            llvm::dyn_cast<mlir::omp::MapClauseOwningOpInterface>(target)) {750      mlir::MutableOperandRange mapMutableOpRange =751          mapClauseOwner.getMapVarsMutable();752      unsigned blockArgInsertIndex =753          argIface754              ? argIface.getMapBlockArgsStart() + argIface.numMapBlockArgs()755              : 0;756      addOperands(mapMutableOpRange,757                  llvm::dyn_cast_if_present<mlir::omp::TargetOp>(758                      argIface.getOperation()),759                  blockArgInsertIndex);760    }761 762    if (auto targetDataOp = llvm::dyn_cast<mlir::omp::TargetDataOp>(target)) {763      mlir::MutableOperandRange useDevAddrMutableOpRange =764          targetDataOp.getUseDeviceAddrVarsMutable();765      addOperands(useDevAddrMutableOpRange, target,766                  argIface.getUseDeviceAddrBlockArgsStart() +767                      argIface.numUseDeviceAddrBlockArgs());768 769      mlir::MutableOperandRange useDevPtrMutableOpRange =770          targetDataOp.getUseDevicePtrVarsMutable();771      addOperands(useDevPtrMutableOpRange, target,772                  argIface.getUseDevicePtrBlockArgsStart() +773                      argIface.numUseDevicePtrBlockArgs());774    } else if (auto targetOp = llvm::dyn_cast<mlir::omp::TargetOp>(target)) {775      mlir::MutableOperandRange hasDevAddrMutableOpRange =776          targetOp.getHasDeviceAddrVarsMutable();777      addOperands(hasDevAddrMutableOpRange, target,778                  argIface.getHasDeviceAddrBlockArgsStart() +779                      argIface.numHasDeviceAddrBlockArgs());780    }781  }782 783  // We retrieve the first user that is a Target operation, of which784  // there should only be one currently. Every MapInfoOp can be tied to785  // at most one Target operation and at the minimum no operations.786  // This may change in the future with IR cleanups/modifications,787  // in which case this pass will need updating to support cases788  // where a map can have more than one user and more than one of789  // those users can be a Target operation. For now, we simply790  // return the first target operation encountered, which may791  // be on the parent MapInfoOp in the case of a member mapping.792  // In that case, we traverse the MapInfoOp chain until we793  // find the first TargetOp user.794  mlir::Operation *getFirstTargetUser(mlir::omp::MapInfoOp mapOp) {795    for (auto *user : mapOp->getUsers()) {796      if (llvm::isa<mlir::omp::TargetOp, mlir::omp::TargetDataOp,797                    mlir::omp::TargetUpdateOp, mlir::omp::TargetExitDataOp,798                    mlir::omp::TargetEnterDataOp,799                    mlir::omp::DeclareMapperInfoOp>(user))800        return user;801 802      if (auto mapUser = llvm::dyn_cast<mlir::omp::MapInfoOp>(user))803        return getFirstTargetUser(mapUser);804    }805 806    return nullptr;807  }808 809  void addImplicitDescriptorMapToTargetDataOp(mlir::omp::MapInfoOp op,810                                              fir::FirOpBuilder &builder,811                                              mlir::Operation &target) {812    // Checks if the map is present as an explicit map already on the target813    // data directive, and not just present on a use_device_addr/ptr, as if814    // that's the case, we should not need to add an implicit map for the815    // descriptor.816    auto explicitMappingPresent = [](mlir::omp::MapInfoOp op,817                                     mlir::omp::TargetDataOp tarData) {818      // Verify top-level descriptor mapping is at least equal with same819      // varPtr, the map type should always be To for a descriptor, which is820      // all we really care about for this mapping as we aim to make sure the821      // descriptor is always present on device if we're expecting to access822      // the underlying data.823      if (tarData.getMapVars().empty())824        return false;825 826      for (mlir::Value mapVar : tarData.getMapVars()) {827        auto mapOp = llvm::cast<mlir::omp::MapInfoOp>(mapVar.getDefiningOp());828        if (mapOp.getVarPtr() == op.getVarPtr() &&829            mapOp.getVarPtrPtr() == op.getVarPtrPtr()) {830          return true;831        }832      }833 834      return false;835    };836 837    // if we're not a top level descriptor with members (e.g. member of a838    // derived type), we do not want to perform this step.839    if (!llvm::isa<mlir::omp::TargetDataOp>(target) || op.getMembers().empty())840      return;841 842    if (!isUseDeviceAddr(op, target) && !isUseDevicePtr(op, target))843      return;844 845    auto targetDataOp = llvm::cast<mlir::omp::TargetDataOp>(target);846    if (explicitMappingPresent(op, targetDataOp))847      return;848 849    mlir::omp::MapInfoOp newDescParentMapOp = mlir::omp::MapInfoOp::create(850        builder, op->getLoc(), op.getResult().getType(), op.getVarPtr(),851        op.getVarTypeAttr(),852        builder.getAttr<mlir::omp::ClauseMapFlagsAttr>(853            mlir::omp::ClauseMapFlags::to | mlir::omp::ClauseMapFlags::always),854        op.getMapCaptureTypeAttr(), /*varPtrPtr=*/mlir::Value{},855        mlir::SmallVector<mlir::Value>{}, mlir::ArrayAttr{},856        /*bounds=*/mlir::SmallVector<mlir::Value>{},857        /*mapperId*/ mlir::FlatSymbolRefAttr(), op.getNameAttr(),858        /*partial_map=*/builder.getBoolAttr(false));859 860    targetDataOp.getMapVarsMutable().append({newDescParentMapOp});861  }862 863  void removeTopLevelDescriptor(mlir::omp::MapInfoOp op,864                                fir::FirOpBuilder &builder,865                                mlir::Operation *target) {866    if (llvm::isa<mlir::omp::TargetOp, mlir::omp::TargetDataOp,867                  mlir::omp::DeclareMapperInfoOp>(target))868      return;869 870    // if we're not a top level descriptor with members (e.g. member of a871    // derived type), we do not want to perform this step.872    if (op.getMembers().empty())873      return;874 875    mlir::SmallVector<mlir::Value> members = op.getMembers();876    mlir::omp::MapInfoOp baseAddr =877        mlir::dyn_cast_or_null<mlir::omp::MapInfoOp>(878            members.front().getDefiningOp());879    assert(baseAddr && "Expected member to be MapInfoOp");880    members.erase(members.begin());881 882    llvm::SmallVector<llvm::SmallVector<int64_t>> memberIndices;883    getMemberIndicesAsVectors(op, memberIndices);884 885    // Can skip the extra processing if there's only 1 member as it'd886    // be the base addresses, which we're promoting to the parent.887    mlir::ArrayAttr membersAttr;888    if (memberIndices.size() > 1) {889      memberIndices.erase(memberIndices.begin());890      membersAttr = builder.create2DI64ArrayAttr(memberIndices);891    }892 893    // VarPtrPtr is tied to detecting if something is a pointer in the later894    // lowering currently, this at the moment comes tied with895    // OMP_MAP_PTR_AND_OBJ being applied which breaks the problem this tries to896    // solve by emitting a 8-byte mapping tied to the descriptor address (even897    // if we only emit a single map). So we circumvent this by removing the898    // varPtrPtr mapping, however, a side affect of this is we lose the899    // additional load from the backend tied to this which is required for900    // correctness and getting the correct address of the data to perform our901    // mapping. So we do our load at this stage.902    // TODO/FIXME: Tidy up the OMP_MAP_PTR_AND_OBJ and varPtrPtr being tied to903    // if something is a pointer to try and tidy up the implementation a bit.904    // This is an unfortunate complexity from push-back from upstream. We905    // could also emit a load at this level for all base addresses as well,906    // which in turn will simplify the later lowering a bit as well. But first907    // need to see how well this alteration works.908    auto loadBaseAddr =909        builder.loadIfRef(op->getLoc(), baseAddr.getVarPtrPtr());910    mlir::omp::MapInfoOp newBaseAddrMapOp = mlir::omp::MapInfoOp::create(911        builder, op->getLoc(), loadBaseAddr.getType(), loadBaseAddr,912        baseAddr.getVarTypeAttr(), baseAddr.getMapTypeAttr(),913        baseAddr.getMapCaptureTypeAttr(), mlir::Value{}, members, membersAttr,914        baseAddr.getBounds(),915        /*mapperId*/ mlir::FlatSymbolRefAttr(), op.getNameAttr(),916        /*partial_map=*/builder.getBoolAttr(false));917    op.replaceAllUsesWith(newBaseAddrMapOp.getResult());918    op->erase();919    baseAddr.erase();920  }921 922  static bool hasADescriptor(mlir::Operation *varOp, mlir::Type varType) {923    if (fir::isTypeWithDescriptor(varType) ||924        mlir::isa<fir::BoxCharType>(varType) ||925        mlir::isa_and_present<fir::BoxAddrOp>(varOp))926      return true;927    return false;928  }929 930  // This pass executes on omp::MapInfoOp's containing descriptor based types931  // (allocatables, pointers, assumed shape etc.) and expanding them into932  // multiple omp::MapInfoOp's for each pointer member contained within the933  // descriptor.934  //935  // From the perspective of the MLIR pass manager this runs on the top level936  // operation (usually function) containing the MapInfoOp because this pass937  // will mutate siblings of MapInfoOp.938  void runOnOperation() override {939    mlir::ModuleOp module = getOperation();940    if (!module)941      module = getOperation()->getParentOfType<mlir::ModuleOp>();942    fir::KindMapping kindMap = fir::getKindMapping(module);943    fir::FirOpBuilder builder{module, std::move(kindMap)};944 945    // We wish to maintain some function level scope (currently946    // just local function scope variables used to load and store box947    // variables into so we can access their base address, an948    // quirk of box_offset requires us to have an in memory box, but Fortran949    // in certain cases does not provide this) whilst not subjecting950    // ourselves to the possibility of race conditions while this pass951    // undergoes frequent re-iteration for the near future. So we loop952    // over function in the module and then map.info inside of those.953    getOperation()->walk([&](mlir::Operation *func) {954      if (!mlir::isa<mlir::func::FuncOp, mlir::omp::DeclareMapperOp>(func))955        return;956      // clear all local allocations we made for any boxes in any prior957      // iterations from previous function scopes.958      localBoxAllocas.clear();959      deferrableDesc.clear();960 961      // Next, walk `omp.map.info` ops to see if any record members should be962      // implicitly mapped.963      func->walk([&](mlir::omp::MapInfoOp op) {964        mlir::Type underlyingType =965            fir::unwrapRefType(op.getVarPtr().getType());966 967        // TODO Test with and support more complicated cases; like arrays for968        // records, for example.969        if (!fir::isRecordWithAllocatableMember(underlyingType))970          return mlir::WalkResult::advance();971 972        // TODO For now, only consider `omp.target` ops. Other ops that support973        // `map` clauses will follow later.974        mlir::omp::TargetOp target =975            mlir::dyn_cast_if_present<mlir::omp::TargetOp>(976                getFirstTargetUser(op));977 978        if (!target)979          return mlir::WalkResult::advance();980 981        auto mapClauseOwner =982            llvm::dyn_cast<mlir::omp::MapClauseOwningOpInterface>(*target);983 984        int64_t mapVarIdx = mapClauseOwner.getOperandIndexForMap(op);985        assert(mapVarIdx >= 0 &&986               mapVarIdx <987                   static_cast<int64_t>(mapClauseOwner.getMapVars().size()));988 989        auto argIface =990            llvm::dyn_cast<mlir::omp::BlockArgOpenMPOpInterface>(*target);991        // TODO How should `map` block argument that correspond to: `private`,992        // `use_device_addr`, `use_device_ptr`, be handled?993        mlir::BlockArgument opBlockArg = argIface.getMapBlockArgs()[mapVarIdx];994        llvm::SetVector<mlir::Operation *> mapVarForwardSlice;995        mlir::getForwardSlice(opBlockArg, &mapVarForwardSlice);996 997        mapVarForwardSlice.remove_if([&](mlir::Operation *sliceOp) {998          // TODO Support coordinate_of ops.999          //1000          // TODO Support call ops by recursively examining the forward slice of1001          // the corresponding parameter to the field in the called function.1002          return !mlir::isa<hlfir::DesignateOp>(sliceOp);1003        });1004 1005        auto recordType = mlir::cast<fir::RecordType>(underlyingType);1006        llvm::SmallVector<mlir::Value> newMapOpsForFields;1007        llvm::SmallVector<llvm::SmallVector<int64_t>> newMemberIndexPaths;1008 1009        // 1) Handle direct top-level allocatable fields.1010        for (auto fieldMemTyPair : recordType.getTypeList()) {1011          auto &field = fieldMemTyPair.first;1012          auto memTy = fieldMemTyPair.second;1013 1014          if (!fir::isAllocatableType(memTy))1015            continue;1016 1017          bool referenced = llvm::any_of(mapVarForwardSlice, [&](auto *opv) {1018            auto designateOp = mlir::dyn_cast<hlfir::DesignateOp>(opv);1019            return designateOp && designateOp.getComponent() &&1020                   designateOp.getComponent()->strref() == field;1021          });1022          if (!referenced)1023            continue;1024 1025          int32_t fieldIdx = recordType.getFieldIndex(field);1026          builder.setInsertionPoint(op);1027          fir::IntOrValue idxConst =1028              mlir::IntegerAttr::get(builder.getI32Type(), fieldIdx);1029          auto fieldCoord = fir::CoordinateOp::create(1030              builder, op.getLoc(), builder.getRefType(memTy), op.getVarPtr(),1031              llvm::SmallVector<fir::IntOrValue, 1>{idxConst});1032          int64_t fieldIdx64 = static_cast<int64_t>(fieldIdx);1033          llvm::SmallVector<int64_t, 1> idxPath{fieldIdx64};1034          appendMemberMapIfNew(op, builder, op.getLoc(), fieldCoord, idxPath,1035                               field, newMapOpsForFields, newMemberIndexPaths);1036        }1037 1038        // Handle nested allocatable fields along any component chain1039        // referenced in the region via HLFIR designates.1040        llvm::SmallVector<llvm::SmallVector<int64_t>> seenIndexPaths;1041        for (mlir::Operation *sliceOp : mapVarForwardSlice) {1042          auto designateOp = mlir::dyn_cast<hlfir::DesignateOp>(sliceOp);1043          if (!designateOp || !designateOp.getComponent())1044            continue;1045          llvm::SmallVector<llvm::StringRef> compPathReversed;1046          compPathReversed.push_back(designateOp.getComponent()->strref());1047          mlir::Value curBase = designateOp.getMemref();1048          bool rootedAtMapArg = false;1049          while (true) {1050            if (auto parentDes = curBase.getDefiningOp<hlfir::DesignateOp>()) {1051              if (!parentDes.getComponent())1052                break;1053              compPathReversed.push_back(parentDes.getComponent()->strref());1054              curBase = parentDes.getMemref();1055              continue;1056            }1057            if (auto decl = curBase.getDefiningOp<hlfir::DeclareOp>()) {1058              if (auto barg =1059                      mlir::dyn_cast<mlir::BlockArgument>(decl.getMemref()))1060                rootedAtMapArg = (barg == opBlockArg);1061            } else if (auto blockArg =1062                           mlir::dyn_cast_or_null<mlir::BlockArgument>(1063                               curBase)) {1064              rootedAtMapArg = (blockArg == opBlockArg);1065            }1066            break;1067          }1068          // Only process nested paths (2+ components). Single-component paths1069          // for direct fields are handled above.1070          if (!rootedAtMapArg || compPathReversed.size() < 2)1071            continue;1072          builder.setInsertionPoint(op);1073          llvm::SmallVector<int64_t> indexPath;1074          mlir::Type curTy = underlyingType;1075          mlir::Value coordRef = op.getVarPtr();1076          bool validPath = true;1077          for (llvm::StringRef compName : llvm::reverse(compPathReversed)) {1078            auto recTy = mlir::dyn_cast<fir::RecordType>(curTy);1079            if (!recTy) {1080              validPath = false;1081              break;1082            }1083            int32_t idx = recTy.getFieldIndex(compName);1084            if (idx < 0) {1085              validPath = false;1086              break;1087            }1088            indexPath.push_back(idx);1089            mlir::Type memTy = recTy.getType(idx);1090            fir::IntOrValue idxConst =1091                mlir::IntegerAttr::get(builder.getI32Type(), idx);1092            coordRef = fir::CoordinateOp::create(1093                builder, op.getLoc(), builder.getRefType(memTy), coordRef,1094                llvm::SmallVector<fir::IntOrValue, 1>{idxConst});1095            curTy = memTy;1096          }1097          if (!validPath)1098            continue;1099          if (auto finalRefTy =1100                  mlir::dyn_cast<fir::ReferenceType>(coordRef.getType())) {1101            mlir::Type eleTy = finalRefTy.getElementType();1102            if (fir::isAllocatableType(eleTy)) {1103              if (!containsPath(seenIndexPaths, indexPath)) {1104                seenIndexPaths.emplace_back(indexPath.begin(), indexPath.end());1105                appendMemberMapIfNew(op, builder, op.getLoc(), coordRef,1106                                     indexPath, compPathReversed.front(),1107                                     newMapOpsForFields, newMemberIndexPaths);1108              }1109            }1110          }1111        }1112 1113        if (newMapOpsForFields.empty())1114          return mlir::WalkResult::advance();1115 1116        // Deduplicate by index path to avoid emitting duplicate members for1117        // the same component. Use a set-based key to keep this near O(n).1118        llvm::SmallVector<mlir::Value> dedupMapOps;1119        llvm::SmallVector<llvm::SmallVector<int64_t>> dedupIndexPaths;1120        llvm::StringSet<> seenKeys;1121        for (auto [i, mapOp] : llvm::enumerate(newMapOpsForFields)) {1122          const auto &path = newMemberIndexPaths[i];1123          llvm::SmallString<64> key;1124          buildPathKey(path, key);1125          if (seenKeys.contains(key))1126            continue;1127          seenKeys.insert(key);1128          dedupMapOps.push_back(mapOp);1129          dedupIndexPaths.emplace_back(path.begin(), path.end());1130        }1131        op.getMembersMutable().append(dedupMapOps);1132        llvm::SmallVector<llvm::SmallVector<int64_t>> newMemberIndices;1133        if (mlir::ArrayAttr oldAttr = op.getMembersIndexAttr())1134          for (mlir::Attribute indexList : oldAttr) {1135            llvm::SmallVector<int64_t> listVec;1136 1137            for (mlir::Attribute index : mlir::cast<mlir::ArrayAttr>(indexList))1138              listVec.push_back(mlir::cast<mlir::IntegerAttr>(index).getInt());1139 1140            newMemberIndices.emplace_back(std::move(listVec));1141          }1142        for (auto &path : dedupIndexPaths)1143          newMemberIndices.emplace_back(path);1144 1145        op.setMembersIndexAttr(builder.create2DI64ArrayAttr(newMemberIndices));1146        op.setPartialMap(true);1147 1148        return mlir::WalkResult::advance();1149      });1150 1151      // Expand type(C_PTR) only when unified_shared_memory is required,1152      // to ensure device-visible pointer size/behavior in USM scenarios1153      // without changing default expectations elsewhere.1154      func->walk([&](mlir::omp::MapInfoOp op) {1155        // Only expand C_PTR members when unified_shared_memory is required.1156        if (!moduleRequiresUSM(func->getParentOfType<mlir::ModuleOp>()))1157          return;1158        builder.setInsertionPoint(op);1159        genCptrMemberMap(op, builder);1160      });1161 1162      func->walk([&](mlir::omp::MapInfoOp op) {1163        // TODO: Currently only supports a single user for the MapInfoOp. This1164        // is fine for the moment, as the Fortran frontend will generate a1165        // new MapInfoOp with at most one user currently. In the case of1166        // members of other objects, like derived types, the user would be the1167        // parent. In cases where it's a regular non-member map, the user would1168        // be the target operation it is being mapped by.1169        //1170        // However, when/if we optimise/cleanup the IR we will have to extend1171        // this pass to support multiple users, as we may wish to have a map1172        // be re-used by multiple users (e.g. across multiple targets that map1173        // the variable and have identical map properties).1174        assert(llvm::hasSingleElement(op->getUsers()) &&1175               "OMPMapInfoFinalization currently only supports single users "1176               "of a MapInfoOp");1177 1178        if (hasADescriptor(op.getVarPtr().getDefiningOp(),1179                           fir::unwrapRefType(op.getVarType()))) {1180          builder.setInsertionPoint(op);1181          mlir::Operation *targetUser = getFirstTargetUser(op);1182          assert(targetUser && "expected user of map operation was not found");1183          genDescriptorMemberMaps(op, builder, targetUser);1184        }1185      });1186 1187      // Now that we've expanded all of our boxes into a descriptor and base1188      // address map where necessary, we check if the map owner is an1189      // enter/exit/target data directive, and if they are we drop the initial1190      // descriptor (top-level parent) and replace it with the1191      // base_address/data.1192      //1193      // This circumvents issues with stack allocated descriptors bound to1194      // device colliding which in Flang is rather trivial for a user to do by1195      // accident due to the rather pervasive local intermediate descriptor1196      // generation that occurs whenever you pass boxes around different scopes.1197      // In OpenMP 6+ mapping these would be a user error as the tools required1198      // to circumvent these issues are provided by the spec (ref_ptr/ptee map1199      // types), but in prior specifications these tools are not available and1200      // it becomes an implementation issue for us to solve.1201      //1202      // We do this by dropping the top-level descriptor which will be the stack1203      // descriptor when we perform enter/exit maps, as we don't want these to1204      // be bound until necessary which is when we utilise the descriptor type1205      // within a target region. At which point we map the relevant descriptor1206      // data and the runtime should correctly associate the data with the1207      // descriptor and bind together and allow clean mapping and execution.1208      for (auto *op : deferrableDesc) {1209        auto mapOp = llvm::dyn_cast<mlir::omp::MapInfoOp>(op);1210        mlir::Operation *targetUser = getFirstTargetUser(mapOp);1211        assert(targetUser && "expected user of map operation was not found");1212        builder.setInsertionPoint(mapOp);1213        removeTopLevelDescriptor(mapOp, builder, targetUser);1214        addImplicitDescriptorMapToTargetDataOp(mapOp, builder, *targetUser);1215      }1216 1217      // Wait until after we have generated all of our maps to add them onto1218      // the target's block arguments, simplifying the process as there would be1219      // no need to avoid accidental duplicate additions.1220      func->walk([&](mlir::omp::MapInfoOp op) {1221        mlir::Operation *targetUser = getFirstTargetUser(op);1222        assert(targetUser && "expected user of map operation was not found");1223        addImplicitMembersToTarget(op, builder, targetUser);1224      });1225    });1226  }1227};1228 1229} // namespace1230