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