[flang-commits] [flang] [mlir] [acc] Pre-cg pass for handling flang data mapping to offload runtime (PR #221282)
Razvan Lupusoru via flang-commits
flang-commits at lists.llvm.org
Fri Sep 4 09:52:26 PDT 2026
https://github.com/razvanlupusoru created https://github.com/llvm/llvm-project/pull/221282
Introduce acc.map_info as the acc dialect operation that captures a single mapped object's offload facts (attach point, descriptor kind, bounds, map flags, and size) so later codegen can lower data clauses to the offload runtime from one representation.
Add a Flang pre-codegen pass, ACCMapInfoPrep, that materializes acc.map_info for FIR-typed operands: it infers Fortran descriptor attach and CFI facts, sizes mapped and privatized storage, and folds paired enter/exit clause effects into map flags. This keeps FIR-specific mapping logic out of ACCToLLVM and leaves that conversion to consume map_info verbatim.
>From 3d556b907b7b82ad63f2cf7328a47a4372ef4e12 Mon Sep 17 00:00:00 2001
From: Razvan Lupusoru <rlupusoru at nvidia.com>
Date: Wed, 26 Aug 2026 14:06:44 -0700
Subject: [PATCH] [acc] Pre-cg pass for handling flang data mapping to offload
runtime
Introduce acc.map_info as the acc dialect operation that captures a
single mapped object's offload facts (attach point, descriptor kind,
bounds, map flags, and size) so later codegen can lower data clauses to
the offload runtime from one representation.
Add a Flang pre-codegen pass, ACCMapInfoPrep, that materializes
acc.map_info for FIR-typed operands: it infers Fortran descriptor attach
and CFI facts, sizes mapped and privatized storage, and folds paired
enter/exit clause effects into map flags. This keeps FIR-specific
mapping logic out of ACCToLLVM and leaves that conversion to consume
map_info verbatim.
---
.../include/flang/Optimizer/OpenACC/Passes.td | 18 +
.../flang/Semantics/runtime-type-info.h | 4 +
flang/lib/Optimizer/Dialect/FIRType.cpp | 3 +
.../Analysis/FIROpenACCSupportAnalysis.cpp | 3 +-
.../Support/FIROpenACCTypeInterfaces.cpp | 6 +
.../OpenACC/Transforms/ACCMapInfoPrep.cpp | 562 ++++++++++++++++++
.../OpenACC/Transforms/CMakeLists.txt | 4 +-
flang/lib/Semantics/runtime-type-info.cpp | 4 +-
.../OpenACC/acc-fir-map-info-prep-attach.mlir | 81 +++
.../acc-fir-map-info-prep-clauses.mlir | 229 +++++++
.../acc-fir-map-info-prep-declare-global.mlir | 40 ++
.../OpenACC/acc-fir-map-info-prep-exit.mlir | 222 +++++++
...cc-fir-map-info-prep-implicit-present.mlir | 135 +++++
.../acc-fir-map-info-prep-privatize.mlir | 122 ++++
.../OpenACC/acc-fir-map-info-prep-types.mlir | 228 +++++++
.../Fir/OpenACC/acc-fir-map-info-prep.mlir | 86 +++
.../OpenACC/FIROpenACCSupportAnalysisTest.cpp | 14 +
.../mlir/Dialect/OpenACC/OpenACCCGEnums.td | 85 +++
.../mlir/Dialect/OpenACC/OpenACCCGOps.td | 122 ++++
.../mlir/Dialect/OpenACC/OpenACCOps.td | 25 +-
.../mlir/Dialect/OpenACC/OpenACCUtilsCG.h | 57 ++
.../mlir/Dialect/OpenACC/OpenACCUtilsType.h | 17 +-
mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp | 124 ++--
mlir/lib/Dialect/OpenACC/IR/OpenACCCG.cpp | 33 +
.../Dialect/OpenACC/Utils/OpenACCUtilsCG.cpp | 302 ++++++++++
.../OpenACC/Utils/OpenACCUtilsType.cpp | 21 +-
.../Dialect/OpenACC/invalid-cg-mapinfo.mlir | 44 ++
mlir/test/Dialect/OpenACC/ops-cg-mapinfo.mlir | 143 +++++
mlir/test/Dialect/OpenACC/ops.mlir | 12 +
29 files changed, 2685 insertions(+), 61 deletions(-)
create mode 100644 flang/lib/Optimizer/OpenACC/Transforms/ACCMapInfoPrep.cpp
create mode 100644 flang/test/Fir/OpenACC/acc-fir-map-info-prep-attach.mlir
create mode 100644 flang/test/Fir/OpenACC/acc-fir-map-info-prep-clauses.mlir
create mode 100644 flang/test/Fir/OpenACC/acc-fir-map-info-prep-declare-global.mlir
create mode 100644 flang/test/Fir/OpenACC/acc-fir-map-info-prep-exit.mlir
create mode 100644 flang/test/Fir/OpenACC/acc-fir-map-info-prep-implicit-present.mlir
create mode 100644 flang/test/Fir/OpenACC/acc-fir-map-info-prep-privatize.mlir
create mode 100644 flang/test/Fir/OpenACC/acc-fir-map-info-prep-types.mlir
create mode 100644 flang/test/Fir/OpenACC/acc-fir-map-info-prep.mlir
create mode 100644 mlir/include/mlir/Dialect/OpenACC/OpenACCCGEnums.td
create mode 100644 mlir/test/Dialect/OpenACC/invalid-cg-mapinfo.mlir
create mode 100644 mlir/test/Dialect/OpenACC/ops-cg-mapinfo.mlir
diff --git a/flang/include/flang/Optimizer/OpenACC/Passes.td b/flang/include/flang/Optimizer/OpenACC/Passes.td
index a2c5e4677fcd2..0a1f636e1849c 100644
--- a/flang/include/flang/Optimizer/OpenACC/Passes.td
+++ b/flang/include/flang/Optimizer/OpenACC/Passes.td
@@ -109,6 +109,24 @@ def ACCOptimizeFirstprivateMap
let dependentDialects = ["mlir::acc::OpenACCDialect", "fir::FIROpsDialect"];
}
+def ACCMapInfoPrep
+ : InterfacePass<"acc-fir-map-info-prep", "mlir::FunctionOpInterface"> {
+ let summary = "Materialize acc.map_info for FIR mappings and privatization";
+ let description = [{
+ Replaces FIR-typed OpenACC data-entry operations, and the data-exit
+ operations paired with them, with `acc.map_info`. FIR-specific facts
+ include attach points inferred from Fortran descriptors (allocatable,
+ pointer, and class), CFI `descKind`, mapped sizes, and `mapFlags` that
+ fold enter and exit clause effects. Privatized storage is wrapped with its
+ byte size and private parallel-level flags, forming the contract for the
+ runtime allocation and replication of that storage. The result is a single
+ representation of runtime map and allocation metadata that no longer
+ depends on FIR types or attributes.
+ }];
+ let dependentDialects = ["mlir::acc::OpenACCDialect", "fir::FIROpsDialect",
+ "mlir::arith::ArithDialect"];
+}
+
def ACCDevicePtrToCUFKernel
: Pass<"acc-device-ptr-to-cuf-kernel", "mlir::ModuleOp"> {
let summary = "Pass device addresses to CUDA Fortran kernels launched inside "
diff --git a/flang/include/flang/Semantics/runtime-type-info.h b/flang/include/flang/Semantics/runtime-type-info.h
index 06a0807462b61..27e355f35a299 100644
--- a/flang/include/flang/Semantics/runtime-type-info.h
+++ b/flang/include/flang/Semantics/runtime-type-info.h
@@ -40,6 +40,10 @@ constexpr char typeInfoBuiltinModule[]{"__fortran_type_info"};
/// derived type descriptors.
constexpr char typeDescriptorTypeName[]{"derivedtype"};
+/// Name of the size-in-bytes component in the DerivedType type of the
+/// __Fortran_type_info module
+constexpr char sizeInBytesCompName[]{"sizeinbytes"};
+
/// Name of the bindings descriptor component in the DerivedType type of the
/// __Fortran_type_info module
constexpr char bindingDescCompName[]{"binding"};
diff --git a/flang/lib/Optimizer/Dialect/FIRType.cpp b/flang/lib/Optimizer/Dialect/FIRType.cpp
index 178860239e17d..5f0dd68aa9396 100644
--- a/flang/lib/Optimizer/Dialect/FIRType.cpp
+++ b/flang/lib/Optimizer/Dialect/FIRType.cpp
@@ -1650,6 +1650,9 @@ fir::getTypeSizeAndAlignment(mlir::Location loc, mlir::Type ty,
return std::pair{size, alignment};
}
if (auto seqTy = mlir::dyn_cast<fir::SequenceType>(ty)) {
+ // Dynamic / unknown shapes have no compile-time byte size.
+ if (seqTy.hasDynamicExtents() || seqTy.hasUnknownShape())
+ return std::nullopt;
auto result = getTypeSizeAndAlignment(loc, seqTy.getEleTy(), dl, kindMap);
if (!result)
return result;
diff --git a/flang/lib/Optimizer/OpenACC/Analysis/FIROpenACCSupportAnalysis.cpp b/flang/lib/Optimizer/OpenACC/Analysis/FIROpenACCSupportAnalysis.cpp
index d8a0c43e40117..d0fe71657a6bf 100644
--- a/flang/lib/Optimizer/OpenACC/Analysis/FIROpenACCSupportAnalysis.cpp
+++ b/flang/lib/Optimizer/OpenACC/Analysis/FIROpenACCSupportAnalysis.cpp
@@ -63,7 +63,8 @@ FIROpenACCSupportAnalysis::getTypeSizeAndAlignment(
if (!dl)
return std::nullopt;
- if (isa<fir::ReferenceType, fir::HeapType, fir::LLVMPointerType>(ty))
+ if (isa<fir::ReferenceType, fir::PointerType, fir::HeapType,
+ fir::LLVMPointerType>(ty))
return mlir::acc::getTypeSizeAndAlignment(
LLVM::LLVMPointerType::get(ty.getContext()), module, *dl, &support);
diff --git a/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp b/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
index 2a1230dc7f5c9..e070de2d908b2 100644
--- a/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
+++ b/flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
@@ -27,6 +27,7 @@
#include "flang/Optimizer/Support/Utils.h"
#include "flang/Optimizer/Transforms/FIRToMemRefTypeConverter.h"
#include "mlir/Dialect/Arith/IR/Arith.h"
+#include "mlir/Dialect/LLVMIR/LLVMTypes.h"
#include "mlir/Dialect/OpenACC/OpenACC.h"
#include "mlir/Dialect/OpenACC/OpenACCUtils.h"
#include "mlir/IR/BuiltinOps.h"
@@ -100,6 +101,11 @@ std::optional<llvm::TypeSize> OpenACCMappableModel<Ty>::getSizeInBytes(
if (auto mappableTy = mlir::dyn_cast<mlir::acc::MappableType>(eleTy))
return mappableTy.getSizeInBytes(var, accBounds, dataLayout);
+ // Procedure pointers map as a single address-sized slot.
+ if (mlir::isa<mlir::FunctionType>(eleTy))
+ return llvm::TypeSize::getFixed(dataLayout.getTypeSize(
+ mlir::LLVM::LLVMPointerType::get(type.getContext())));
+
// Dynamic extents or unknown ranks generally do not have compile-time
// computable dimensions.
auto seqType = mlir::dyn_cast<fir::SequenceType>(eleTy);
diff --git a/flang/lib/Optimizer/OpenACC/Transforms/ACCMapInfoPrep.cpp b/flang/lib/Optimizer/OpenACC/Transforms/ACCMapInfoPrep.cpp
new file mode 100644
index 0000000000000..4ba5cc8b2d909
--- /dev/null
+++ b/flang/lib/Optimizer/OpenACC/Transforms/ACCMapInfoPrep.cpp
@@ -0,0 +1,562 @@
+//===- ACCMapInfoPrep.cpp - Materialize acc.map_info for FIR --------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This pass replaces the OpenACC data clause operations on FIR-typed operands
+// with acc.map_info. A map entry states everything the offload runtime needs
+// about one mapped object: the address to transfer, the pointer slot to attach
+// it to, the Fortran descriptor that describes it, the element and object
+// sizes, the bounds of the section being mapped, and the map-type flags. All
+// of that is derived from FIR types and attributes here, so that lowering to
+// runtime calls can work from the map entry alone.
+//
+// A data entry operation and the data exit operations paired with it describe
+// the same object, so they collapse into a single map entry whose flags carry
+// the effects of both directions. Privatized storage (acc.privatize,
+// acc.firstprivate_map) is wrapped the same way, with the parallel levels that
+// govern its replication.
+//
+// Example transformation, for an allocatable scalar in a copy clause:
+//
+// Before:
+// %slot = fir.declare %alloca : !fir.ref<!fir.box<!fir.heap<i32>>>
+// %in = acc.copyin varPtr(%slot : !fir.ref<!fir.box<!fir.heap<i32>>>)
+// dataClause(acc_copy) name("n") -> !fir.ref<!fir.box<!fir.heap<i32>>>
+// acc.data dataOperands(%in : !fir.ref<!fir.box<!fir.heap<i32>>>) {
+// ...
+// }
+// acc.copyout accPtr(%in : !fir.ref<!fir.box<!fir.heap<i32>>>)
+// to varPtr(%slot : !fir.ref<!fir.box<!fir.heap<i32>>>)
+// dataClause(acc_copy) name("n")
+//
+// After:
+// %slot = fir.declare %alloca : !fir.ref<!fir.box<!fir.heap<i32>>>
+// %c0 = arith.constant 0 : i64
+// // The copyin and the copyout fold into one entry, so the flags name both
+// // directions. The descriptor makes this an attach (ptr_and_obj) of a
+// // CFI-described object, and a size of zero defers the byte count to that
+// // descriptor. exitLoc points at the erased copyout.
+// %map = acc.map_info varPtr(%slot : !fir.ref<!fir.box<!fir.heap<i32>>>)
+// size(%c0 : i64) elementSize(4) name("n") exitLoc(...)
+// descKind(cfi) mapFlags(to,from,ptr_and_obj)
+// -> !fir.ref<!fir.box<!fir.heap<i32>>>
+// acc.data dataOperands(%map : !fir.ref<!fir.box<!fir.heap<i32>>>) {
+// ...
+// }
+//
+//===----------------------------------------------------------------------===//
+
+#include "flang/Optimizer/Dialect/CUF/Attributes/CUFAttr.h"
+#include "flang/Optimizer/Dialect/FIROps.h"
+#include "flang/Optimizer/Dialect/FIRType.h"
+#include "flang/Optimizer/OpenACC/Analysis/FIROpenACCSupportAnalysis.h"
+#include "flang/Optimizer/OpenACC/Passes.h"
+#include "flang/Optimizer/OpenACC/Support/FIROpenACCUtils.h"
+#include "flang/Optimizer/Support/InternalNames.h"
+#include "flang/Semantics/runtime-type-info.h"
+#include "mlir/Dialect/Arith/IR/Arith.h"
+#include "mlir/Dialect/OpenACC/Analysis/OpenACCSupport.h"
+#include "mlir/Dialect/OpenACC/OpenACC.h"
+#include "mlir/Dialect/OpenACC/OpenACCUtilsCG.h"
+#include "mlir/Dialect/OpenACC/OpenACCUtilsType.h"
+#include "mlir/IR/BuiltinOps.h"
+#include "mlir/IR/SymbolTable.h"
+#include "mlir/Pass/Pass.h"
+
+namespace fir {
+namespace acc {
+#define GEN_PASS_DEF_ACCMAPINFOPREP
+#include "flang/Optimizer/OpenACC/Passes.h.inc"
+} // namespace acc
+} // namespace fir
+
+using namespace mlir;
+
+namespace {
+
+/// Returns the pointer slot holding the address of \p mapVar, which the runtime
+/// rewrites once the pointee has a device copy. Such a slot exists only when
+/// the clause maps a pointee obtained by dereferencing it; mapping the slot
+/// itself has no second indirection and therefore no attach point. Only slots
+/// reached through a Fortran descriptor are recognized here.
+static Value findAttachPoint(Value mapVar) {
+ if (auto boxAddr = mapVar.getDefiningOp<fir::BoxAddrOp>()) {
+ if (auto load = boxAddr.getVal().getDefiningOp<fir::LoadOp>())
+ return load.getMemref();
+ }
+ if (fir::isa_box_type(fir::unwrapRefType(mapVar.getType()))) {
+ if (auto load = mapVar.getDefiningOp<fir::LoadOp>())
+ return load.getMemref();
+ }
+ return {};
+}
+
+/// True when the clause maps descriptor storage rather than a plain object. The
+/// runtime must then treat the entry as pointer-and-object: besides the
+/// descriptor bytes it fixes up the base address recorded inside them.
+///
+/// The base-address slot is identified downstream by the descriptor address
+/// (`desc`) alone, which currently works only because `base_addr` sits at
+/// offset 0 of the F18/CFI descriptor, so `&desc == &desc->base_addr`. If that
+/// layout changes, the attach point must be formed from the descriptor's actual
+/// `base_addr` field rather than the descriptor address.
+static bool mapsDescriptorStorage(Value mapVar) {
+ auto refTy = dyn_cast<fir::ReferenceType>(mapVar.getType());
+ return refTy && fir::isa_box_type(refTy.getEleTy());
+}
+
+static bool isManagedData(Value var) {
+ auto hasManagedAttr = [](Value v) {
+ Operation *op = v.getDefiningOp();
+ return op && cuf::hasDataAttr(op, cuf::DataAttribute::Managed);
+ };
+ if (hasManagedAttr(var))
+ return true;
+ Value orig = fir::acc::getOriginalDef(var, /*stripDeclare=*/false);
+ return orig && orig != var && hasManagedAttr(orig);
+}
+
+static std::pair<acc::DataDescKind, Value>
+findDescriptorFacts(Value mapVar, Type mappedObjectType, bool isImplicit) {
+ Type mapTy = mapVar.getType();
+ if (auto refTy = dyn_cast<fir::ReferenceType>(mapTy)) {
+ if (fir::isa_box_type(refTy.getEleTy()))
+ return {acc::DataDescKind::cfi, mapVar};
+ }
+ if (fir::isa_box_type(fir::unwrapRefType(mapTy)))
+ return {acc::DataDescKind::cfi, mapVar};
+ // box_addr of a loaded box can be either the pointee of a nested descriptor
+ // map or a host data-base address derived from an already-mapped box. The
+ // latter is always an implicit clause; only treat the explicit case as CFI.
+ if (!isImplicit) {
+ if (auto boxAddr = mapVar.getDefiningOp<fir::BoxAddrOp>()) {
+ Value boxVal = boxAddr.getVal();
+ if (fir::isa_box_type(boxVal.getType()) &&
+ boxVal.getDefiningOp<fir::LoadOp>())
+ return {acc::DataDescKind::cfi, boxVal};
+ }
+ }
+ (void)mappedObjectType;
+ return {acc::DataDescKind::none, {}};
+}
+
+/// Byte size of \p type as storage - what the type alone describes, without a
+/// value to interpret it as a mapped object. The types here are mostly FIR
+/// ones, which only the OpenACCSupport implementation sizes; it falls back to
+/// the dialect-agnostic acc::getTypeSizeAndAlignment for the rest.
+static std::optional<int64_t> computeTypeSizeBytes(acc::OpenACCSupport &support,
+ ModuleOp module, Type type) {
+ std::optional<acc::TypeSizeAndAlignment> sizeAndAlignment =
+ support.getTypeSizeAndAlignment(type, module);
+ if (!sizeAndAlignment || sizeAndAlignment->first.isScalable())
+ return std::nullopt;
+ return static_cast<int64_t>(sizeAndAlignment->first.getFixedValue());
+}
+
+/// Load the size-in-bytes field from the Fortran type descriptor for
+/// \p recordType. Returns null when no matching type-descriptor global is
+/// present.
+static Value loadRecordTypeSizeFromTypeDesc(
+ Location loc, fir::RecordType recordType, Operation *entryOp,
+ std::optional<SymbolTable> &symbolTable, OpBuilder &builder) {
+ ModuleOp module = entryOp->getParentOfType<ModuleOp>();
+ if (!module)
+ return {};
+
+ // Keep a TypeDesc use so later passes see the record as referenced.
+ (void)fir::TypeDescOp::create(builder, loc, TypeAttr::get(recordType));
+
+ if (!symbolTable)
+ symbolTable.emplace(module);
+ StringAttr typeDescName = builder.getStringAttr(
+ fir::NameUniquer::getTypeDescriptorAssemblyName(recordType.getName()));
+ auto global = symbolTable->lookup<fir::GlobalOp>(typeDescName);
+ if (!global)
+ return {};
+
+ auto typeDescRecTy = dyn_cast<fir::RecordType>(global.getType());
+ if (!typeDescRecTy)
+ return {};
+
+ Value typeDescAddr = fir::AddrOfOp::create(
+ builder, loc, fir::ReferenceType::get(typeDescRecTy), global.getSymbol());
+ Type fieldTy = fir::FieldType::get(builder.getContext());
+ Value field = fir::FieldIndexOp::create(
+ builder, loc, fieldTy, Fortran::semantics::sizeInBytesCompName,
+ typeDescRecTy, ValueRange{});
+ Type coorTy = fir::ReferenceType::get(
+ typeDescRecTy.getType(Fortran::semantics::sizeInBytesCompName));
+ Value addr =
+ fir::CoordinateOp::create(builder, loc, coorTy, typeDescAddr, field);
+ return fir::LoadOp::create(builder, loc, addr);
+}
+
+static Value materializeMapSize(acc::OpenACCSupport &support,
+ Operation *entryOp, Value var, Type varType,
+ acc::DataDescKind descKind, ValueRange bounds,
+ acc::MapFlags mapFlags,
+ std::optional<SymbolTable> &symbolTable,
+ OpBuilder &builder) {
+ Location loc = entryOp->getLoc();
+ Type i64Ty = builder.getI64Type();
+
+ int64_t staticSize = -1;
+ if (std::optional<DataLayout> dl = acc::getDataLayout(entryOp)) {
+ // Privatized maps keep the full object ArgSize; AccDataDesc carries the
+ // section. Device firstprivate copies still index with the parent lower
+ // bound, so a compact section size (or ArgSize 0) is incorrect.
+ if (bitEnumContainsAny(mapFlags, acc::MapFlags::private_))
+ staticSize = acc::computeMapInfoSizeBytes(
+ var, varType, acc::DataDescKind::none, /*bounds=*/{}, *dl, &support);
+ else
+ staticSize = acc::computeMapInfoSizeBytes(var, varType, descKind, bounds,
+ *dl, &support);
+ }
+
+ // Derived types with descriptor fields often have no compile-time layout
+ // size; load the type descriptor's size-in-bytes field instead.
+ if (staticSize < 0) {
+ if (auto recordType =
+ dyn_cast<fir::RecordType>(fir::unwrapRefType(varType))) {
+ if (Value dynamicSize = loadRecordTypeSizeFromTypeDesc(
+ loc, recordType, entryOp, symbolTable, builder))
+ return dynamicSize;
+ }
+ }
+
+ // An implicit present of an object whose size is not recoverable is only an
+ // address lookup. Size 0 matches the present-table entry whatever its
+ // extents are, including a zero-sized array, whereas an unknown size does
+ // not. An explicit clause keeps the unknown size so that the runtime can
+ // report the missing data instead.
+ if (staticSize < 0 && bounds.empty() &&
+ bitEnumContainsAll(mapFlags,
+ acc::MapFlags::present | acc::MapFlags::implicit))
+ staticSize = 0;
+
+ return arith::ConstantIntOp::create(builder, loc, i64Ty, staticSize);
+}
+
+/// Describes the storage of \p baseTy - the base type of `acc.private_type` -
+/// as an element type whose extents are appended to \p extents. Extents that
+/// the type does not encode are `ShapedType::kDynamic` and are supplied by the
+/// `acc.privatize` dynamic sizes. Returns a null type when the type does not
+/// describe the storage, such as a descriptor that carries its own extents.
+static Type getPrivateStorageShape(Type baseTy,
+ SmallVectorImpl<int64_t> &extents) {
+ if (auto memrefTy = dyn_cast<MemRefType>(baseTy)) {
+ llvm::append_range(extents, memrefTy.getShape());
+ return memrefTy.getElementType();
+ }
+
+ Type storageTy = baseTy;
+ if (Type eleTy = fir::dyn_cast_ptrOrBoxEleTy(baseTy))
+ storageTy = eleTy;
+ if (fir::isa_box_type(storageTy))
+ return {};
+
+ if (auto seqTy = dyn_cast<fir::SequenceType>(storageTy)) {
+ if (seqTy.hasUnknownShape())
+ return {};
+ llvm::append_range(extents, seqTy.getShape());
+ return seqTy.getEleTy();
+ }
+ return storageTy;
+}
+
+/// Materializes the byte size of privatized storage. The extents that the type
+/// encodes are sized as an array, which is what applies the padded element
+/// stride; the dynamic extents are then multiplied in. Returns null when the
+/// size is not obtainable.
+static Value materializePrivateStorageSize(
+ acc::OpenACCSupport &support, ModuleOp module, acc::PrivatizeOp privatizeOp,
+ Type elementType, ArrayRef<int64_t> extents,
+ std::optional<SymbolTable> &symbolTable, OpBuilder &builder) {
+ Location loc = privatizeOp.getLoc();
+ ValueRange dynamicSizes = privatizeOp.getDynamicSizes();
+
+ SmallVector<int64_t> staticExtents;
+ for (int64_t extent : extents)
+ if (!ShapedType::isDynamic(extent))
+ staticExtents.push_back(extent);
+ if (extents.size() - staticExtents.size() != dynamicSizes.size())
+ return {};
+
+ // Size the extents the type encodes as a FIR array, which is what applies
+ // the padded element stride - also for a memref base, since the stride rule
+ // does not depend on where the element type comes from. A single element
+ // stands in when all extents are dynamic: its size is the stride that those
+ // extents multiply.
+ Type staticTy = elementType;
+ if (!extents.empty()) {
+ if (staticExtents.empty())
+ staticExtents.push_back(1);
+ staticTy = fir::SequenceType::get(staticExtents, elementType);
+ }
+
+ Value size;
+ if (std::optional<int64_t> staticBytes =
+ computeTypeSizeBytes(support, module, staticTy)) {
+ size = arith::ConstantIntOp::create(builder, loc, builder.getI64Type(),
+ *staticBytes);
+ } else if (auto recordType = dyn_cast<fir::RecordType>(elementType)) {
+ // A derived type whose layout is not computable here carries its padded
+ // size in the Fortran type descriptor.
+ size = loadRecordTypeSizeFromTypeDesc(
+ loc, recordType, privatizeOp.getOperation(), symbolTable, builder);
+ if (!size)
+ return {};
+ int64_t staticExtent = 1;
+ for (int64_t extent : staticExtents)
+ staticExtent *= extent;
+ if (staticExtent != 1) {
+ Value extentVal = arith::ConstantIntOp::create(
+ builder, loc, size.getType(), staticExtent);
+ size = arith::MulIOp::create(builder, loc, size, extentVal);
+ }
+ } else {
+ return {};
+ }
+
+ for (Value dynamicSize : dynamicSizes) {
+ Value extentVal =
+ arith::IndexCastOp::create(builder, loc, size.getType(), dynamicSize);
+ size = arith::MulIOp::create(builder, loc, size, extentVal);
+ }
+ return size;
+}
+
+/// Wraps \p privatizeOp so that privatized storage carries offload facts in
+/// `acc.map_info` like any other mapped variable, including the parallel
+/// levels that select gang/worker/vector private replication.
+static std::optional<acc::MapInfoOp> buildPrivatizeMapInfo(
+ acc::OpenACCSupport &support, ModuleOp module, acc::PrivatizeOp privatizeOp,
+ const acc::ACCToGPUMappingPolicy &policy,
+ std::optional<SymbolTable> &symbolTable, OpBuilder &builder) {
+ auto privateTy =
+ dyn_cast<acc::PrivateType>(privatizeOp.getResult().getType());
+ if (!privateTy)
+ return std::nullopt;
+ Type baseTy = privateTy.getBaseTy();
+
+ SmallVector<int64_t> extents;
+ Type elementType = getPrivateStorageShape(baseTy, extents);
+ if (!elementType)
+ return std::nullopt;
+
+ builder.setInsertionPointAfter(privatizeOp);
+ Value size = materializePrivateStorageSize(
+ support, module, privatizeOp, elementType, extents, symbolTable, builder);
+ if (!size)
+ return std::nullopt;
+
+ return acc::MapInfoOp::create(
+ builder, privatizeOp.getLoc(), privateTy, privatizeOp.getResult(), baseTy,
+ acc::computePrivatizeMapFlags(privatizeOp, policy), /*varPtrPtr=*/{},
+ /*desc=*/{}, acc::DataDescKind::none, /*bounds=*/{}, /*name=*/{},
+ computeTypeSizeBytes(support, module, elementType), size);
+}
+
+static std::optional<acc::MapInfoOp>
+buildMapInfo(acc::OpenACCSupport &support, ModuleOp module, Operation *entryOp,
+ std::optional<SymbolTable> &symbolTable, OpBuilder &builder) {
+ if (!entryOp || isa<acc::MapInfoOp>(entryOp))
+ return std::nullopt;
+ if (!isa<ACC_DATA_ENTRY_OPS>(entryOp))
+ return std::nullopt;
+
+ Value var = acc::getVar(entryOp);
+ if (!var)
+ var = acc::getVarPtr(entryOp);
+ // This pass only materializes map_info for FIR-typed operands.
+ if (!var || !fir::isa_fir_type(var.getType()))
+ return std::nullopt;
+
+ std::optional<acc::DataClause> clause = acc::getDataClause(entryOp);
+ if (!clause)
+ return std::nullopt;
+
+ Type varType = acc::getVarType(entryOp);
+ if (!varType)
+ varType = fir::unwrapRefType(var.getType());
+
+ // Implicit clauses that carry a data address derived from a box are not
+ // descriptor maps and must not pick up attach / CFI facts from that box.
+ // Mapping descriptor storage directly is unaffected: it stays a
+ // pointer-and-object map whether the clause is implicit or explicit.
+ const bool isImplicit = acc::getImplicitFlag(entryOp);
+ // Preserve an attach point already made explicit on the data entry. Otherwise
+ // infer one from an explicit FIR descriptor dereference. Implicit present
+ // siblings of a descriptor map deliberately do not infer it: the descriptor
+ // map already owns the attach semantics.
+ Value attachPoint = acc::getVarPtrPtr(entryOp);
+ if (!attachPoint && !isImplicit)
+ attachPoint = findAttachPoint(var);
+
+ auto [descKind, desc] = findDescriptorFacts(var, varType, isImplicit);
+ // When the mapped var *is* the descriptor, leave `desc` unset and rely on
+ // `var` whenever descKind is set. Keep `desc` only when it differs (e.g. a
+ // pointee map whose CFI metadata lives in a separate box value).
+ if (desc && desc == var)
+ desc = {};
+ acc::MapFlags mapFlags = acc::computeDataClauseMapFlags(
+ entryOp, attachPoint || mapsDescriptorStorage(var));
+ if (isManagedData(var))
+ mapFlags = mapFlags | acc::MapFlags::managed_devptr;
+
+ Type elementType = fir::getFortranElementType(varType);
+ std::optional<int64_t> elementSize =
+ computeTypeSizeBytes(support, module, elementType);
+
+ SmallVector<Value> bounds = acc::getBounds(entryOp);
+ if (auto seqTy =
+ dyn_cast_or_null<fir::SequenceType>(fir::unwrapRefType(varType)))
+ acc::populateSourceExtents(bounds, seqTy.getShape(), builder);
+
+ Location loc = entryOp->getLoc();
+ Value size = materializeMapSize(support, entryOp, var, varType, descKind,
+ bounds, mapFlags, symbolTable, builder);
+
+ return acc::MapInfoOp::create(builder, loc, entryOp->getResult(0).getType(),
+ var, varType, mapFlags, attachPoint, desc,
+ descKind, bounds, acc::getVarName(entryOp),
+ elementSize, size);
+}
+
+static void materializeMapInfoForEntryOp(
+ Operation *entryOp,
+ llvm::function_ref<std::optional<acc::MapInfoOp>(Operation *, OpBuilder &)>
+ buildMapInfo) {
+ if (!entryOp || isa<acc::MapInfoOp>(entryOp))
+ return;
+ OpBuilder builder(entryOp);
+ std::optional<acc::MapInfoOp> mapInfo = buildMapInfo(entryOp, builder);
+ if (!mapInfo)
+ return;
+
+ // declare_enter uses device_resident to establish a persistent allocation.
+ // A kernel use must instead find that existing allocation with PRESENT:
+ // treating it as device_resident again would perform declaration-time
+ // mapping at every launch rather than diagnose a missing declaration map.
+ // Represent the two call sites with distinct map_info operations.
+ acc::MapFlags flags = mapInfo->getMapFlags();
+ if (bitEnumContainsAny(flags, acc::MapFlags::device_resident)) {
+ SmallVector<OpOperand *> kernelUses;
+ for (OpOperand &use : entryOp->getResult(0).getUses()) {
+ Operation *owner = use.getOwner();
+ if (isa<acc::KernelEnvironmentOp>(owner) ||
+ owner->getParentOfType<acc::KernelEnvironmentOp>())
+ kernelUses.push_back(&use);
+ }
+ if (!kernelUses.empty()) {
+ builder.setInsertionPointAfter(*mapInfo);
+ acc::MapFlags kernelFlags =
+ acc::bitEnumClear(flags, acc::MapFlags::device_resident) |
+ acc::MapFlags::present;
+ acc::MapInfoOp kernelMap = acc::MapInfoOp::create(
+ builder, mapInfo->getLoc(), mapInfo->getAccVar().getType(),
+ mapInfo->getVar(), mapInfo->getVarType(), kernelFlags,
+ mapInfo->getVarPtrPtr(), mapInfo->getDesc(), mapInfo->getDescKind(),
+ mapInfo->getBounds(), acc::getVarName(*mapInfo),
+ acc::getMapElementSize(*mapInfo), mapInfo->getSize());
+ for (OpOperand *use : kernelUses)
+ use->set(kernelMap.getAccVar());
+ }
+ }
+
+ // The exit clause effects are folded into the map flags, which leaves the
+ // paired data exit operations describing nothing that the map entry does not
+ // already carry - except where those effects happen, which for a structured
+ // construct is its end directive.
+ SmallVector<Operation *> exitOps =
+ acc::getPairedDataExitOps(entryOp->getResult(0));
+ if (!exitOps.empty() && exitOps.front()->getLoc() != mapInfo->getLoc())
+ mapInfo->setExitLoc(exitOps.front()->getLoc());
+ for (Operation *exitOp : exitOps)
+ exitOp->erase();
+
+ entryOp->getResult(0).replaceAllUsesWith(mapInfo->getAccVar());
+ entryOp->erase();
+}
+
+struct ACCMapInfoPrep
+ : public fir::acc::impl::ACCMapInfoPrepBase<ACCMapInfoPrep> {
+ void runOnOperation() override {
+ FunctionOpInterface func = getOperation();
+ ModuleOp module = func->getParentOfType<ModuleOp>();
+ if (!module)
+ return;
+
+ // FIR type sizes come from the OpenACCSupport implementation registered
+ // earlier in the pipeline. Register the FIR one when this pass runs on its
+ // own, so that sizing does not silently fall back to the generic handling
+ // that knows no FIR type.
+ auto cachedAnalysis =
+ getCachedParentAnalysis<acc::OpenACCSupport>(func->getParentOp());
+ acc::OpenACCSupport *localSupport = nullptr;
+ if (!cachedAnalysis) {
+ localSupport = &getAnalysis<acc::OpenACCSupport>();
+ localSupport->setImplementation(fir::acc::FIROpenACCSupportAnalysis());
+ }
+ acc::OpenACCSupport &support =
+ cachedAnalysis ? cachedAnalysis->get() : *localSupport;
+ acc::DefaultACCToGPUMappingPolicy mappingPolicy;
+
+ auto createEntryMapInfo = [&](Operation *entryOp, OpBuilder &builder) {
+ return buildMapInfo(support, module, entryOp, symbolTable, builder);
+ };
+
+ // Collect before rewriting, which replaces and erases the entry ops.
+ SmallVector<Operation *> entryOps;
+ SmallVector<acc::PrivatizeOp> privatizeOps;
+ func->walk([&](Operation *op) {
+ if (auto privatizeOp = dyn_cast<acc::PrivatizeOp>(op)) {
+ privatizeOps.push_back(privatizeOp);
+ return;
+ }
+ if (!isa<ACC_DATA_ENTRY_OPS>(op))
+ return;
+ // acc.use_device stays itself to keep acc.host_data intact, and so does
+ // acc.cache for shared memory promotion. Private, firstprivate and
+ // reduction storage is created from their recipes; of those clauses only
+ // the firstprivate initial value is mapped, as acc.firstprivate_map.
+ if (isa<acc::UseDeviceOp, acc::CacheOp, acc::PrivateOp,
+ acc::FirstprivateOp, acc::ReductionOp>(op))
+ return;
+ entryOps.push_back(op);
+ });
+
+ for (Operation *entryOp : entryOps)
+ materializeMapInfoForEntryOp(entryOp, createEntryMapInfo);
+
+ // Privatized storage is sized on acc.map_info as well. Unlike a data entry
+ // op, the privatize op stays: it holds the storage handle and its dynamic
+ // sizes.
+ for (acc::PrivatizeOp op : privatizeOps) {
+ if (llvm::any_of(op.getResult().getUsers(), [](Operation *user) {
+ return isa<acc::MapInfoOp>(user);
+ }))
+ continue;
+ OpBuilder builder(op);
+ std::optional<acc::MapInfoOp> mapInfo = buildPrivatizeMapInfo(
+ support, module, op, mappingPolicy, symbolTable, builder);
+ if (!mapInfo)
+ continue;
+ op.getResult().replaceAllUsesExcept(mapInfo->getAccVar(),
+ mapInfo->getOperation());
+ }
+ }
+
+private:
+ /// Type-descriptor globals are looked up by name whenever a derived type has
+ /// no compile-time layout size. Built on the first such lookup and kept for
+ /// later ones, including across the functions this pass instance visits: the
+ /// pass creates no module-level symbols, so the table cannot go stale.
+ std::optional<SymbolTable> symbolTable;
+};
+
+} // namespace
diff --git a/flang/lib/Optimizer/OpenACC/Transforms/CMakeLists.txt b/flang/lib/Optimizer/OpenACC/Transforms/CMakeLists.txt
index 4ee040f6e1e44..e872a8200b0b6 100644
--- a/flang/lib/Optimizer/OpenACC/Transforms/CMakeLists.txt
+++ b/flang/lib/Optimizer/OpenACC/Transforms/CMakeLists.txt
@@ -3,8 +3,9 @@ add_flang_library(FIROpenACCTransforms
ACCDevicePtrToCUFKernel.cpp
ACCEmitNYIFlang.cpp
ACCInitializeFIRAnalyses.cpp
- ACCPipeline.cpp
+ ACCMapInfoPrep.cpp
ACCOptimizeFirstprivateMap.cpp
+ ACCPipeline.cpp
ACCRecipeBufferization.cpp
ACCUseDeviceCanonicalizer.cpp
@@ -22,6 +23,7 @@ add_flang_library(FIROpenACCTransforms
HLFIRDialect
MLIR_LIBS
+ MLIRArithDialect
MLIRIR
MLIRPass
MLIROpenACCDialect
diff --git a/flang/lib/Semantics/runtime-type-info.cpp b/flang/lib/Semantics/runtime-type-info.cpp
index 24f8f571439a3..dd6f9e43c5015 100644
--- a/flang/lib/Semantics/runtime-type-info.cpp
+++ b/flang/lib/Semantics/runtime-type-info.cpp
@@ -474,8 +474,8 @@ const Symbol *RuntimeTableBuilder::DescribeType(
sizeInBytes /= alignment;
sizeInBytes *= alignment;
}
- AddValue(
- dtValues, derivedTypeSchema_, "sizeinbytes"s, IntToExpr(sizeInBytes));
+ AddValue(dtValues, derivedTypeSchema_, sizeInBytesCompName,
+ IntToExpr(sizeInBytes));
}
if (const Symbol *
uninstDescObject{isPDTInstantiation
diff --git a/flang/test/Fir/OpenACC/acc-fir-map-info-prep-attach.mlir b/flang/test/Fir/OpenACC/acc-fir-map-info-prep-attach.mlir
new file mode 100644
index 0000000000000..147493316c787
--- /dev/null
+++ b/flang/test/Fir/OpenACC/acc-fir-map-info-prep-attach.mlir
@@ -0,0 +1,81 @@
+// RUN: fir-opt %s --pass-pipeline="builtin.module(func.func(acc-fir-map-info-prep))" | FileCheck %s
+// RUN: fir-opt %s --pass-pipeline="builtin.module(func.func(acc-fir-map-info-prep,acc-fir-map-info-prep))" | FileCheck %s --check-prefix=IDEMP
+
+// Attach metadata for descriptor-backed components. Distinguish the mapped
+// pointee, optional separate desc value, and optional varPtrPtr attach slot.
+
+// A component nested through more than one record still attaches through the
+// immediate descriptor slot. Its descriptor and mapped pointee are distinct.
+// CHECK-LABEL: func.func @nested_component
+// CHECK: %[[INNER:.*]] = fir.coordinate_of %{{.*}}, inner
+// CHECK: %[[SLOT:.*]] = fir.coordinate_of %[[INNER]], values
+// CHECK: %[[BOX:.*]] = fir.load %[[SLOT]]
+// CHECK: %[[DATA:.*]] = fir.box_addr %[[BOX]]
+// CHECK: acc.map_info varPtr(%[[DATA]] : !fir.heap<!fir.array<?xf64>>)
+// CHECK-SAME: varPtrPtr(%[[SLOT]] : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf64>>>>)
+// CHECK-SAME: desc(%[[BOX]] : !fir.box<!fir.heap<!fir.array<?xf64>>>)
+// CHECK-SAME: elementSize(8)
+// CHECK-SAME: descKind(cfi)
+// CHECK-SAME: mapFlags(to,ptr_and_obj)
+// IDEMP-LABEL: func.func @nested_component
+// IDEMP-COUNT-1: acc.map_info
+// IDEMP-NOT: acc.copyin
+func.func @nested_component() {
+ %outer = fir.undefined !fir.ref<!fir.type<_QFattachTouter{inner:!fir.type<_QFattachTinner{values:!fir.box<!fir.heap<!fir.array<?xf64>>>}>}>>
+ %inner = fir.coordinate_of %outer, inner : (!fir.ref<!fir.type<_QFattachTouter{inner:!fir.type<_QFattachTinner{values:!fir.box<!fir.heap<!fir.array<?xf64>>>}>}>>) -> !fir.ref<!fir.type<_QFattachTinner{values:!fir.box<!fir.heap<!fir.array<?xf64>>>}>>
+ %slot = fir.coordinate_of %inner, values : (!fir.ref<!fir.type<_QFattachTinner{values:!fir.box<!fir.heap<!fir.array<?xf64>>>}>>) -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xf64>>>>
+ %box = fir.load %slot : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf64>>>>
+ %data = fir.box_addr %box : (!fir.box<!fir.heap<!fir.array<?xf64>>>) -> !fir.heap<!fir.array<?xf64>>
+ %copy = acc.copyin varPtr(%data : !fir.heap<!fir.array<?xf64>>)
+ dataClause(acc_copyin) name("outer%inner%values")
+ -> !fir.heap<!fir.array<?xf64>>
+ acc.data dataOperands(%copy : !fir.heap<!fir.array<?xf64>>) {
+ acc.terminator
+ }
+ return
+}
+
+// Preserve an explicit varPtrPtr on an implicit entry: inference must not
+// replace metadata already carried on the data entry.
+// CHECK-LABEL: func.func @existing_attach_point
+// CHECK: %[[SLOT:.*]] = fir.undefined !fir.ref<!fir.box<!fir.ptr<f32>>>
+// CHECK: %[[BOX:.*]] = fir.load %[[SLOT]]
+// CHECK: %[[DATA:.*]] = fir.box_addr %[[BOX]]
+// CHECK: acc.map_info varPtr(%[[DATA]] : !fir.ptr<f32>)
+// CHECK-SAME: varPtrPtr(%[[SLOT]] : !fir.ref<!fir.box<!fir.ptr<f32>>>)
+// CHECK-SAME: descKind(none)
+// CHECK-SAME: mapFlags(to,ptr_and_obj,implicit)
+// IDEMP-LABEL: func.func @existing_attach_point
+func.func @existing_attach_point() {
+ %slot = fir.undefined !fir.ref<!fir.box<!fir.ptr<f32>>>
+ %box = fir.load %slot : !fir.ref<!fir.box<!fir.ptr<f32>>>
+ %data = fir.box_addr %box : (!fir.box<!fir.ptr<f32>>) -> !fir.ptr<f32>
+ %copy = acc.copyin varPtr(%data : !fir.ptr<f32>)
+ varPtrPtr(%slot : !fir.ref<!fir.box<!fir.ptr<f32>>>)
+ dataClause(acc_copyin) implicit(true) name("p")
+ -> !fir.ptr<f32>
+ acc.data dataOperands(%copy : !fir.ptr<f32>) {
+ acc.terminator
+ }
+ return
+}
+
+// Mapping descriptor storage itself has no second indirection operand. The
+// descriptor is recovered from var and supplies both CFI and ptr_and_obj facts.
+// CHECK-LABEL: func.func @descriptor_storage
+// CHECK: %[[SLOT:.*]] = fir.undefined !fir.ref<!fir.box<!fir.ptr<i32>>>
+// CHECK: acc.map_info varPtr(%[[SLOT]] : !fir.ref<!fir.box<!fir.ptr<i32>>>)
+// CHECK-NOT: varPtrPtr
+// CHECK-NOT: desc(
+// CHECK-SAME: elementSize(4)
+// CHECK-SAME: descKind(cfi)
+// CHECK-SAME: mapFlags(to,ptr_and_obj)
+func.func @descriptor_storage() {
+ %slot = fir.undefined !fir.ref<!fir.box<!fir.ptr<i32>>>
+ %copy = acc.copyin varPtr(%slot : !fir.ref<!fir.box<!fir.ptr<i32>>>)
+ dataClause(acc_copyin) name("p") -> !fir.ref<!fir.box<!fir.ptr<i32>>>
+ acc.data dataOperands(%copy : !fir.ref<!fir.box<!fir.ptr<i32>>>) {
+ acc.terminator
+ }
+ return
+}
\ No newline at end of file
diff --git a/flang/test/Fir/OpenACC/acc-fir-map-info-prep-clauses.mlir b/flang/test/Fir/OpenACC/acc-fir-map-info-prep-clauses.mlir
new file mode 100644
index 0000000000000..2aaf3bc71c48d
--- /dev/null
+++ b/flang/test/Fir/OpenACC/acc-fir-map-info-prep-clauses.mlir
@@ -0,0 +1,229 @@
+// RUN: fir-opt %s --pass-pipeline="builtin.module(func.func(acc-fir-map-info-prep))" | FileCheck %s
+
+// Exercise entry operations that are not ordinary structured copy clauses.
+
+// CHECK-LABEL: func.func @update_if_present
+// CHECK: %[[TO:.*]] = acc.map_info varPtr(%{{.*}} : !fir.ref<!fir.array<10xi32>>)
+// CHECK-SAME: bounds(
+// CHECK-SAME: elementSize(4)
+// CHECK-SAME: mapFlags(to,if_present)
+// CHECK: acc.update dataOperands(%[[TO]]
+// CHECK: %[[FROM:.*]] = acc.map_info varPtr(%{{.*}} : !fir.ref<!fir.array<10xi32>>)
+// CHECK-SAME: bounds(
+// CHECK-SAME: elementSize(4)
+// CHECK-SAME: mapFlags(from,if_present)
+// CHECK: acc.update dataOperands(%[[FROM]]
+// CHECK-NOT: acc.update_device
+// CHECK-NOT: acc.update_host
+func.func @update_if_present() {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %c9 = arith.constant 9 : index
+ %c10 = arith.constant 10 : index
+ %array = fir.undefined !fir.ref<!fir.array<10xi32>>
+ %bounds = acc.bounds lowerbound(%c0 : index) upperbound(%c9 : index)
+ extent(%c10 : index) stride(%c1 : index) startIdx(%c1 : index)
+ %to = acc.update_device varPtr(%array : !fir.ref<!fir.array<10xi32>>)
+ bounds(%bounds) structured(false) name("a")
+ -> !fir.ref<!fir.array<10xi32>>
+ acc.update dataOperands(%to : !fir.ref<!fir.array<10xi32>>) ifPresent
+ %from = acc.getdeviceptr varPtr(%array : !fir.ref<!fir.array<10xi32>>)
+ bounds(%bounds) dataClause(acc_update_host) structured(false) name("a")
+ -> !fir.ref<!fir.array<10xi32>>
+ acc.update dataOperands(%from : !fir.ref<!fir.array<10xi32>>) ifPresent
+ acc.update_host accPtr(%from : !fir.ref<!fir.array<10xi32>>)
+ bounds(%bounds) to varPtr(%array : !fir.ref<!fir.array<10xi32>>)
+ structured(false) name("a")
+ return
+}
+
+// CHECK-LABEL: func.func @nocreate
+// CHECK: %[[MAP:.*]] = acc.map_info var(%{{.*}} : !fir.box<!fir.array<?xf32>>)
+// CHECK-SAME: descKind(cfi)
+// CHECK-SAME: mapFlags(no_create)
+// CHECK: acc.data dataOperands(%[[MAP]]
+// CHECK-NOT: acc.nocreate
+// CHECK-NOT: acc.delete
+func.func @nocreate() {
+ %box = fir.undefined !fir.box<!fir.array<?xf32>>
+ %map = acc.nocreate var(%box : !fir.box<!fir.array<?xf32>>)
+ name("a") -> !fir.box<!fir.array<?xf32>>
+ acc.data dataOperands(%map : !fir.box<!fir.array<?xf32>>) {
+ acc.terminator
+ }
+ acc.delete accVar(%map : !fir.box<!fir.array<?xf32>>)
+ dataClause(acc_no_create) name("a")
+ return
+}
+
+// An unstructured declaration entry is rewritten like any other data operand,
+// while the declaration directive remains attached to the map result.
+// CHECK-LABEL: func.func @declare_create
+// CHECK: %[[SIZE:.*]] = arith.constant 28 : i64
+// CHECK: %[[MAP:.*]] = acc.map_info varPtr(%{{.*}} : !fir.ref<!fir.array<7xf32>>)
+// CHECK-SAME: size(%[[SIZE]] : i64)
+// CHECK-SAME: elementSize(4)
+// CHECK-SAME: mapFlags(none)
+// CHECK: acc.declare_enter dataOperands(%[[MAP]]
+// CHECK-NOT: acc.create
+func.func @declare_create() {
+ %array = fir.undefined !fir.ref<!fir.array<7xf32>>
+ %map = acc.create varPtr(%array : !fir.ref<!fir.array<7xf32>>)
+ structured(false) name("a") -> !fir.ref<!fir.array<7xf32>>
+ %token = acc.declare_enter dataOperands(%map : !fir.ref<!fir.array<7xf32>>)
+ acc.declare_exit token(%token) dataOperands(%map : !fir.ref<!fir.array<7xf32>>)
+ return
+}
+
+// host_data requires acc.use_device to survive this pass; replacing it with a
+// map_info would change the operation's device-address lookup semantics.
+// CHECK-LABEL: func.func @keep_use_device
+// CHECK: %[[USE:.*]] = acc.use_device
+// CHECK-NOT: acc.map_info
+// CHECK: acc.host_data dataOperands(%[[USE]]
+func.func @keep_use_device() {
+ %array = fir.undefined !fir.ref<!fir.array<4xf32>>
+ %use = acc.use_device varPtr(%array : !fir.ref<!fir.array<4xf32>>)
+ name("a") -> !fir.ref<!fir.array<4xf32>>
+ acc.host_data dataOperands(%use : !fir.ref<!fir.array<4xf32>>) {
+ acc.terminator
+ }
+ return
+}
+
+// An explicit map with no statically recoverable layout must remain unknown.
+// Only an implicit present lookup is allowed to turn an unknown size into zero.
+// CHECK-LABEL: func.func @explicit_unknown_size
+// CHECK: %[[UNKNOWN:.*]] = arith.constant -1 : i64
+// CHECK: acc.map_info
+// CHECK-SAME: size(%[[UNKNOWN]] : i64)
+// CHECK-SAME: mapFlags(to)
+func.func @explicit_unknown_size() {
+ %record = fir.undefined !fir.ref<!fir.type<_QFunknownTrecord{member:!fir.box<!fir.heap<!fir.array<?xf32>>>}>>
+ %copy = acc.copyin varPtr(%record : !fir.ref<!fir.type<_QFunknownTrecord{member:!fir.box<!fir.heap<!fir.array<?xf32>>>}>>)
+ dataClause(acc_copyin) name("record")
+ -> !fir.ref<!fir.type<_QFunknownTrecord{member:!fir.box<!fir.heap<!fir.array<?xf32>>>}>>
+ acc.data dataOperands(%copy : !fir.ref<!fir.type<_QFunknownTrecord{member:!fir.box<!fir.heap<!fir.array<?xf32>>>}>>) {
+ acc.terminator
+ }
+ return
+}
+
+// A device address supplied by the user is mapped as such: the runtime must not
+// look it up or transfer it.
+// CHECK-LABEL: func.func @deviceptr
+// CHECK: acc.map_info varPtr(%{{.*}} : !fir.ref<!fir.array<4xf32>>)
+// CHECK-SAME: mapFlags(devptr)
+// CHECK-NOT: acc.deviceptr
+func.func @deviceptr() {
+ %array = fir.undefined !fir.ref<!fir.array<4xf32>>
+ %map = acc.deviceptr varPtr(%array : !fir.ref<!fir.array<4xf32>>)
+ name("a") -> !fir.ref<!fir.array<4xf32>>
+ acc.data dataOperands(%map : !fir.ref<!fir.array<4xf32>>) {
+ acc.terminator
+ }
+ return
+}
+
+// Detaching a pointer component acts on the descriptor and the address it
+// holds, so the map keeps ptr_and_obj without requesting any transfer.
+// CHECK-LABEL: func.func @detach
+// CHECK: %[[MAP:.*]] = acc.map_info varPtr(%{{.*}} : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>)
+// CHECK-SAME: descKind(cfi)
+// CHECK-SAME: mapFlags(ptr_and_obj)
+// CHECK: acc.exit_data dataOperands(%[[MAP]]
+// CHECK-NOT: acc.detach
+func.func @detach() {
+ %box = fir.alloca !fir.box<!fir.ptr<!fir.array<?xf32>>>
+ %ptr = acc.getdeviceptr varPtr(%box : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>)
+ dataClause(acc_detach) structured(false) name("p")
+ -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>
+ acc.exit_data dataOperands(%ptr : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>)
+ acc.detach accPtr(%ptr : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>)
+ structured(false) name("p")
+ return
+}
+
+// -----
+
+// Storage that a recipe creates on the device is never a host mapping: the
+// clause operations that carry those recipes stay as they are.
+// CHECK-LABEL: func.func @recipe_clauses_stay
+// CHECK: %[[PRIV:.*]] = acc.private
+// CHECK: %[[RED:.*]] = acc.reduction
+// CHECK: acc.parallel private(%[[PRIV]]
+// CHECK-SAME: reduction(%[[RED]]
+// CHECK-NOT: acc.map_info
+acc.private.recipe @privatization_ref_i32 : !fir.ref<i32> init {
+^bb0(%arg0: !fir.ref<i32>):
+ %0 = fir.alloca i32
+ acc.yield %0 : !fir.ref<i32>
+}
+acc.reduction.recipe @reduction_add_ref_i32 : !fir.ref<i32>
+ reduction_operator <add> init {
+^bb0(%arg0: !fir.ref<i32>):
+ %0 = fir.alloca i32
+ acc.yield %0 : !fir.ref<i32>
+} combiner {
+^bb0(%arg0: !fir.ref<i32>, %arg1: !fir.ref<i32>):
+ acc.yield %arg0 : !fir.ref<i32>
+}
+func.func @recipe_clauses_stay(%a: !fir.ref<i32>, %b: !fir.ref<i32>) {
+ %priv = acc.private varPtr(%a : !fir.ref<i32>)
+ recipe(@privatization_ref_i32) -> !fir.ref<i32>
+ %red = acc.reduction varPtr(%b : !fir.ref<i32>)
+ recipe(@reduction_add_ref_i32) -> !fir.ref<i32>
+ acc.parallel private(%priv : !fir.ref<i32>)
+ reduction(%red : !fir.ref<i32>) {
+ acc.yield
+ }
+ return
+}
+
+// -----
+
+// A cache hint describes storage the compiler may promote inside the loop, not
+// a mapping the runtime performs.
+// CHECK-LABEL: func.func @cache_stays
+// CHECK: %[[CACHE:.*]] = acc.cache
+// CHECK-NOT: acc.map_info
+// CHECK: acc.loop {{.*}}cache(%[[CACHE]]
+func.func @cache_stays(%a: !fir.ref<!fir.array<10xf32>>) {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %c10 = arith.constant 10 : index
+ %cache = acc.cache varPtr(%a : !fir.ref<!fir.array<10xf32>>)
+ name("a") -> !fir.ref<!fir.array<10xf32>>
+ acc.loop cache(%cache : !fir.ref<!fir.array<10xf32>>)
+ control(%iv : index) = (%c0 : index) to (%c10 : index)
+ step (%c1 : index) {
+ acc.yield
+ } inclusiveUpperbound(array<i1: true>) independent
+ return
+}
+
+// -----
+
+// CUDA Fortran managed allocatables set managed_devptr from cuf.data_attr.
+// CHECK-LABEL: func.func @managed_copy
+// CHECK: acc.map_info varPtr(%{{.*}} : !fir.ref<!fir.array<10xf32>>)
+// CHECK-SAME: mapFlags(to,from,managed_devptr)
+func.func @managed_copy() {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %c9 = arith.constant 9 : index
+ %c10 = arith.constant 10 : index
+ %0 = fir.alloca !fir.array<10xf32> {cuf.data_attr = #cuf.cuda<managed>}
+ %shape = fir.shape %c10 : (index) -> !fir.shape<1>
+ %1 = fir.declare %0(%shape) {cuf.data_attr = #cuf.cuda<managed>, uniq_name = "_QFEa"} : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>) -> !fir.ref<!fir.array<10xf32>>
+ %bounds = acc.bounds lowerbound(%c0 : index) upperbound(%c9 : index)
+ extent(%c10 : index) stride(%c1 : index) startIdx(%c1 : index)
+ %copy = acc.copyin varPtr(%1 : !fir.ref<!fir.array<10xf32>>) bounds(%bounds)
+ dataClause(acc_copy) name("a") -> !fir.ref<!fir.array<10xf32>>
+ acc.data dataOperands(%copy : !fir.ref<!fir.array<10xf32>>) {
+ acc.terminator
+ }
+ acc.copyout accPtr(%copy : !fir.ref<!fir.array<10xf32>>) bounds(%bounds)
+ to varPtr(%1 : !fir.ref<!fir.array<10xf32>>) dataClause(acc_copy) name("a")
+ return
+}
diff --git a/flang/test/Fir/OpenACC/acc-fir-map-info-prep-declare-global.mlir b/flang/test/Fir/OpenACC/acc-fir-map-info-prep-declare-global.mlir
new file mode 100644
index 0000000000000..7b3df274454ab
--- /dev/null
+++ b/flang/test/Fir/OpenACC/acc-fir-map-info-prep-declare-global.mlir
@@ -0,0 +1,40 @@
+// RUN: fir-opt %s --pass-pipeline="builtin.module(any(acc-fir-map-info-prep))" | FileCheck %s
+
+// The data clauses of a declare directive on a module variable are mapped from
+// the constructor and destructor of that variable. Those run outside of any
+// Fortran function, so the map entries have to be materialized there as well:
+// an allocatable maps as an attach of a CFI-described object, which the
+// address of the descriptor alone does not state.
+
+fir.global @_QMmEdata : !fir.box<!fir.heap<!fir.array<?xi32>>>
+
+// CHECK-LABEL: llvm.func @_QMmEdata_acc_ctor
+// CHECK: %[[SIZE:.*]] = arith.constant 0 : i64
+// CHECK: acc.map_info varPtr(%{{.*}} : !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>)
+// CHECK-SAME: size(%[[SIZE]] : i64) elementSize(4)
+// CHECK-SAME: descKind(cfi) mapFlags(ptr_and_obj)
+// CHECK-NOT: acc.create
+llvm.func @_QMmEdata_acc_ctor() {
+ %addr = fir.address_of(@_QMmEdata) : !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>
+ %create = acc.create varPtr(%addr : !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>)
+ structured(false) name("data") -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>
+ acc.declare_enter dataOperands(%create : !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>)
+ llvm.return
+}
+
+// CHECK-LABEL: llvm.func @_QMmEdata_acc_dtor
+// CHECK: %[[SIZE:.*]] = arith.constant 0 : i64
+// CHECK: acc.map_info varPtr(%{{.*}} : !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>)
+// CHECK-SAME: size(%[[SIZE]] : i64) elementSize(4)
+// CHECK-SAME: exitLoc({{.*}}) descKind(cfi) mapFlags(ptr_and_obj)
+// CHECK-NOT: acc.getdeviceptr
+// CHECK-NOT: acc.delete
+llvm.func @_QMmEdata_acc_dtor() {
+ %addr = fir.address_of(@_QMmEdata) : !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>
+ %devptr = acc.getdeviceptr varPtr(%addr : !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>)
+ dataClause(acc_create) structured(false) name("data") -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>
+ acc.declare_exit dataOperands(%devptr : !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>)
+ acc.delete accPtr(%devptr : !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>)
+ dataClause(acc_create) structured(false) name("data")
+ llvm.return
+}
diff --git a/flang/test/Fir/OpenACC/acc-fir-map-info-prep-exit.mlir b/flang/test/Fir/OpenACC/acc-fir-map-info-prep-exit.mlir
new file mode 100644
index 0000000000000..5ffdb162f3f9b
--- /dev/null
+++ b/flang/test/Fir/OpenACC/acc-fir-map-info-prep-exit.mlir
@@ -0,0 +1,222 @@
+// RUN: fir-opt %s --pass-pipeline="builtin.module(func.func(acc-fir-map-info-prep))" | FileCheck %s
+
+// The map entry replaces both the data entry operation and the data exit
+// operations paired with it: its flags hold the effects of both, and it keeps
+// the location where the exit effects happen - the end directive of a
+// structured construct.
+
+// CHECK: #[[END_DATA:.*]] = loc("end-data":8:4)
+
+// CHECK-LABEL: func.func @structured_copy
+// CHECK: acc.map_info varPtr(%{{.*}} : !fir.ref<f32>)
+// CHECK-SAME: exitLoc(#[[END_DATA]])
+// CHECK-SAME: mapFlags(to,from)
+// CHECK-NOT: acc.copyin
+// CHECK-NOT: acc.copyout
+func.func @structured_copy() {
+ %ref = fir.undefined !fir.ref<f32>
+ %copy = acc.copyin varPtr(%ref : !fir.ref<f32>) dataClause(acc_copy)
+ name("x") -> !fir.ref<f32> loc("data":4:2)
+ acc.data dataOperands(%copy : !fir.ref<f32>) {
+ acc.terminator
+ }
+ acc.copyout accPtr(%copy : !fir.ref<f32>) to varPtr(%ref : !fir.ref<f32>)
+ dataClause(acc_copy) name("x") loc("end-data":8:4)
+ return
+}
+
+// A construct that maps one variable through two clauses, as `!$acc data
+// copyin(x) copyout(x)` does. The runtime keeps a single mapping, so the
+// copy-in entry - whose own exit only releases the device copy - has to carry
+// the copy-back of its sibling.
+
+// CHECK-LABEL: func.func @aliased_clauses
+// CHECK: acc.map_info var(%[[BOX:.*]] : !fir.box<!fir.array<?xf32>>)
+// CHECK-SAME: mapFlags(to,from)
+// CHECK: acc.map_info var(%[[BOX]] : !fir.box<!fir.array<?xf32>>)
+// CHECK-SAME: mapFlags(from)
+// CHECK-NOT: acc.delete
+// CHECK-NOT: acc.copyout
+func.func @aliased_clauses(%box: !fir.box<!fir.array<?xf32>>) {
+ %copyin = acc.copyin var(%box : !fir.box<!fir.array<?xf32>>)
+ dataClause(acc_copyin) name("x") -> !fir.box<!fir.array<?xf32>>
+ %create = acc.create var(%box : !fir.box<!fir.array<?xf32>>)
+ dataClause(acc_copyout) name("x") -> !fir.box<!fir.array<?xf32>>
+ acc.data dataOperands(
+ %copyin, %create : !fir.box<!fir.array<?xf32>>,
+ !fir.box<!fir.array<?xf32>>) {
+ acc.terminator
+ }
+ acc.delete accVar(%copyin : !fir.box<!fir.array<?xf32>>)
+ dataClause(acc_copyin) name("x")
+ acc.copyout accVar(%create : !fir.box<!fir.array<?xf32>>)
+ to var(%box : !fir.box<!fir.array<?xf32>>)
+ dataClause(acc_copyout) name("x")
+ return
+}
+
+// A readonly copy-in is released, not copied back: its exit repeats the entry
+// clause and no sibling clause on the construct copies the variable out, so the
+// mapping stays copy-to-device only.
+
+// CHECK-LABEL: func.func @copyin_readonly_release
+// CHECK: acc.map_info varPtr(%{{.*}} : !fir.ref<f32>)
+// CHECK-SAME: mapFlags(to)
+// CHECK-NOT: acc.delete
+func.func @copyin_readonly_release() {
+ %ref = fir.undefined !fir.ref<f32>
+ %copyin = acc.copyin varPtr(%ref : !fir.ref<f32>)
+ dataClause(acc_copyin_readonly) name("x") -> !fir.ref<f32>
+ acc.data dataOperands(%copyin : !fir.ref<f32>) {
+ acc.terminator
+ }
+ acc.delete accPtr(%copyin : !fir.ref<f32>) dataClause(acc_copyin_readonly)
+ name("x")
+ return
+}
+
+// -----
+
+// `!$acc declare copy(a)` uses one data entry operation at two program points:
+// acc.declare_enter and acc.declare_exit. Both keep referring to the single map
+// entry, which holds the union of the entry and exit effects - the same shape a
+// structured acc.data region has. Each declare call site uses the same map_info
+// token with the flags that apply there, rather than splitting the entry.
+
+// CHECK-LABEL: func.func @declare_copy
+// CHECK: %[[MAP:.*]] = acc.map_info varPtr(%{{.*}} : !fir.ref<!fir.array<100xi32>>)
+// CHECK-SAME: mapFlags(to,from)
+// CHECK: %[[TOKEN:.*]] = acc.declare_enter dataOperands(%[[MAP]] : !fir.ref<!fir.array<100xi32>>)
+// CHECK: acc.declare_exit token(%[[TOKEN]]) dataOperands(%[[MAP]] : !fir.ref<!fir.array<100xi32>>)
+// CHECK-NOT: acc.copyout
+func.func @declare_copy(%decl: !fir.ref<!fir.array<100xi32>>) {
+ %copyin = acc.copyin varPtr(%decl : !fir.ref<!fir.array<100xi32>>)
+ dataClause(acc_copy) name("a") -> !fir.ref<!fir.array<100xi32>>
+ %token = acc.declare_enter dataOperands(%copyin : !fir.ref<!fir.array<100xi32>>)
+ acc.declare_exit token(%token) dataOperands(%copyin : !fir.ref<!fir.array<100xi32>>)
+ acc.copyout accPtr(%copyin : !fir.ref<!fir.array<100xi32>>)
+ to varPtr(%decl : !fir.ref<!fir.array<100xi32>>) dataClause(acc_copy)
+ name("a")
+ return
+}
+
+// -----
+
+// Only device_resident needs the entry and the exit to differ: the exit adds
+// the teardown of the mapping. The map entry records device_resident once and
+// the exit call site adds that teardown, so the entry stays unsplit here too.
+
+// CHECK-LABEL: func.func @declare_device_resident
+// CHECK: %[[MAP:.*]] = acc.map_info varPtr(%{{.*}} : !fir.ref<!fir.array<100xi32>>)
+// CHECK-SAME: mapFlags(device_resident)
+// CHECK: %[[TOKEN:.*]] = acc.declare_enter dataOperands(%[[MAP]] : !fir.ref<!fir.array<100xi32>>)
+// CHECK: acc.declare_exit token(%[[TOKEN]]) dataOperands(%[[MAP]] : !fir.ref<!fir.array<100xi32>>)
+// CHECK-NOT: acc.delete
+func.func @declare_device_resident(%decl: !fir.ref<!fir.array<100xi32>>) {
+ %dr = acc.declare_device_resident varPtr(%decl : !fir.ref<!fir.array<100xi32>>)
+ name("a") -> !fir.ref<!fir.array<100xi32>>
+ %token = acc.declare_enter dataOperands(%dr : !fir.ref<!fir.array<100xi32>>)
+ acc.declare_exit token(%token) dataOperands(%dr : !fir.ref<!fir.array<100xi32>>)
+ acc.delete accPtr(%dr : !fir.ref<!fir.array<100xi32>>)
+ dataClause(acc_declare_device_resident) name("a")
+ return
+}
+
+// A declare map and a kernel use of the same variable need different map flags:
+// declare_enter keeps device_resident; the kernel must use present instead.
+// CHECK-LABEL: func.func @declare_device_resident_kernel_present
+// CHECK: %[[DECLARE_MAP:.*]] = acc.map_info varPtr(%{{.*}} : !fir.ref<!fir.array<100xi32>>)
+// CHECK-SAME: mapFlags(device_resident)
+// CHECK: %[[KERNEL_MAP:.*]] = acc.map_info varPtr(%{{.*}} : !fir.ref<!fir.array<100xi32>>)
+// CHECK-SAME: mapFlags(present)
+// CHECK: %[[TOKEN:.*]] = acc.declare_enter dataOperands(%[[DECLARE_MAP]] : !fir.ref<!fir.array<100xi32>>)
+// CHECK: acc.kernel_environment dataOperands(%[[KERNEL_MAP]] : !fir.ref<!fir.array<100xi32>>)
+// CHECK-NOT: acc.declare_device_resident
+func.func @declare_device_resident_kernel_present(%decl: !fir.ref<!fir.array<100xi32>>) {
+ %dr = acc.declare_device_resident varPtr(%decl : !fir.ref<!fir.array<100xi32>>)
+ name("a") -> !fir.ref<!fir.array<100xi32>>
+ %token = acc.declare_enter dataOperands(%dr : !fir.ref<!fir.array<100xi32>>)
+ acc.kernel_environment dataOperands(%dr : !fir.ref<!fir.array<100xi32>>) {
+ }
+ return
+}
+
+// -----
+
+// An unstructured exit_data copy-out with finalize becomes one map entry whose
+// flags hold both the copy-back and the delete, and the exit_data construct
+// keeps that token as its operand.
+
+// CHECK-LABEL: func.func @unstructured_exit_data
+// CHECK: %[[MAP:.*]] = acc.map_info varPtr(%{{.*}} : !fir.ref<f32>)
+// CHECK-SAME: mapFlags(from,delete)
+// CHECK: acc.exit_data dataOperands(%[[MAP]] : !fir.ref<f32>)
+// CHECK-NOT: acc.copyout
+func.func @unstructured_exit_data() {
+ %ref = fir.undefined !fir.ref<f32>
+ %devptr = acc.getdeviceptr varPtr(%ref : !fir.ref<f32>)
+ dataClause(acc_copyout) structured(false) name("x") -> !fir.ref<f32>
+ acc.exit_data dataOperands(%devptr : !fir.ref<f32>) finalize
+ acc.copyout accPtr(%devptr : !fir.ref<f32>) to varPtr(%ref : !fir.ref<f32>)
+ dataClause(acc_copyout) structured(false) name("x")
+ return
+}
+
+// -----
+
+// `delete` decrements the dynamic reference counter of the mapping, so it must
+// not ask the runtime to force an unmap: an enclosing data region may still
+// hold a reference to the same memory, and tearing the mapping down here makes
+// a later `present` on that memory fail. Only `finalize` zeroes the counter.
+
+// CHECK-LABEL: func.func @unstructured_delete
+// CHECK: acc.map_info varPtr(%{{.*}} : !fir.ref<!fir.box<!fir.ptr<!fir.array<?x?xf64>>>>)
+// CHECK-SAME: mapFlags(ptr_and_obj)
+// CHECK: acc.exit_data dataOperands
+// CHECK-NOT: acc.delete
+func.func @unstructured_delete(%box: !fir.ref<!fir.box<!fir.ptr<!fir.array<?x?xf64>>>>) {
+ %devptr = acc.getdeviceptr varPtr(%box : !fir.ref<!fir.box<!fir.ptr<!fir.array<?x?xf64>>>>)
+ dataClause(acc_delete) structured(false) name("dwork")
+ -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?x?xf64>>>>
+ acc.exit_data dataOperands(%devptr : !fir.ref<!fir.box<!fir.ptr<!fir.array<?x?xf64>>>>)
+ acc.delete accPtr(%devptr : !fir.ref<!fir.box<!fir.ptr<!fir.array<?x?xf64>>>>)
+ structured(false) name("dwork")
+ return
+}
+
+// -----
+
+// The same `delete` clause under `finalize` does force the unmap.
+
+// CHECK-LABEL: func.func @unstructured_delete_finalize
+// CHECK: acc.map_info varPtr(%{{.*}} : !fir.ref<!fir.box<!fir.ptr<!fir.array<?x?xf64>>>>)
+// CHECK-SAME: mapFlags(delete,ptr_and_obj)
+// CHECK: acc.exit_data dataOperands(%{{.*}} : !fir.ref<!fir.box<!fir.ptr<!fir.array<?x?xf64>>>>) finalize
+// CHECK-NOT: acc.delete
+func.func @unstructured_delete_finalize(%box: !fir.ref<!fir.box<!fir.ptr<!fir.array<?x?xf64>>>>) {
+ %devptr = acc.getdeviceptr varPtr(%box : !fir.ref<!fir.box<!fir.ptr<!fir.array<?x?xf64>>>>)
+ dataClause(acc_delete) structured(false) name("dwork")
+ -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?x?xf64>>>>
+ acc.exit_data dataOperands(%devptr : !fir.ref<!fir.box<!fir.ptr<!fir.array<?x?xf64>>>>) finalize
+ acc.delete accPtr(%devptr : !fir.ref<!fir.box<!fir.ptr<!fir.array<?x?xf64>>>>)
+ structured(false) name("dwork")
+ return
+}
+
+// -----
+
+// Reduction mapping records both transfer directions and the reduction
+// behavior on the map entry.
+// CHECK-LABEL: func.func @reduction
+// CHECK: acc.map_info varPtr(%{{.*}} : !fir.ref<i32>)
+// CHECK-SAME: mapFlags(to,from,reduction)
+// CHECK-NOT: acc.copyout
+func.func @reduction(%ref: !fir.ref<i32>) {
+ %map = acc.copyin varPtr(%ref : !fir.ref<i32>)
+ dataClause(acc_reduction) name("sum") -> !fir.ref<i32>
+ acc.kernel_environment dataOperands(%map : !fir.ref<i32>) {
+ }
+ acc.copyout accPtr(%map : !fir.ref<i32>) to varPtr(%ref : !fir.ref<i32>)
+ dataClause(acc_reduction) name("sum")
+ return
+}
diff --git a/flang/test/Fir/OpenACC/acc-fir-map-info-prep-implicit-present.mlir b/flang/test/Fir/OpenACC/acc-fir-map-info-prep-implicit-present.mlir
new file mode 100644
index 0000000000000..db0f6d691c201
--- /dev/null
+++ b/flang/test/Fir/OpenACC/acc-fir-map-info-prep-implicit-present.mlir
@@ -0,0 +1,135 @@
+// RUN: fir-opt %s --pass-pipeline="builtin.module(func.func(acc-fir-map-info-prep))" | FileCheck %s
+
+// When a data region maps both a descriptor entry and an implicit present of the
+// pointee address, the implicit sibling must not inherit CFI or attach facts
+// from the descriptor map. Only the explicit descriptor entry keeps
+// descKind(cfi) and ptr_and_obj.
+
+// CHECK-LABEL: func.func @assumed_shape_with_implicit_present
+// CHECK: %[[BOX:.*]] = fir.undefined !fir.box<!fir.array<?xf32>>
+// CHECK: %[[DESC:.*]] = acc.map_info var(%[[BOX]] : !fir.box<!fir.array<?xf32>>)
+// CHECK-SAME: elementSize(4)
+// CHECK-SAME: descKind(cfi)
+// CHECK-SAME: mapFlags(to)
+// CHECK: %[[ADDR:.*]] = fir.box_addr %[[BOX]]
+// CHECK: %[[ZERO:.*]] = arith.constant 0 : i64
+// CHECK: %[[BASE:.*]] = acc.map_info varPtr(%[[ADDR]] : !fir.ref<!fir.array<?xf32>>)
+// CHECK-SAME: size(%[[ZERO]] : i64)
+// CHECK-SAME: descKind(none)
+// CHECK-SAME: mapFlags(implicit,present)
+// CHECK-NOT: ptr_and_obj
+// CHECK: acc.data dataOperands(%[[DESC]], %[[BASE]]
+func.func @assumed_shape_with_implicit_present() {
+ %box = fir.undefined !fir.box<!fir.array<?xf32>>
+ %desc = acc.copyin var(%box : !fir.box<!fir.array<?xf32>>)
+ name("a") -> !fir.box<!fir.array<?xf32>>
+ %addr = fir.box_addr %box : (!fir.box<!fir.array<?xf32>>) -> !fir.ref<!fir.array<?xf32>>
+ %base = acc.present varPtr(%addr : !fir.ref<!fir.array<?xf32>>)
+ implicit(true) name("a") -> !fir.ref<!fir.array<?xf32>>
+ acc.data dataOperands(%desc, %base : !fir.box<!fir.array<?xf32>>, !fir.ref<!fir.array<?xf32>>) {
+ acc.terminator
+ }
+ return
+}
+
+// CHECK-LABEL: func.func @allocatable_array_with_implicit_present
+// CHECK: %[[SLOT:.*]] = fir.undefined !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
+// CHECK: %[[DESC:.*]] = acc.map_info varPtr(%[[SLOT]] : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>)
+// CHECK-SAME: elementSize(4)
+// CHECK-SAME: descKind(cfi)
+// CHECK-SAME: mapFlags(to,ptr_and_obj)
+// CHECK: %[[LOAD:.*]] = fir.load %[[SLOT]]
+// CHECK: %[[BADDR:.*]] = fir.box_addr %[[LOAD]]
+// CHECK: %[[CONV:.*]] = fir.convert %[[BADDR]]
+// CHECK: %[[ZERO:.*]] = arith.constant 0 : i64
+// CHECK: %[[BASE:.*]] = acc.map_info varPtr(%[[CONV]] : !fir.ref<!fir.array<?xf32>>)
+// CHECK-SAME: size(%[[ZERO]] : i64)
+// CHECK-SAME: descKind(none)
+// CHECK-SAME: mapFlags(implicit,present)
+// CHECK-NOT: varPtrPtr
+// CHECK: acc.data dataOperands(%[[DESC]], %[[BASE]]
+func.func @allocatable_array_with_implicit_present() {
+ %slot = fir.undefined !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
+ %desc = acc.copyin varPtr(%slot : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>)
+ name("a") -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
+ %load = fir.load %slot : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
+ %baddr = fir.box_addr %load : (!fir.box<!fir.heap<!fir.array<?xf32>>>) -> !fir.heap<!fir.array<?xf32>>
+ %conv = fir.convert %baddr : (!fir.heap<!fir.array<?xf32>>) -> !fir.ref<!fir.array<?xf32>>
+ %base = acc.present varPtr(%conv : !fir.ref<!fir.array<?xf32>>)
+ implicit(true) name("a") -> !fir.ref<!fir.array<?xf32>>
+ acc.data dataOperands(%desc, %base : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>, !fir.ref<!fir.array<?xf32>>) {
+ acc.terminator
+ }
+ return
+}
+
+// CHECK-LABEL: func.func @pointer_array_with_implicit_present
+// CHECK: %[[SLOT:.*]] = fir.undefined !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>
+// CHECK: %[[DESC:.*]] = acc.map_info varPtr(%[[SLOT]] : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>)
+// CHECK-SAME: elementSize(4)
+// CHECK-SAME: descKind(cfi)
+// CHECK-SAME: mapFlags(to,ptr_and_obj)
+// CHECK: %[[LOAD:.*]] = fir.load %[[SLOT]]
+// CHECK: %[[BADDR:.*]] = fir.box_addr %[[LOAD]]
+// CHECK: %[[CONV:.*]] = fir.convert %[[BADDR]]
+// CHECK: %[[BASE:.*]] = acc.map_info varPtr(%[[CONV]] : !fir.ref<!fir.array<?xf32>>)
+// CHECK-SAME: descKind(none)
+// CHECK-SAME: mapFlags(implicit,present)
+// CHECK: acc.data dataOperands(%[[DESC]], %[[BASE]]
+func.func @pointer_array_with_implicit_present() {
+ %slot = fir.undefined !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>
+ %desc = acc.copyin varPtr(%slot : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>)
+ name("a") -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>
+ %load = fir.load %slot : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>
+ %baddr = fir.box_addr %load : (!fir.box<!fir.ptr<!fir.array<?xf32>>>) -> !fir.ptr<!fir.array<?xf32>>
+ %conv = fir.convert %baddr : (!fir.ptr<!fir.array<?xf32>>) -> !fir.ref<!fir.array<?xf32>>
+ %base = acc.present varPtr(%conv : !fir.ref<!fir.array<?xf32>>)
+ implicit(true) name("a") -> !fir.ref<!fir.array<?xf32>>
+ acc.data dataOperands(%desc, %base : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>, !fir.ref<!fir.array<?xf32>>) {
+ acc.terminator
+ }
+ return
+}
+
+// Struct members with both descriptor and implicit pointee present entries.
+// Scalar c_ptr, allocatable scalar, and polymorphic cases are covered in
+// acc-fir-map-info-prep-types.mlir.
+// CHECK-LABEL: func.func @struct_array_members_with_implicit_present
+// CHECK: %[[ALLOC_ARR:.*]] = fir.coordinate_of %{{.*}}, alloc_arr
+// CHECK: %[[PTR_ARR:.*]] = fir.coordinate_of %{{.*}}, ptr_arr
+// CHECK: %[[DESC0:.*]] = acc.map_info varPtr(%[[ALLOC_ARR]] : {{[^)]*}})
+// CHECK-SAME: elementSize(4)
+// CHECK-SAME: mapFlags(to,ptr_and_obj)
+// CHECK: %[[DESC1:.*]] = acc.map_info varPtr(%[[PTR_ARR]] : {{[^)]*}})
+// CHECK-SAME: elementSize(4)
+// CHECK-SAME: mapFlags(to,ptr_and_obj)
+// CHECK: %[[BASE0:.*]] = acc.map_info varPtr(%{{.*}} : !fir.ref<!fir.array<?xf32>>)
+// CHECK-SAME: descKind(none)
+// CHECK-SAME: mapFlags(implicit,present)
+// CHECK: %[[BASE1:.*]] = acc.map_info varPtr(%{{.*}} : !fir.ref<!fir.array<?xf32>>)
+// CHECK-SAME: descKind(none)
+// CHECK-SAME: mapFlags(implicit,present)
+// CHECK: acc.data dataOperands(%[[DESC0]], %[[DESC1]], %[[BASE0]], %[[BASE1]]
+func.func @struct_array_members_with_implicit_present() {
+ %h = fir.undefined !fir.ref<!fir.type<_QFstruct_membersTholder{alloc_arr:!fir.box<!fir.heap<!fir.array<?xf32>>>,ptr_arr:!fir.box<!fir.ptr<!fir.array<?xf32>>>}>>
+ %alloc_arr = fir.coordinate_of %h, alloc_arr : (!fir.ref<!fir.type<_QFstruct_membersTholder{alloc_arr:!fir.box<!fir.heap<!fir.array<?xf32>>>,ptr_arr:!fir.box<!fir.ptr<!fir.array<?xf32>>>}>>) -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
+ %ptr_arr = fir.coordinate_of %h, ptr_arr : (!fir.ref<!fir.type<_QFstruct_membersTholder{alloc_arr:!fir.box<!fir.heap<!fir.array<?xf32>>>,ptr_arr:!fir.box<!fir.ptr<!fir.array<?xf32>>>}>>) -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>
+ %d0 = acc.copyin varPtr(%alloc_arr : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>)
+ name("h%alloc_arr") -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
+ %d1 = acc.copyin varPtr(%ptr_arr : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>)
+ name("h%ptr_arr") -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>
+ %l0 = fir.load %alloc_arr : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
+ %a0 = fir.box_addr %l0 : (!fir.box<!fir.heap<!fir.array<?xf32>>>) -> !fir.heap<!fir.array<?xf32>>
+ %c0 = fir.convert %a0 : (!fir.heap<!fir.array<?xf32>>) -> !fir.ref<!fir.array<?xf32>>
+ %b0 = acc.present varPtr(%c0 : !fir.ref<!fir.array<?xf32>>)
+ implicit(true) name("h%alloc_arr") -> !fir.ref<!fir.array<?xf32>>
+ %l1 = fir.load %ptr_arr : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>
+ %a1 = fir.box_addr %l1 : (!fir.box<!fir.ptr<!fir.array<?xf32>>>) -> !fir.ptr<!fir.array<?xf32>>
+ %c1 = fir.convert %a1 : (!fir.ptr<!fir.array<?xf32>>) -> !fir.ref<!fir.array<?xf32>>
+ %b1 = acc.present varPtr(%c1 : !fir.ref<!fir.array<?xf32>>)
+ implicit(true) name("h%ptr_arr") -> !fir.ref<!fir.array<?xf32>>
+ acc.data dataOperands(%d0, %d1, %b0, %b1 : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>, !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>, !fir.ref<!fir.array<?xf32>>, !fir.ref<!fir.array<?xf32>>) {
+ acc.terminator
+ }
+ return
+}
diff --git a/flang/test/Fir/OpenACC/acc-fir-map-info-prep-privatize.mlir b/flang/test/Fir/OpenACC/acc-fir-map-info-prep-privatize.mlir
new file mode 100644
index 0000000000000..54953870331f0
--- /dev/null
+++ b/flang/test/Fir/OpenACC/acc-fir-map-info-prep-privatize.mlir
@@ -0,0 +1,122 @@
+// RUN: fir-opt %s --pass-pipeline="builtin.module(func.func(acc-fir-map-info-prep))" | FileCheck %s
+
+// Privatized storage is wrapped by acc.map_info with byte size and map flags.
+// The acc.privatize op remains for the storage handle, dynamic sizes, and
+// parallelism levels.
+
+// CHECK-LABEL: func.func @private_static_memref
+// CHECK: %[[PRIV:.*]] = acc.privatize
+// CHECK: %[[SIZE:.*]] = arith.constant 32 : i64
+// CHECK: acc.map_info varPtr(%[[PRIV]] : !acc.private_type<memref<8xi32>>)
+// CHECK-SAME: size(%[[SIZE]] : i64)
+// CHECK-SAME: elementSize(4)
+// CHECK-SAME: descKind(none)
+// CHECK-SAME: mapFlags(private)
+func.func @private_static_memref() {
+ %priv = acc.privatize par_dims(#acc<par_dims[]>)
+ : () -> !acc.private_type<memref<8xi32>>
+ return
+}
+
+// -----
+
+// Parallel-level private flags are recorded on map_info alongside private.
+
+// CHECK-LABEL: func.func @private_parallel_levels
+// CHECK: %[[PRIV:.*]] = acc.privatize
+// CHECK: acc.map_info varPtr(%[[PRIV]] : !acc.private_type<memref<8xi32>>)
+// CHECK-SAME: mapFlags(private,gang_private,worker_private,vector_private)
+func.func @private_parallel_levels() {
+ %priv = acc.privatize
+ par_dims(#acc<par_dims[block_x, thread_y, thread_x]>)
+ : () -> !acc.private_type<memref<8xi32>>
+ return
+}
+
+// -----
+
+// Privatized storage need not name any parallel dimension - storage promoted to
+// shared memory is private without being replicated per level. Such a
+// privatization carries no par_dims at all, not an empty one.
+
+// CHECK-LABEL: func.func @private_without_par_dims
+// CHECK: %[[PRIV:.*]] = acc.privatize
+// CHECK: acc.map_info varPtr(%[[PRIV]] : !acc.private_type<memref<8xi32>>)
+// CHECK-SAME: mapFlags(private)
+// CHECK-NOT: gang_private
+func.func @private_without_par_dims() {
+ %priv = acc.privatize : () -> !acc.private_type<memref<8xi32>>
+ return
+}
+
+// -----
+
+// A record element uses the padded stride: real(8) + real(4) has a size of 12
+// and an alignment of 8, so consecutive elements are 16 bytes apart.
+
+// CHECK-LABEL: func.func @private_static_record
+// CHECK: %[[PRIV:.*]] = acc.privatize
+// CHECK: %[[SIZE:.*]] = arith.constant 128 : i64
+// CHECK: acc.map_info varPtr(%[[PRIV]]
+// CHECK-SAME: size(%[[SIZE]] : i64)
+// CHECK-SAME: elementSize(12)
+// CHECK-SAME: mapFlags(private)
+func.func @private_static_record() {
+ %priv = acc.privatize par_dims(#acc<par_dims[]>)
+ : () -> !acc.private_type<!fir.array<8x!fir.type<_QFTpair{hi:f64,lo:f32}>>>
+ return
+}
+
+// -----
+
+// A runtime extent is multiplied in, so the size is an SSA value rather than a
+// constant. This is the case that has no memref equivalent: memref cannot hold
+// a record element type.
+
+// CHECK-LABEL: func.func @private_dynamic_record
+// CHECK-SAME: %[[N:.*]]: index
+// CHECK: %[[PRIV:.*]] = acc.privatize(%[[N]])
+// CHECK: %[[STRIDE:.*]] = arith.constant 16 : i64
+// CHECK: %[[EXTENT:.*]] = arith.index_cast %[[N]] : index to i64
+// CHECK: %[[SIZE:.*]] = arith.muli %[[STRIDE]], %[[EXTENT]] : i64
+// CHECK: acc.map_info varPtr(%[[PRIV]]
+// CHECK-SAME: size(%[[SIZE]] : i64)
+// CHECK-SAME: mapFlags(private)
+func.func @private_dynamic_record(%n: index) {
+ %priv = acc.privatize(%n) par_dims(#acc<par_dims[]>)
+ : (index) -> !acc.private_type<!fir.heap<!fir.array<?x!fir.type<_QFTpair{hi:f64,lo:f32}>>>>
+ return
+}
+
+// -----
+
+// Both extents of a partially dynamic shape contribute: the static extent is
+// folded into the stride constant, the dynamic one is multiplied in.
+
+// CHECK-LABEL: func.func @private_mixed_extents
+// CHECK-SAME: %[[N:.*]]: index
+// CHECK: %[[PRIV:.*]] = acc.privatize(%[[N]])
+// CHECK: %[[STRIDE:.*]] = arith.constant 16 : i64
+// CHECK: %[[EXTENT:.*]] = arith.index_cast %[[N]] : index to i64
+// CHECK: %[[SIZE:.*]] = arith.muli %[[STRIDE]], %[[EXTENT]] : i64
+// CHECK: acc.map_info varPtr(%[[PRIV]]
+// CHECK-SAME: size(%[[SIZE]] : i64)
+func.func @private_mixed_extents(%n: index) {
+ %priv = acc.privatize(%n) par_dims(#acc<par_dims[]>)
+ : (index) -> !acc.private_type<!fir.heap<!fir.array<4x?xf32>>>
+ return
+}
+
+// -----
+
+// A descriptor carries its own extents, so the type does not describe the
+// storage and no map_info is created for it.
+
+// CHECK-LABEL: func.func @private_descriptor
+// CHECK: acc.privatize
+// CHECK-NOT: acc.map_info
+func.func @private_descriptor() {
+ %priv = acc.privatize par_dims(#acc<par_dims[]>)
+ : () -> !acc.private_type<!fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>>
+ return
+}
diff --git a/flang/test/Fir/OpenACC/acc-fir-map-info-prep-types.mlir b/flang/test/Fir/OpenACC/acc-fir-map-info-prep-types.mlir
new file mode 100644
index 0000000000000..91b257a9c3165
--- /dev/null
+++ b/flang/test/Fir/OpenACC/acc-fir-map-info-prep-types.mlir
@@ -0,0 +1,228 @@
+// RUN: fir-opt %s --pass-pipeline="builtin.module(func.func(acc-fir-map-info-prep))" | FileCheck %s
+
+// Descriptor-backed variables mapped through their descriptor or c_ptr slot,
+// without a sibling implicit present of the pointee. When var is the
+// descriptor, desc is omitted from map_info whenever descKind names CFI on var.
+
+// CHECK-LABEL: func.func @assumed_shape
+// CHECK: %[[BOX:.*]] = fir.undefined !fir.box<!fir.array<?xf32>>
+// CHECK: acc.map_info var(%[[BOX]] : !fir.box<!fir.array<?xf32>>)
+// CHECK-SAME: elementSize(4)
+// CHECK-SAME: descKind(cfi)
+// CHECK-SAME: mapFlags(to)
+// CHECK-NOT: ptr_and_obj
+// CHECK-NOT: acc.copyin
+// CHECK: acc.data
+func.func @assumed_shape() {
+ %box = fir.undefined !fir.box<!fir.array<?xf32>>
+ %copy = acc.copyin var(%box : !fir.box<!fir.array<?xf32>>)
+ name("a") -> !fir.box<!fir.array<?xf32>>
+ acc.data dataOperands(%copy : !fir.box<!fir.array<?xf32>>) {
+ acc.terminator
+ }
+ return
+}
+
+// CHECK-LABEL: func.func @allocatable_array
+// CHECK: %[[SLOT:.*]] = fir.undefined !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
+// CHECK: acc.map_info varPtr(%[[SLOT]] : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>)
+// CHECK-SAME: elementSize(4)
+// CHECK-SAME: descKind(cfi)
+// CHECK-SAME: mapFlags(to,ptr_and_obj)
+// CHECK-NOT: acc.copyin
+// CHECK: acc.data
+func.func @allocatable_array() {
+ %slot = fir.undefined !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
+ %copy = acc.copyin varPtr(%slot : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>)
+ name("a") -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
+ acc.data dataOperands(%copy : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>) {
+ acc.terminator
+ }
+ return
+}
+
+// CHECK-LABEL: func.func @pointer_array
+// CHECK: %[[SLOT:.*]] = fir.undefined !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>
+// CHECK: acc.map_info varPtr(%[[SLOT]] : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>)
+// CHECK-SAME: elementSize(4)
+// CHECK-SAME: descKind(cfi)
+// CHECK-SAME: mapFlags(to,ptr_and_obj)
+// CHECK-NOT: acc.copyin
+// CHECK: acc.data
+func.func @pointer_array() {
+ %slot = fir.undefined !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>
+ %copy = acc.copyin varPtr(%slot : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>)
+ name("a") -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>
+ acc.data dataOperands(%copy : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>) {
+ acc.terminator
+ }
+ return
+}
+
+// CHECK-LABEL: func.func @allocatable_integer
+// CHECK: %[[SLOT:.*]] = fir.undefined !fir.ref<!fir.box<!fir.heap<i32>>>
+// CHECK: acc.map_info varPtr(%[[SLOT]] : !fir.ref<!fir.box<!fir.heap<i32>>>)
+// CHECK-SAME: elementSize(4)
+// CHECK-SAME: descKind(cfi)
+// CHECK-SAME: mapFlags(to,from,ptr_and_obj)
+// CHECK-NOT: acc.copyin
+// CHECK: acc.data
+func.func @allocatable_integer() {
+ %slot = fir.undefined !fir.ref<!fir.box<!fir.heap<i32>>>
+ %copy = acc.copyin varPtr(%slot : !fir.ref<!fir.box<!fir.heap<i32>>>)
+ dataClause(acc_copy) name("n")
+ -> !fir.ref<!fir.box<!fir.heap<i32>>>
+ acc.copyout accPtr(%copy : !fir.ref<!fir.box<!fir.heap<i32>>>)
+ to varPtr(%slot : !fir.ref<!fir.box<!fir.heap<i32>>>)
+ dataClause(acc_copy) name("n")
+ acc.data dataOperands(%copy : !fir.ref<!fir.box<!fir.heap<i32>>>) {
+ acc.terminator
+ }
+ return
+}
+
+// CHECK-LABEL: func.func @polymorphic_entity
+// CHECK: %[[SLOT:.*]] = fir.undefined !fir.ref<!fir.class<!fir.heap<!fir.type<_QMm_baseTbase_t{tag:i32}>>>>
+// CHECK: acc.map_info varPtr(%[[SLOT]] : !fir.ref<!fir.class<!fir.heap<!fir.type<_QMm_baseTbase_t{tag:i32}>>>>)
+// CHECK-SAME: elementSize(4)
+// CHECK-SAME: descKind(cfi)
+// CHECK-SAME: mapFlags(to,ptr_and_obj)
+// CHECK-NOT: acc.copyin
+// CHECK: acc.data
+func.func @polymorphic_entity() {
+ %slot = fir.undefined !fir.ref<!fir.class<!fir.heap<!fir.type<_QMm_baseTbase_t{tag:i32}>>>>
+ %copy = acc.copyin varPtr(%slot : !fir.ref<!fir.class<!fir.heap<!fir.type<_QMm_baseTbase_t{tag:i32}>>>>)
+ name("p") -> !fir.ref<!fir.class<!fir.heap<!fir.type<_QMm_baseTbase_t{tag:i32}>>>>
+ acc.data dataOperands(%copy : !fir.ref<!fir.class<!fir.heap<!fir.type<_QMm_baseTbase_t{tag:i32}>>>>) {
+ acc.terminator
+ }
+ return
+}
+
+// A c_ptr mapped by itself transfers only the 8-byte address object; its
+// pointee is not mapped, so there is nothing to attach.
+// CHECK-LABEL: func.func @cptr_copyin
+// CHECK: %[[CPTR:.*]] = fir.undefined !fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>>
+// CHECK: acc.map_info varPtr(%[[CPTR]] : !fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>>)
+// CHECK-SAME: elementSize(8)
+// CHECK-SAME: descKind(none)
+// CHECK-SAME: mapFlags(to)
+// CHECK-NOT: acc.copyin
+// CHECK: acc.data
+func.func @cptr_copyin() {
+ %p = fir.undefined !fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>>
+ %copy = acc.copyin varPtr(%p : !fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>>)
+ name("p") -> !fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>>
+ acc.data dataOperands(%copy : !fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>>) {
+ acc.terminator
+ }
+ return
+}
+
+// CHECK-LABEL: func.func @attach_pointer
+// CHECK: %[[SLOT:.*]] = fir.undefined !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>
+// CHECK: acc.map_info varPtr(%[[SLOT]] : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>)
+// CHECK-SAME: elementSize(4)
+// CHECK-SAME: descKind(cfi)
+// CHECK-SAME: mapFlags(ptr_and_obj)
+// CHECK-NOT: acc.attach
+// CHECK: acc.data
+func.func @attach_pointer() {
+ %slot = fir.undefined !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>
+ %att = acc.attach varPtr(%slot : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>)
+ name("a") -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>
+ acc.data dataOperands(%att : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>) {
+ acc.terminator
+ }
+ return
+}
+
+// Derived-type members: same attach / CFI facts as the standalone entities.
+// CHECK-LABEL: func.func @struct_members
+// CHECK-DAG: %[[ALLOC_ARR:.*]] = fir.coordinate_of %{{.*}}, alloc_arr
+// CHECK-DAG: %[[PTR_ARR:.*]] = fir.coordinate_of %{{.*}}, ptr_arr
+// CHECK-DAG: %[[ALLOC_INT:.*]] = fir.coordinate_of %{{.*}}, alloc_int
+// CHECK-DAG: %[[POLY:.*]] = fir.coordinate_of %{{.*}}, poly
+// CHECK-DAG: %[[CP:.*]] = fir.coordinate_of %{{.*}}, cp
+// CHECK: acc.map_info varPtr(%[[ALLOC_ARR]] : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>)
+// CHECK-SAME: elementSize(4)
+// CHECK-SAME: descKind(cfi)
+// CHECK-SAME: mapFlags(to,ptr_and_obj)
+// CHECK: acc.map_info varPtr(%[[PTR_ARR]] : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>)
+// CHECK-SAME: elementSize(4)
+// CHECK-SAME: descKind(cfi)
+// CHECK-SAME: mapFlags(to,ptr_and_obj)
+// CHECK: acc.map_info varPtr(%[[ALLOC_INT]] : !fir.ref<!fir.box<!fir.heap<i32>>>)
+// CHECK-SAME: elementSize(4)
+// CHECK-SAME: descKind(cfi)
+// CHECK-SAME: mapFlags(to,from,ptr_and_obj)
+// CHECK: acc.map_info varPtr(%[[POLY]] : !fir.ref<!fir.class<!fir.heap<!fir.type<_QMm_baseTbase_t{tag:i32}>>>>)
+// CHECK-SAME: elementSize(4)
+// CHECK-SAME: descKind(cfi)
+// CHECK-SAME: mapFlags(to,ptr_and_obj)
+// CHECK: acc.map_info varPtr(%[[CP]] : !fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>>)
+// CHECK-SAME: elementSize(8)
+// CHECK-SAME: descKind(none)
+// CHECK-SAME: mapFlags(to)
+// CHECK-NOT: acc.copyin
+// CHECK: acc.data
+func.func @struct_members() {
+ %h = fir.undefined !fir.ref<!fir.type<_QFstruct_membersTholder{alloc_arr:!fir.box<!fir.heap<!fir.array<?xf32>>>,ptr_arr:!fir.box<!fir.ptr<!fir.array<?xf32>>>,alloc_int:!fir.box<!fir.heap<i32>>,poly:!fir.class<!fir.heap<!fir.type<_QMm_baseTbase_t{tag:i32}>>>,cp:!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>}>>
+ %alloc_arr = fir.coordinate_of %h, alloc_arr : (!fir.ref<!fir.type<_QFstruct_membersTholder{alloc_arr:!fir.box<!fir.heap<!fir.array<?xf32>>>,ptr_arr:!fir.box<!fir.ptr<!fir.array<?xf32>>>,alloc_int:!fir.box<!fir.heap<i32>>,poly:!fir.class<!fir.heap<!fir.type<_QMm_baseTbase_t{tag:i32}>>>,cp:!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>}>>) -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
+ %ptr_arr = fir.coordinate_of %h, ptr_arr : (!fir.ref<!fir.type<_QFstruct_membersTholder{alloc_arr:!fir.box<!fir.heap<!fir.array<?xf32>>>,ptr_arr:!fir.box<!fir.ptr<!fir.array<?xf32>>>,alloc_int:!fir.box<!fir.heap<i32>>,poly:!fir.class<!fir.heap<!fir.type<_QMm_baseTbase_t{tag:i32}>>>,cp:!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>}>>) -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>
+ %alloc_int = fir.coordinate_of %h, alloc_int : (!fir.ref<!fir.type<_QFstruct_membersTholder{alloc_arr:!fir.box<!fir.heap<!fir.array<?xf32>>>,ptr_arr:!fir.box<!fir.ptr<!fir.array<?xf32>>>,alloc_int:!fir.box<!fir.heap<i32>>,poly:!fir.class<!fir.heap<!fir.type<_QMm_baseTbase_t{tag:i32}>>>,cp:!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>}>>) -> !fir.ref<!fir.box<!fir.heap<i32>>>
+ %poly = fir.coordinate_of %h, poly : (!fir.ref<!fir.type<_QFstruct_membersTholder{alloc_arr:!fir.box<!fir.heap<!fir.array<?xf32>>>,ptr_arr:!fir.box<!fir.ptr<!fir.array<?xf32>>>,alloc_int:!fir.box<!fir.heap<i32>>,poly:!fir.class<!fir.heap<!fir.type<_QMm_baseTbase_t{tag:i32}>>>,cp:!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>}>>) -> !fir.ref<!fir.class<!fir.heap<!fir.type<_QMm_baseTbase_t{tag:i32}>>>>
+ %cp = fir.coordinate_of %h, cp : (!fir.ref<!fir.type<_QFstruct_membersTholder{alloc_arr:!fir.box<!fir.heap<!fir.array<?xf32>>>,ptr_arr:!fir.box<!fir.ptr<!fir.array<?xf32>>>,alloc_int:!fir.box<!fir.heap<i32>>,poly:!fir.class<!fir.heap<!fir.type<_QMm_baseTbase_t{tag:i32}>>>,cp:!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>}>>) -> !fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>>
+ %c0 = acc.copyin varPtr(%alloc_arr : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>)
+ name("h%alloc_arr") -> !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
+ %c1 = acc.copyin varPtr(%ptr_arr : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>)
+ name("h%ptr_arr") -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>
+ %c2 = acc.copyin varPtr(%alloc_int : !fir.ref<!fir.box<!fir.heap<i32>>>)
+ dataClause(acc_copy) name("h%alloc_int")
+ -> !fir.ref<!fir.box<!fir.heap<i32>>>
+ acc.copyout accPtr(%c2 : !fir.ref<!fir.box<!fir.heap<i32>>>)
+ to varPtr(%alloc_int : !fir.ref<!fir.box<!fir.heap<i32>>>)
+ dataClause(acc_copy) name("h%alloc_int")
+ %c3 = acc.copyin varPtr(%poly : !fir.ref<!fir.class<!fir.heap<!fir.type<_QMm_baseTbase_t{tag:i32}>>>>)
+ name("h%poly") -> !fir.ref<!fir.class<!fir.heap<!fir.type<_QMm_baseTbase_t{tag:i32}>>>>
+ %c4 = acc.copyin varPtr(%cp : !fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>>)
+ name("h%cp") -> !fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>>
+ acc.data dataOperands(%c0, %c1, %c2, %c3, %c4 : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>, !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>, !fir.ref<!fir.box<!fir.heap<i32>>>, !fir.ref<!fir.class<!fir.heap<!fir.type<_QMm_baseTbase_t{tag:i32}>>>>, !fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>>) {
+ acc.terminator
+ }
+ return
+}
+
+// Mapping a tuple of references transfers the tuple storage: every member is
+// one target address, so this is two pointers (16).
+// CHECK-LABEL: func.func @tuple_of_references
+// CHECK: %[[SIZE:.*]] = arith.constant 16 : i64
+// CHECK: acc.map_info varPtr(%arg0 : !fir.ref<tuple<!fir.ref<i32>, !fir.ref<f64>>>)
+// CHECK-SAME: size(%[[SIZE]] : i64)
+// CHECK-SAME: mapFlags(to,implicit)
+func.func @tuple_of_references(
+ %arg0: !fir.ref<tuple<!fir.ref<i32>, !fir.ref<f64>>>) {
+ %copy = acc.copyin
+ varPtr(%arg0 : !fir.ref<tuple<!fir.ref<i32>, !fir.ref<f64>>>)
+ implicit(true) name("") -> !fir.ref<tuple<!fir.ref<i32>, !fir.ref<f64>>>
+ acc.kernel_environment
+ dataOperands(%copy : !fir.ref<tuple<!fir.ref<i32>, !fir.ref<f64>>>) {
+ }
+ return
+}
+
+// A derived type with no data components, such as one that only declares a
+// type-bound procedure, has zero-sized storage.
+// CHECK-LABEL: func.func @empty_record
+// CHECK: %[[SIZE:.*]] = arith.constant 0 : i64
+// CHECK: acc.map_info varPtr(%arg0 : !fir.ref<!fir.type<_QMm_emptyTempty_t>>)
+// CHECK-SAME: size(%[[SIZE]] : i64)
+// CHECK-SAME: elementSize(0)
+func.func @empty_record(%arg0: !fir.ref<!fir.type<_QMm_emptyTempty_t>>) {
+ %copy = acc.copyin varPtr(%arg0 : !fir.ref<!fir.type<_QMm_emptyTempty_t>>)
+ name("tt") -> !fir.ref<!fir.type<_QMm_emptyTempty_t>>
+ acc.data dataOperands(%copy : !fir.ref<!fir.type<_QMm_emptyTempty_t>>) {
+ acc.terminator
+ }
+ return
+}
diff --git a/flang/test/Fir/OpenACC/acc-fir-map-info-prep.mlir b/flang/test/Fir/OpenACC/acc-fir-map-info-prep.mlir
new file mode 100644
index 0000000000000..06246ff4342b9
--- /dev/null
+++ b/flang/test/Fir/OpenACC/acc-fir-map-info-prep.mlir
@@ -0,0 +1,86 @@
+// RUN: fir-opt %s --pass-pipeline="builtin.module(func.func(acc-fir-map-info-prep))" | FileCheck %s
+
+// Nested box member copyin: map_info carries varPtrPtr = descriptor slot.
+
+// CHECK-LABEL: func.func @nested_box_member
+// CHECK: %[[BOX:.*]] = fir.load %[[SLOT:.*]] : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
+// CHECK: %[[ADDR:.*]] = fir.box_addr %[[BOX]]
+// CHECK: acc.map_info varPtr(%[[ADDR]] : !fir.heap<!fir.array<?xf32>>)
+// CHECK-SAME: varPtrPtr(%[[SLOT]] : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>)
+// CHECK-SAME: desc(%[[BOX]] : !fir.box<!fir.heap<!fir.array<?xf32>>>)
+// CHECK-SAME: elementSize(4)
+// CHECK-SAME: descKind(cfi)
+// CHECK-SAME: mapFlags(to,ptr_and_obj)
+// CHECK-NOT: acc.copyin
+// CHECK: acc.data
+
+func.func @nested_box_member() {
+ %0 = fir.undefined !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
+ %1 = fir.load %0 : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
+ %2 = fir.box_addr %1 : (!fir.box<!fir.heap<!fir.array<?xf32>>>) -> !fir.heap<!fir.array<?xf32>>
+ %copy = acc.copyin varPtr(%2 : !fir.heap<!fir.array<?xf32>>)
+ dataClause(acc_copyin) structured(true) name("m") -> !fir.heap<!fir.array<?xf32>>
+ acc.data dataOperands(%copy : !fir.heap<!fir.array<?xf32>>) {
+ acc.terminator
+ }
+ return
+}
+
+// CHECK-LABEL: func.func @derived_with_box
+// CHECK: %[[VAR:.*]] = fir.undefined !fir.ref<!fir.type<_QMtypesTderived{member:!fir.box<!fir.heap<!fir.array<?xf32>>>}>>
+// CHECK: fir.type_desc !fir.type<_QMtypesTderived{{.*}}>
+// CHECK: %[[TDESC:.*]] = fir.address_of(@_QMtypesEXdtXderived)
+// CHECK: fir.field_index sizeinbytes
+// CHECK: %[[ADDR:.*]] = fir.coordinate_of %[[TDESC]], sizeinbytes
+// CHECK: %[[SIZE:.*]] = fir.load %[[ADDR]]
+// CHECK: acc.map_info varPtr(%[[VAR]] : !fir.ref<!fir.type<_QMtypesTderived{member:!fir.box<!fir.heap<!fir.array<?xf32>>>}>>)
+// CHECK-SAME: size(%[[SIZE]] : i64)
+// CHECK-SAME: descKind(none)
+// CHECK-SAME: mapFlags(to)
+// CHECK-NOT: acc.copyin
+// CHECK: acc.data
+
+fir.global linkonce_odr @_QMtypesEXdtXderived constant target : !fir.type<_QM__fortran_type_infoTderivedtype{sizeinbytes:i64}> {
+ %0 = fir.undefined !fir.type<_QM__fortran_type_infoTderivedtype{sizeinbytes:i64}>
+ fir.has_value %0 : !fir.type<_QM__fortran_type_infoTderivedtype{sizeinbytes:i64}>
+}
+
+func.func @derived_with_box() {
+ %0 = fir.undefined !fir.ref<!fir.type<_QMtypesTderived{member:!fir.box<!fir.heap<!fir.array<?xf32>>>}>>
+ %copy = acc.copyin varPtr(%0 : !fir.ref<!fir.type<_QMtypesTderived{member:!fir.box<!fir.heap<!fir.array<?xf32>>>}>>)
+ dataClause(acc_copyin) name("st")
+ -> !fir.ref<!fir.type<_QMtypesTderived{member:!fir.box<!fir.heap<!fir.array<?xf32>>>}>>
+ acc.data dataOperands(%copy : !fir.ref<!fir.type<_QMtypesTderived{member:!fir.box<!fir.heap<!fir.array<?xf32>>>}>>) {
+ acc.terminator
+ }
+ return
+}
+
+// firstprivate_map is a live-in (not on dataOperands) but still gets map_info.
+// A partial array section keeps the full-array byte size on map_info; bounds
+// carry the section.
+
+// CHECK-LABEL: func.func @firstprivate_partial_array
+// CHECK: %[[SOURCE_EXTENT:.*]] = arith.constant 100 : index
+// CHECK: %[[BOUND:.*]] = acc.bounds
+// CHECK-SAME: sourceExtent(%[[SOURCE_EXTENT]] : index)
+// CHECK: %[[SIZE:.*]] = arith.constant 400 : i64
+// CHECK: acc.map_info varPtr(%{{.*}} : !fir.ref<!fir.array<100xf32>>)
+// CHECK-SAME: bounds(%[[BOUND]])
+// CHECK-SAME: size(%[[SIZE]] : i64)
+// CHECK-SAME: elementSize(4)
+// CHECK-SAME: mapFlags(to,private)
+// CHECK-NOT: acc.firstprivate_map
+func.func @firstprivate_partial_array(%w: !fir.ref<!fir.array<100xf32>>) {
+ %c1 = arith.constant 1 : index
+ %lb = arith.constant 4 : index
+ %ub = arith.constant 7 : index
+ %ext = arith.constant 100 : index
+ %st = arith.constant 1 : index
+ %bnd = acc.bounds lowerbound(%lb : index) upperbound(%ub : index)
+ extent(%ext : index) stride(%st : index) startIdx(%c1 : index)
+ sourceExtent(%ext : index)
+ %fp = acc.firstprivate_map varPtr(%w : !fir.ref<!fir.array<100xf32>>)
+ bounds(%bnd) name("w") -> !fir.ref<!fir.array<100xf32>>
+ return
+}
diff --git a/flang/unittests/Optimizer/OpenACC/FIROpenACCSupportAnalysisTest.cpp b/flang/unittests/Optimizer/OpenACC/FIROpenACCSupportAnalysisTest.cpp
index d00aef28d290a..4b74e26b34816 100644
--- a/flang/unittests/Optimizer/OpenACC/FIROpenACCSupportAnalysisTest.cpp
+++ b/flang/unittests/Optimizer/OpenACC/FIROpenACCSupportAnalysisTest.cpp
@@ -123,6 +123,20 @@ TEST_F(FIROpenACCSupportAnalysisTest, TupleWithFIRArrayMemberSizeAndAlignment) {
EXPECT_EQ(result->second, expected->second);
}
+TEST_F(FIROpenACCSupportAnalysisTest, TupleOfReferencesSizesAsPointers) {
+ Type tupleTy = TupleType::get(&context,
+ {fir::ReferenceType::get(IntegerType::get(&context, 32)),
+ fir::ReferenceType::get(Float64Type::get(&context))});
+ std::optional<acc::TypeSizeAndAlignment> result =
+ support.getTypeSizeAndAlignment(tupleTy, module);
+ LLVM::LLVMPointerType ptrTy = LLVM::LLVMPointerType::get(&context);
+ std::optional<acc::TypeSizeAndAlignment> pointer =
+ acc::getTypeSizeAndAlignment(ptrTy, module);
+ ASSERT_TRUE(result.has_value());
+ ASSERT_TRUE(pointer.has_value());
+ EXPECT_EQ(result->first.getFixedValue(), pointer->first.getFixedValue() * 2);
+}
+
TEST_F(FIROpenACCSupportAnalysisTest, FIRBoxTypeSizeAndAlignment) {
Type f32 = Float32Type::get(&context);
Type seqTy = fir::SequenceType::get({4, 3}, f32);
diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCCGEnums.td b/mlir/include/mlir/Dialect/OpenACC/OpenACCCGEnums.td
new file mode 100644
index 0000000000000..31b0523480c79
--- /dev/null
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCCGEnums.td
@@ -0,0 +1,85 @@
+//===- OpenACCCGEnums.td - OpenACC codegen enums -----------*- tablegen -*-===//
+//
+// Part of the MLIR Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Defines OpenACC codegen enums used by intermediate operations that do not
+// map directly to OpenACC language constructs.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef OPENACCCG_ENUMS
+#define OPENACCCG_ENUMS
+
+// Descriptor layouts. Bit positions match the runtime TGT_ACC_DESC_* values.
+def OpenACC_DataDescKindNone : I32BitEnumAttrCaseNone<"none">;
+def OpenACC_DataDescKindCFI : I32BitEnumAttrCaseBit<"cfi", 0>;
+def OpenACC_DataDescKindMemref : I32BitEnumAttrCaseBit<"memref", 1>;
+// Overlay of OpenACC bounds nested on a base descriptor (cfi / memref /
+// generic). Combines with the cases above; matches TGT_ACC_DESC_OPENACC.
+def OpenACC_DataDescKindOpenACC : I32BitEnumAttrCaseBit<"openacc", 12>;
+
+def OpenACC_DataDescKindEnum : I32BitEnumAttr<
+ "DataDescKind",
+ "OpenACC offload descriptor kind (may be combined for nested descriptors)",
+ [OpenACC_DataDescKindNone, OpenACC_DataDescKindCFI,
+ OpenACC_DataDescKindMemref, OpenACC_DataDescKindOpenACC]> {
+ let separator = ",";
+ let cppNamespace = "::mlir::acc";
+ let genSpecializedAttr = 0;
+ let printBitEnumPrimaryGroups = 1;
+ let printBitEnumQuoted = 0;
+}
+
+def OpenACC_DataDescKindAttr
+ : EnumAttr<OpenACC_Dialect, OpenACC_DataDescKindEnum, "desc_kind">;
+
+// Offload map-type flags. Bit positions match the runtime TGT_ACC_MAPTYPE_*
+// values.
+def OpenACC_MapFlagsNone : I32BitEnumAttrCaseNone<"none">;
+def OpenACC_MapFlagsTo : I32BitEnumAttrCaseBit<"to", 0>;
+def OpenACC_MapFlagsFrom : I32BitEnumAttrCaseBit<"from", 1>;
+def OpenACC_MapFlagsDelete : I32BitEnumAttrCaseBit<"delete_", 3, "delete">;
+def OpenACC_MapFlagsPtrAndObj : I32BitEnumAttrCaseBit<"ptr_and_obj", 4>;
+def OpenACC_MapFlagsPrivate : I32BitEnumAttrCaseBit<"private_", 7, "private">;
+def OpenACC_MapFlagsLiteral : I32BitEnumAttrCaseBit<"literal", 8>;
+def OpenACC_MapFlagsImplicit : I32BitEnumAttrCaseBit<"implicit", 9>;
+def OpenACC_MapFlagsDevptr : I32BitEnumAttrCaseBit<"devptr", 10>;
+def OpenACC_MapFlagsManagedDevptr : I32BitEnumAttrCaseBit<"managed_devptr", 11>;
+def OpenACC_MapFlagsNoCreate : I32BitEnumAttrCaseBit<"no_create", 13>;
+def OpenACC_MapFlagsGangPrivate : I32BitEnumAttrCaseBit<"gang_private", 14>;
+def OpenACC_MapFlagsWorkerPrivate : I32BitEnumAttrCaseBit<"worker_private", 15>;
+def OpenACC_MapFlagsVectorPrivate : I32BitEnumAttrCaseBit<"vector_private", 16>;
+def OpenACC_MapFlagsInitZero : I32BitEnumAttrCaseBit<"init_zero", 17>;
+def OpenACC_MapFlagsDeviceResident
+ : I32BitEnumAttrCaseBit<"device_resident", 18>;
+def OpenACC_MapFlagsIfPresent : I32BitEnumAttrCaseBit<"if_present", 19>;
+def OpenACC_MapFlagsPresent : I32BitEnumAttrCaseBit<"present", 20>;
+def OpenACC_MapFlagsDescriptor : I32BitEnumAttrCaseBit<"descriptor", 21>;
+def OpenACC_MapFlagsReduction : I32BitEnumAttrCaseBit<"reduction", 22>;
+
+def OpenACC_MapFlagsEnum : I32BitEnumAttr<
+ "MapFlags", "OpenACC offload map-type flags",
+ [OpenACC_MapFlagsNone, OpenACC_MapFlagsTo, OpenACC_MapFlagsFrom,
+ OpenACC_MapFlagsDelete, OpenACC_MapFlagsPtrAndObj, OpenACC_MapFlagsPrivate,
+ OpenACC_MapFlagsLiteral, OpenACC_MapFlagsImplicit, OpenACC_MapFlagsDevptr,
+ OpenACC_MapFlagsManagedDevptr, OpenACC_MapFlagsNoCreate,
+ OpenACC_MapFlagsGangPrivate, OpenACC_MapFlagsWorkerPrivate,
+ OpenACC_MapFlagsVectorPrivate, OpenACC_MapFlagsInitZero,
+ OpenACC_MapFlagsDeviceResident, OpenACC_MapFlagsIfPresent,
+ OpenACC_MapFlagsPresent, OpenACC_MapFlagsDescriptor,
+ OpenACC_MapFlagsReduction]> {
+ let separator = ",";
+ let cppNamespace = "::mlir::acc";
+ let genSpecializedAttr = 0;
+ let printBitEnumPrimaryGroups = 1;
+ let printBitEnumQuoted = 0;
+}
+
+def OpenACC_MapFlagsAttr
+ : EnumAttr<OpenACC_Dialect, OpenACC_MapFlagsEnum, "map_flags">;
+
+#endif // OPENACCCG_ENUMS
diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCCGOps.td b/mlir/include/mlir/Dialect/OpenACC/OpenACCCGOps.td
index 30dc6f73062e6..253c0f5bf1cf2 100644
--- a/mlir/include/mlir/Dialect/OpenACC/OpenACCCGOps.td
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCCGOps.td
@@ -746,4 +746,126 @@ def OpenACC_PredicateRegionOp
let hasVerifier = 1;
}
+//===----------------------------------------------------------------------===//
+// acc.map_info
+//===----------------------------------------------------------------------===//
+
+def OpenACC_MapInfoOp : OpenACC_Op<"map_info", [AttrSizedOperandSegments,
+ AllTypesMatch<["var", "accVar"]>]> {
+ let summary = "OpenACC map metadata for offload lowering";
+ let description = [{
+ Captures the runtime allocation or mapping contract for one object,
+ including attach points, descriptor facts, bounds, byte size, and offload
+ map-type flags. This operation does not itself allocate or map the object.
+ `accVar` is a result token of the same type as `var`; a construct consumes
+ the token to issue the corresponding runtime operation.
+
+ Data clauses use the token as a data operand of a data, compute, declare,
+ or update construct. When `var` is an `acc.privatize` result, the token is
+ used as the kernel argument and its `private` and parallel-level map flags
+ request the runtime allocation with the required replication.
+
+ - `var`: Mapped variable (pointer-like or mappable, same as data-entry
+ `var`).
+ - `varType`: Type of the mapped object (same role as data-entry `varType`).
+ - `varPtrPtr`: Optional attach point (host address of the pointer slot).
+ - `desc`: Optional base descriptor when it differs from `var`. Omitted when
+ `var` itself is that descriptor; consumers then use `var` whenever
+ `descKind` is not `none`.
+ - `descKind`: Which descriptor layouts describe the mapped object, as a
+ bitfield so that a nested descriptor can name each level. `none` means
+ the object is described by its address and `size` alone.
+ - `mapFlags`: Offload map-type flags (`to`/`from`/`ptr_and_obj`/`private`/
+ ...), combining enter and exit clause effects or describing a
+ privatized allocation.
+ - `elementSize`: Optional byte size of one element of the mapped object.
+ `bounds` and descriptor extents count elements rather than bytes, so
+ this is what converts them into byte strides. It is stated explicitly
+ because `varType` does not always determine it: a character or
+ derived-type element takes its length from the descriptor.
+ - `size`: Optional total mapped byte size. A constant `0` means the size is
+ carried by `bounds` or by a descriptor instead of being stated here, a
+ constant `-1` means it is not known at compile time, and a non-constant
+ value supplies the size at run time (e.g. loaded from a type
+ descriptor).
+ - `exitLoc`: Optional source location of the exit effects, which for a
+ structured construct is its end directive rather than the location of
+ this operation. Reported by the runtime for the region end.
+ - `accVar`: Result token. Its use by a construct causes that construct to
+ issue the runtime mapping or allocation described here.
+ }];
+
+ let arguments = (ins OpenACC_AnyPointerOrMappableType:$var,
+ TypeAttr:$varType,
+ Optional<OpenACC_PointerLikeTypeInterface>:$varPtrPtr,
+ Optional<OpenACC_AnyPointerOrMappableType>:$desc,
+ DefaultValuedAttr<OpenACC_DataDescKindAttr,
+ "mlir::acc::DataDescKind::none">:$descKind,
+ Variadic<OpenACC_DataBoundsType>:$bounds,
+ Optional<IntOrIndex>:$size,
+ DefaultValuedAttr<OpenACC_MapFlagsAttr,
+ "mlir::acc::MapFlags::none">:$mapFlags,
+ OptionalAttr<StrAttr>:$name,
+ OptionalAttr<ConfinedAttr<I64Attr, [IntNonNegative]>>:$elementSize,
+ OptionalAttr<LocationAttr>:$exitLoc);
+
+ let results = (outs OpenACC_AnyPointerOrMappableType:$accVar);
+
+ let extraClassDeclaration = [{
+ mlir::TypedValue<mlir::acc::PointerLikeType> getVarPtr() {
+ return mlir::dyn_cast<mlir::TypedValue<mlir::acc::PointerLikeType>>(
+ getVar());
+ }
+ mlir::TypedValue<mlir::acc::PointerLikeType> getAccPtr() {
+ return mlir::dyn_cast<mlir::TypedValue<mlir::acc::PointerLikeType>>(
+ getAccVar());
+ }
+ }];
+
+ let assemblyFormat = [{
+ custom<Var>($var) `:` custom<VarPtrType>(type($var), $varType)
+ oilist(
+ `varPtrPtr` `(` $varPtrPtr `:` type($varPtrPtr) `)`
+ | `desc` `(` $desc `:` type($desc) `)`
+ | `bounds` `(` $bounds `)`
+ | `size` `(` $size `:` type($size) `)`
+ | `elementSize` `(` $elementSize `)`
+ | `name` `(` $name `)`
+ | `exitLoc` `(` custom<SourceLocation>($exitLoc) `)`
+ )
+ `descKind` `(` enum($descKind) `)`
+ `mapFlags` `(` enum($mapFlags) `)`
+ `->` type($accVar) attr-dict
+ }];
+
+ let builders = [
+ OpBuilder<(ins "::mlir::Type":$accVarType, "::mlir::Value":$var,
+ "::mlir::Type":$varType,
+ "::mlir::acc::MapFlags":$mapFlags,
+ CArg<"::mlir::Value", "{}">:$varPtrPtr,
+ CArg<"::mlir::Value", "{}">:$desc,
+ CArg<"::mlir::acc::DataDescKind",
+ "::mlir::acc::DataDescKind::none">:$descKind,
+ CArg<"::mlir::ValueRange", "{}">:$bounds,
+ CArg<"std::optional<::llvm::StringRef>",
+ "std::nullopt">:$name,
+ CArg<"std::optional<int64_t>", "std::nullopt">:$elementSize,
+ CArg<"::mlir::Value", "{}">:$size,
+ CArg<"::mlir::LocationAttr", "{}">:$exitLoc),
+ [{
+ ::mlir::acc::DataDescKind kind = descKind;
+ if (!bounds.empty())
+ kind = kind | ::mlir::acc::DataDescKind::openacc;
+ build($_builder, $_state, accVarType, var, varType, varPtrPtr, desc,
+ kind, bounds, size, mapFlags,
+ name ? $_builder.getStringAttr(*name) : ::mlir::StringAttr{},
+ elementSize ? $_builder.getI64IntegerAttr(*elementSize)
+ : ::mlir::IntegerAttr{},
+ exitLoc);
+ }]>,
+ ];
+
+ let hasVerifier = 1;
+}
+
#endif // OPENACC_CG_OPS
diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td b/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td
index b91f93298c339..dbb3502049a92 100644
--- a/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td
@@ -534,6 +534,10 @@ def OpenACC_DataBoundsOp : OpenACC_Op<"bounds",
but not checked for consistency). When the source language's arrays are
not zero-based, the `startIdx` must specify the zero-position index.
+ `sourceExtent` is the extent of the corresponding dimension in the
+ original array. It may differ from `extent`, which describes the selected
+ section. When absent, the source extent is the same as `extent`.
+
The `stride` represents the distance between consecutive elements. For
multi-dimensional arrays, the `stride` for each outer dimension must account
for the complete size of all inner dimensions.
@@ -570,6 +574,7 @@ def OpenACC_DataBoundsOp : OpenACC_Op<"bounds",
let arguments = (ins Optional<IntOrIndex>:$lowerbound,
Optional<IntOrIndex>:$upperbound,
Optional<IntOrIndex>:$extent,
+ Optional<IntOrIndex>:$sourceExtent,
Optional<IntOrIndex>:$stride,
DefaultValuedAttr<BoolAttr, "false">:$strideInBytes,
Optional<IntOrIndex>:$startIdx);
@@ -580,6 +585,7 @@ def OpenACC_DataBoundsOp : OpenACC_Op<"bounds",
`lowerbound` `(` $lowerbound `:` type($lowerbound) `)`
| `upperbound` `(` $upperbound `:` type($upperbound) `)`
| `extent` `(` $extent `:` type($extent) `)`
+ | `sourceExtent` `(` $sourceExtent `:` type($sourceExtent) `)`
| `stride` `(` $stride `:` type($stride) `)`
| `startIdx` `(` $startIdx `:` type($startIdx) `)`
) (`strideInBytes` `(` $strideInBytes^ `)`)? attr-dict
@@ -592,7 +598,8 @@ def OpenACC_DataBoundsOp : OpenACC_Op<"bounds",
build($_builder, $_state,
::mlir::acc::DataBoundsType::get($_builder.getContext()),
/*lowerbound=*/{}, /*upperbound=*/{}, extent,
- /*stride=*/{}, /*strideInBytes=*/$_builder.getBoolAttr(false),
+ /*sourceExtent=*/{}, /*stride=*/{},
+ /*strideInBytes=*/$_builder.getBoolAttr(false),
/*startIdx=*/{});
}]
>,
@@ -601,10 +608,21 @@ def OpenACC_DataBoundsOp : OpenACC_Op<"bounds",
build($_builder, $_state,
::mlir::acc::DataBoundsType::get($_builder.getContext()),
lowerbound, upperbound, /*extent=*/{},
- /*stride=*/{}, /*strideInBytes=*/$_builder.getBoolAttr(false),
+ /*sourceExtent=*/{}, /*stride=*/{},
+ /*strideInBytes=*/$_builder.getBoolAttr(false),
/*startIdx=*/{});
}]
- >
+ >,
+ OpBuilder<(ins "::mlir::Type":$resultType,
+ "::mlir::Value":$lowerbound,
+ "::mlir::Value":$upperbound,
+ "::mlir::Value":$extent,
+ "::mlir::Value":$stride,
+ "bool":$strideInBytes,
+ "::mlir::Value":$startIdx), [{
+ build($_builder, $_state, resultType, lowerbound, upperbound, extent,
+ /*sourceExtent=*/{}, stride, strideInBytes, startIdx);
+ }]>
];
}
@@ -3808,6 +3826,7 @@ def OpenACC_OnDeviceOp : OpenACC_Op<"on_device"> {
}];
}
+include "mlir/Dialect/OpenACC/OpenACCCGEnums.td"
include "mlir/Dialect/OpenACC/OpenACCCGAttributes.td"
include "mlir/Dialect/OpenACC/OpenACCCGOps.td"
diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCUtilsCG.h b/mlir/include/mlir/Dialect/OpenACC/OpenACCUtilsCG.h
index 9550f951c11dc..2e9a734acecf3 100644
--- a/mlir/include/mlir/Dialect/OpenACC/OpenACCUtilsCG.h
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCUtilsCG.h
@@ -16,6 +16,7 @@
#include "mlir/Dialect/OpenACC/OpenACC.h"
#include "mlir/Dialect/OpenACC/OpenACCParMapping.h"
+#include "mlir/IR/Builders.h"
#include "mlir/IR/IRMapping.h"
#include "mlir/IR/Value.h"
#include "mlir/Interfaces/DataLayoutInterfaces.h"
@@ -189,6 +190,62 @@ std::optional<int64_t> getPrivateLocalSharedMemoryUpperBoundBytes(
PrivateLocalOp privateLocal, ComputeRegionOp computeRegion, ModuleOp module,
const ACCToGPUMappingPolicy &policy, OpenACCSupport *support = nullptr);
+/// Returns true when `mapEntryOp` carries an attach point (`varPtrPtr`).
+bool hasAttachPoint(Operation *mapEntryOp);
+
+/// Returns descriptor kind from `acc.map_info`, or `none` for other ops.
+DataDescKind getDataDescKind(Operation *mapEntryOp);
+
+/// Returns descriptor value from `acc.map_info`. When `desc` is omitted and
+/// `descKind` is not `none`, returns `var` (the mapped object is the
+/// descriptor). Returns null for other ops.
+Value getDesc(Operation *mapEntryOp);
+
+/// Returns element size in bytes from `acc.map_info`, if present.
+std::optional<int64_t> getMapElementSize(Operation *mapEntryOp);
+
+/// Returns the optional `size` operand from `acc.map_info`, or null.
+Value getMapSize(Operation *mapEntryOp);
+
+/// Returns offload map-type flags from `acc.map_info`, if present.
+std::optional<MapFlags> getMapFlags(Operation *mapEntryOp);
+
+/// Compute the private and parallel-level map flags for privatized storage.
+/// Storage that names no parallel dimension is private without being
+/// replicated per level, so only the private flag is set.
+MapFlags computePrivatizeMapFlags(PrivatizeOp privatizeOp,
+ const ACCToGPUMappingPolicy &policy);
+
+/// Fold enter (+ paired exit) data-clause semantics into offload map flags.
+/// `ptrAndObj` is supplied by the caller from type-specific attach discovery.
+MapFlags computeDataClauseMapFlags(Operation *entryOp, bool ptrAndObj);
+
+/// True when another data clause of the same construct maps the same variable
+/// with a copy-back and no copy-in. One entry operation is emitted per clause,
+/// so a construct such as `create(a) copyout(a)` or `copyin(a) copyout(a)` maps
+/// `a` twice. The runtime keeps one mapping per variable and acts on it once,
+/// which makes the copy-back depend on whichever entry it processes: the entry
+/// that has no copy-back of its own must therefore carry `from` as well.
+bool hasCopyOutSibling(Operation *entryOp);
+
+/// Returns the data exit operations paired with the data entry result
+/// \p entryResult, which take it as their `accVar`.
+SmallVector<Operation *> getPairedDataExitOps(Value entryResult);
+
+/// Compute total mapped byte size for `acc.map_info`.
+/// Returns 0 when bounds or a non-`none` descriptor kind carry size, the
+/// mappable size when statically known, and -1 when the size cannot be
+/// determined statically. \p support sizes the members of aggregate types that
+/// belong to a dialect, such as a tuple holding dialect-specific references.
+int64_t computeMapInfoSizeBytes(Value var, Type varType, DataDescKind descKind,
+ ValueRange bounds, const DataLayout &dataLayout,
+ OpenACCSupport *support = nullptr);
+
+/// Record known extents of the source array on bounds that may describe a
+/// section. Dynamic / unknown extents are left unset.
+void populateSourceExtents(ValueRange bounds, ArrayRef<int64_t> shape,
+ OpBuilder &builder);
+
} // namespace acc
} // namespace mlir
diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCUtilsType.h b/mlir/include/mlir/Dialect/OpenACC/OpenACCUtilsType.h
index d37549a25230c..093367223d17b 100644
--- a/mlir/include/mlir/Dialect/OpenACC/OpenACCUtilsType.h
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCUtilsType.h
@@ -13,6 +13,7 @@
#ifndef MLIR_DIALECT_OPENACC_OPENACCUTILSTYPE_H_
#define MLIR_DIALECT_OPENACC_OPENACCUTILSTYPE_H_
+#include "mlir/IR/Value.h"
#include "llvm/Support/TypeSize.h"
#include <optional>
#include <utility>
@@ -38,20 +39,26 @@ using TypeSizeAndAlignment = std::pair<llvm::TypeSize, llvm::TypeSize>;
/// OpenACC layout decisions, but not a complete ABI guarantee. For final size
/// computations, use LLVM materialized types.
///
-/// When \p support is provided, aggregate element types are sized by recursing
-/// through \p support so dialect-specific implementations can handle nested
-/// types.
+/// \p ty itself is sized dialect-agnostically; when \p support is provided it
+/// sizes aggregate element types, so that nested dialect types are handled.
+/// Callers that hold an OpenACCSupport should therefore ask it directly -
+/// OpenACCSupport::getTypeSizeAndAlignment covers dialect types and falls back
+/// to this utility - and call this utility directly only for a type this
+/// utility is expected to know.
+///
+/// When \p var is provided, MappableType sizes the mapped object rather than
+/// the type's storage alone.
///
/// Returns std::nullopt when the size is not statically computable or the type
/// is not supported.
std::optional<TypeSizeAndAlignment>
getTypeSizeAndAlignment(Type ty, ModuleOp module, const DataLayout &dl,
- OpenACCSupport *support = nullptr);
+ OpenACCSupport *support = nullptr, Value var = {});
/// Same as above, obtaining \p dl from \p module via getDataLayout.
std::optional<TypeSizeAndAlignment>
getTypeSizeAndAlignment(Type ty, ModuleOp module,
- OpenACCSupport *support = nullptr);
+ OpenACCSupport *support = nullptr, Value var = {});
/// Cast \p value to \p resultType via PointerLikeType::genCast when needed.
/// Returns \p value unchanged if types already match. Emits an error and
diff --git a/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp b/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
index e8860ae462819..332226846f4dd 100644
--- a/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
+++ b/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
@@ -1007,6 +1007,26 @@ static void printVarPtrType(mlir::OpAsmPrinter &p, mlir::Operation *op,
}
}
+// A location cannot be parsed through the generic attribute directive: the
+// generated parser needs a concrete attribute class, while any of the location
+// attributes may appear here.
+static ParseResult parseSourceLocation(mlir::OpAsmParser &parser,
+ mlir::LocationAttr &locAttr) {
+ llvm::SMLoc attrLoc = parser.getCurrentLocation();
+ mlir::Attribute attr;
+ if (failed(parser.parseAttribute(attr)))
+ return failure();
+ locAttr = mlir::dyn_cast<mlir::LocationAttr>(attr);
+ if (!locAttr)
+ return parser.emitError(attrLoc, "expected location attribute");
+ return success();
+}
+
+static void printSourceLocation(mlir::OpAsmPrinter &p, mlir::Operation *op,
+ mlir::LocationAttr locAttr) {
+ p.printAttribute(locAttr);
+}
+
static ParseResult parseRecipeSym(mlir::OpAsmParser &parser,
mlir::SymbolRefAttr &recipeAttr) {
if (failed(parser.parseAttribute(recipeAttr)))
@@ -2154,8 +2174,8 @@ static LogicalResult checkDataOperands(Op op,
for (mlir::Value operand : operands)
if (!mlir::isa<acc::AttachOp, acc::CopyinOp, acc::CopyoutOp, acc::CreateOp,
acc::DeleteOp, acc::DetachOp, acc::DevicePtrOp,
- acc::GetDevicePtrOp, acc::NoCreateOp, acc::PresentOp>(
- operand.getDefiningOp()))
+ acc::GetDevicePtrOp, acc::NoCreateOp, acc::PresentOp,
+ acc::MapInfoOp>(operand.getDefiningOp()))
return op.emitError(
"expect data entry/exit operation or acc.getdeviceptr "
"as defining op");
@@ -4279,8 +4299,8 @@ LogicalResult acc::DataOp::verify() {
if (isa<BlockArgument>(operand) ||
!mlir::isa<acc::AttachOp, acc::CopyinOp, acc::CopyoutOp, acc::CreateOp,
acc::DeleteOp, acc::DetachOp, acc::DevicePtrOp,
- acc::GetDevicePtrOp, acc::NoCreateOp, acc::PresentOp>(
- operand.getDefiningOp()))
+ acc::GetDevicePtrOp, acc::NoCreateOp, acc::PresentOp,
+ acc::MapInfoOp>(operand.getDefiningOp()))
return emitError("expect data entry/exit operation or acc.getdeviceptr "
"as defining op");
@@ -4503,7 +4523,7 @@ LogicalResult acc::EnterDataOp::verify() {
return emitError("wait_devnum cannot appear without waitOperands");
for (mlir::Value operand : getDataClauseOperands())
- if (!mlir::isa<acc::AttachOp, acc::CreateOp, acc::CopyinOp>(
+ if (!mlir::isa<acc::AttachOp, acc::CreateOp, acc::CopyinOp, acc::MapInfoOp>(
operand.getDefiningOp()))
return emitError("expect data entry operation as defining op");
@@ -4650,8 +4670,8 @@ checkDeclareOperands(Op &op, const mlir::ValueRange &operands,
if (isa<BlockArgument>(operand) ||
!mlir::isa<acc::CopyinOp, acc::CopyoutOp, acc::CreateOp,
acc::DevicePtrOp, acc::GetDevicePtrOp, acc::PresentOp,
- acc::DeclareDeviceResidentOp, acc::DeclareLinkOp>(
- operand.getDefiningOp()))
+ acc::DeclareDeviceResidentOp, acc::DeclareLinkOp,
+ acc::MapInfoOp>(operand.getDefiningOp()))
return op.emitError(
"expect valid declare data entry operation or acc.getdeviceptr "
"as defining op");
@@ -4660,12 +4680,15 @@ checkDeclareOperands(Op &op, const mlir::ValueRange &operands,
assert(var && "declare operands can only be data entry operations which "
"must have var");
(void)var;
- std::optional<mlir::acc::DataClause> dataClauseOptional{
- getDataClause(operand.getDefiningOp())};
- assert(dataClauseOptional.has_value() &&
- "declare operands can only be data entry operations which must have "
- "dataClause");
- (void)dataClauseOptional;
+ // acc.map_info encodes the clause effects in mapFlags instead.
+ if (!mlir::isa<acc::MapInfoOp>(operand.getDefiningOp())) {
+ std::optional<mlir::acc::DataClause> dataClauseOptional{
+ getDataClause(operand.getDefiningOp())};
+ assert(dataClauseOptional.has_value() &&
+ "declare operands can only be data entry operations which must "
+ "have dataClause");
+ (void)dataClauseOptional;
+ }
}
return success();
@@ -5196,8 +5219,8 @@ LogicalResult acc::UpdateOp::verify() {
return failure();
for (mlir::Value operand : getDataClauseOperands())
- if (!mlir::isa<acc::UpdateDeviceOp, acc::UpdateHostOp, acc::GetDevicePtrOp>(
- operand.getDefiningOp()))
+ if (!mlir::isa<acc::UpdateDeviceOp, acc::UpdateHostOp, acc::GetDevicePtrOp,
+ acc::MapInfoOp>(operand.getDefiningOp()))
return emitError("expect data entry/exit operation or acc.getdeviceptr "
"as defining op");
@@ -5347,7 +5370,7 @@ mlir::acc::getVarPtr(mlir::Operation *accDataClauseOp) {
auto varPtr{llvm::TypeSwitch<mlir::Operation *,
mlir::TypedValue<mlir::acc::PointerLikeType>>(
accDataClauseOp)
- .Case<ACC_DATA_ENTRY_OPS>(
+ .Case<ACC_DATA_ENTRY_OPS, mlir::acc::MapInfoOp>(
[&](auto entry) { return entry.getVarPtr(); })
.Case<mlir::acc::CopyoutOp, mlir::acc::UpdateHostOp>(
[&](auto exit) { return exit.getVarPtr(); })
@@ -5358,16 +5381,16 @@ mlir::acc::getVarPtr(mlir::Operation *accDataClauseOp) {
}
mlir::Value mlir::acc::getVar(mlir::Operation *accDataClauseOp) {
- auto varPtr{
- llvm::TypeSwitch<mlir::Operation *, mlir::Value>(accDataClauseOp)
- .Case<ACC_DATA_ENTRY_OPS>([&](auto entry) { return entry.getVar(); })
- .Default([&](mlir::Operation *) { return mlir::Value(); })};
+ auto varPtr{llvm::TypeSwitch<mlir::Operation *, mlir::Value>(accDataClauseOp)
+ .Case<ACC_DATA_ENTRY_OPS, mlir::acc::MapInfoOp>(
+ [&](auto entry) { return entry.getVar(); })
+ .Default([&](mlir::Operation *) { return mlir::Value(); })};
return varPtr;
}
mlir::Type mlir::acc::getVarType(mlir::Operation *accDataClauseOp) {
auto varType{llvm::TypeSwitch<mlir::Operation *, mlir::Type>(accDataClauseOp)
- .Case<ACC_DATA_ENTRY_OPS>(
+ .Case<ACC_DATA_ENTRY_OPS, mlir::acc::MapInfoOp>(
[&](auto entry) { return entry.getVarType(); })
.Case<mlir::acc::CopyoutOp, mlir::acc::UpdateHostOp>(
[&](auto exit) { return exit.getVarType(); })
@@ -5377,29 +5400,31 @@ mlir::Type mlir::acc::getVarType(mlir::Operation *accDataClauseOp) {
mlir::TypedValue<mlir::acc::PointerLikeType>
mlir::acc::getAccPtr(mlir::Operation *accDataClauseOp) {
- auto accPtr{llvm::TypeSwitch<mlir::Operation *,
- mlir::TypedValue<mlir::acc::PointerLikeType>>(
- accDataClauseOp)
- .Case<ACC_DATA_ENTRY_OPS, ACC_DATA_EXIT_OPS>(
- [&](auto dataClause) { return dataClause.getAccPtr(); })
- .Default([&](mlir::Operation *) {
- return mlir::TypedValue<mlir::acc::PointerLikeType>();
- })};
+ auto accPtr{
+ llvm::TypeSwitch<mlir::Operation *,
+ mlir::TypedValue<mlir::acc::PointerLikeType>>(
+ accDataClauseOp)
+ .Case<ACC_DATA_ENTRY_OPS, ACC_DATA_EXIT_OPS, mlir::acc::MapInfoOp>(
+ [&](auto dataClause) { return dataClause.getAccPtr(); })
+ .Default([&](mlir::Operation *) {
+ return mlir::TypedValue<mlir::acc::PointerLikeType>();
+ })};
return accPtr;
}
mlir::Value mlir::acc::getAccVar(mlir::Operation *accDataClauseOp) {
- auto accPtr{llvm::TypeSwitch<mlir::Operation *, mlir::Value>(accDataClauseOp)
- .Case<ACC_DATA_ENTRY_OPS, ACC_DATA_EXIT_OPS>(
- [&](auto dataClause) { return dataClause.getAccVar(); })
- .Default([&](mlir::Operation *) { return mlir::Value(); })};
+ auto accPtr{
+ llvm::TypeSwitch<mlir::Operation *, mlir::Value>(accDataClauseOp)
+ .Case<ACC_DATA_ENTRY_OPS, ACC_DATA_EXIT_OPS, mlir::acc::MapInfoOp>(
+ [&](auto dataClause) { return dataClause.getAccVar(); })
+ .Default([&](mlir::Operation *) { return mlir::Value(); })};
return accPtr;
}
mlir::Value mlir::acc::getVarPtrPtr(mlir::Operation *accDataClauseOp) {
auto varPtrPtr{
llvm::TypeSwitch<mlir::Operation *, mlir::Value>(accDataClauseOp)
- .Case<ACC_DATA_ENTRY_OPS>(
+ .Case<ACC_DATA_ENTRY_OPS, mlir::acc::MapInfoOp>(
[&](auto dataClause) { return dataClause.getVarPtrPtr(); })
.Default([&](mlir::Operation *) { return mlir::Value(); })};
return varPtrPtr;
@@ -5410,10 +5435,12 @@ mlir::acc::getBounds(mlir::Operation *accDataClauseOp) {
mlir::SmallVector<mlir::Value> bounds{
llvm::TypeSwitch<mlir::Operation *, mlir::SmallVector<mlir::Value>>(
accDataClauseOp)
- .Case<ACC_DATA_ENTRY_OPS, ACC_DATA_EXIT_OPS>([&](auto dataClause) {
- return mlir::SmallVector<mlir::Value>(
- dataClause.getBounds().begin(), dataClause.getBounds().end());
- })
+ .Case<ACC_DATA_ENTRY_OPS, ACC_DATA_EXIT_OPS, mlir::acc::MapInfoOp>(
+ [&](auto dataClause) {
+ return mlir::SmallVector<mlir::Value>(
+ dataClause.getBounds().begin(),
+ dataClause.getBounds().end());
+ })
.Default([&](mlir::Operation *) {
return mlir::SmallVector<mlir::Value, 0>();
})};
@@ -5453,7 +5480,8 @@ mlir::ArrayAttr mlir::acc::getAsyncOnly(mlir::Operation *accDataClauseOp) {
std::optional<llvm::StringRef> mlir::acc::getVarName(mlir::Operation *accOp) {
auto name{
llvm::TypeSwitch<mlir::Operation *, std::optional<llvm::StringRef>>(accOp)
- .Case<ACC_DATA_ENTRY_OPS>([&](auto entry) { return entry.getName(); })
+ .Case<ACC_DATA_ENTRY_OPS, mlir::acc::MapInfoOp>(
+ [&](auto entry) { return entry.getName(); })
.Default([&](mlir::Operation *) -> std::optional<llvm::StringRef> {
return {};
})};
@@ -5472,17 +5500,20 @@ mlir::acc::getDataClause(mlir::Operation *accDataEntryOp) {
}
bool mlir::acc::getImplicitFlag(mlir::Operation *accDataEntryOp) {
- auto implicit{llvm::TypeSwitch<mlir::Operation *, bool>(accDataEntryOp)
- .Case<ACC_DATA_ENTRY_OPS>(
- [&](auto entry) { return entry.getImplicit(); })
- .Default([&](mlir::Operation *) { return false; })};
- return implicit;
+ return llvm::TypeSwitch<mlir::Operation *, bool>(accDataEntryOp)
+ .Case<ACC_DATA_ENTRY_OPS>([&](auto entry) { return entry.getImplicit(); })
+ .Case<mlir::acc::MapInfoOp>([&](auto mapInfo) {
+ return bitEnumContainsAny(mapInfo.getMapFlags(),
+ mlir::acc::MapFlags::implicit);
+ })
+ .Default([&](mlir::Operation *) { return false; });
}
mlir::ValueRange mlir::acc::getDataOperands(mlir::Operation *accOp) {
auto dataOperands{
llvm::TypeSwitch<mlir::Operation *, mlir::ValueRange>(accOp)
- .Case<ACC_COMPUTE_AND_DATA_CONSTRUCT_OPS>(
+ .Case<ACC_COMPUTE_AND_DATA_CONSTRUCT_OPS,
+ mlir::acc::KernelEnvironmentOp>(
[&](auto entry) { return entry.getDataClauseOperands(); })
.Default([&](mlir::Operation *) { return mlir::ValueRange(); })};
return dataOperands;
@@ -5492,7 +5523,8 @@ mlir::MutableOperandRange
mlir::acc::getMutableDataOperands(mlir::Operation *accOp) {
auto dataOperands{
llvm::TypeSwitch<mlir::Operation *, mlir::MutableOperandRange>(accOp)
- .Case<ACC_COMPUTE_AND_DATA_CONSTRUCT_OPS>(
+ .Case<ACC_COMPUTE_AND_DATA_CONSTRUCT_OPS,
+ mlir::acc::KernelEnvironmentOp>(
[&](auto entry) { return entry.getDataClauseOperandsMutable(); })
.Default([&](mlir::Operation *) { return nullptr; })};
return dataOperands;
diff --git a/mlir/lib/Dialect/OpenACC/IR/OpenACCCG.cpp b/mlir/lib/Dialect/OpenACC/IR/OpenACCCG.cpp
index 7db3fefd01e24..16af8771546c1 100644
--- a/mlir/lib/Dialect/OpenACC/IR/OpenACCCG.cpp
+++ b/mlir/lib/Dialect/OpenACC/IR/OpenACCCG.cpp
@@ -918,6 +918,39 @@ LogicalResult PredicateRegionOp::verify() {
return success();
}
+//===----------------------------------------------------------------------===//
+// MapInfoOp
+//===----------------------------------------------------------------------===//
+
+LogicalResult MapInfoOp::verify() {
+ // A pointer-like var addresses the mapped object, so varType has to name that
+ // object rather than the address of it.
+ if (mlir::isa<acc::PointerLikeType>(getVar().getType()) &&
+ getVarType() == getVar().getType())
+ return emitOpError("varType must capture the element type of var");
+
+ // A descriptor operand is only meaningful together with the layout it
+ // follows. Without a kind, consumers have no way to interpret it.
+ if (getDesc() && getDescKind() == DataDescKind::none)
+ return emitOpError("desc requires a descKind other than none");
+
+ // Bounds count elements of the mapped object, which is the OpenACC
+ // descriptor layout, so it must be among the kinds named here.
+ if (!getBounds().empty() &&
+ !acc::bitEnumContainsAny(getDescKind(), DataDescKind::openacc))
+ return emitOpError("bounds require descKind openacc");
+
+ // Anything below -1 has no meaning: -1 states that the size is unknown at
+ // compile time and 0 defers it to bounds or to a descriptor.
+ if (getSize()) {
+ std::optional<int64_t> constantSize = getConstantIntValue(getSize());
+ if (constantSize && *constantSize < -1)
+ return emitOpError("size must be -1, 0, or a positive byte count");
+ }
+
+ return success();
+}
+
//===----------------------------------------------------------------------===//
// GPUParallelDimAttr
//===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsCG.cpp b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsCG.cpp
index bb7e886f387bc..1bdce5068567f 100644
--- a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsCG.cpp
+++ b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsCG.cpp
@@ -464,5 +464,307 @@ std::optional<int64_t> getPrivateLocalSharedMemoryUpperBoundBytes(
numCopies->value();
}
+bool hasAttachPoint(Operation *mapEntryOp) {
+ if (!mapEntryOp)
+ return false;
+ if (auto mapInfo = dyn_cast<MapInfoOp>(mapEntryOp))
+ return mapInfo.getVarPtrPtr() != nullptr;
+ if (isa<AttachOp>(mapEntryOp))
+ return true;
+ if (std::optional<DataClause> clause = getDataClause(mapEntryOp)) {
+ if (*clause == DataClause::acc_attach || *clause == DataClause::acc_detach)
+ return true;
+ }
+ return getVarPtrPtr(mapEntryOp) != nullptr;
+}
+
+DataDescKind getDataDescKind(Operation *mapEntryOp) {
+ if (auto mapInfo = dyn_cast<MapInfoOp>(mapEntryOp))
+ return mapInfo.getDescKind();
+ return DataDescKind::none;
+}
+
+Value getDesc(Operation *mapEntryOp) {
+ auto mapInfo = dyn_cast<MapInfoOp>(mapEntryOp);
+ if (!mapInfo)
+ return {};
+ if (Value desc = mapInfo.getDesc())
+ return desc;
+ // When the mapped var is itself the descriptor, map_info omits a redundant
+ // `desc` operand; recover it from `var` whenever a descriptor kind is set.
+ if (mapInfo.getDescKind() != DataDescKind::none)
+ return mapInfo.getVar();
+ return {};
+}
+
+std::optional<int64_t> getMapElementSize(Operation *mapEntryOp) {
+ if (auto mapInfo = dyn_cast<MapInfoOp>(mapEntryOp))
+ if (auto attr = mapInfo.getElementSizeAttr())
+ return attr.getInt();
+ return std::nullopt;
+}
+
+Value getMapSize(Operation *mapEntryOp) {
+ if (auto mapInfo = dyn_cast<MapInfoOp>(mapEntryOp))
+ return mapInfo.getSize();
+ return {};
+}
+
+std::optional<MapFlags> getMapFlags(Operation *mapEntryOp) {
+ if (auto mapInfo = dyn_cast<MapInfoOp>(mapEntryOp))
+ return mapInfo.getMapFlags();
+ return std::nullopt;
+}
+
+SmallVector<Operation *> getPairedDataExitOps(Value entryResult) {
+ SmallVector<Operation *> exitOps;
+ for (OpOperand &use : entryResult.getUses()) {
+ Operation *op = use.getOwner();
+ if (!isa<ACC_DATA_EXIT_OPS>(op))
+ continue;
+ Value accVar;
+ llvm::TypeSwitch<Operation *>(op).Case<ACC_DATA_EXIT_OPS>(
+ [&](auto exit) { accVar = exit.getAccVar(); });
+ // The entry result can also be used as another operand, such as async.
+ if (accVar == entryResult)
+ exitOps.push_back(op);
+ }
+ return exitOps;
+}
+
+static Operation *findCorrespondingDataExit(Value entryResult) {
+ SmallVector<Operation *> exitOps = getPairedDataExitOps(entryResult);
+ return exitOps.empty() ? nullptr : exitOps.front();
+}
+
+static std::optional<DataClause> getExitDataClause(Operation *exitOp) {
+ return llvm::TypeSwitch<Operation *, std::optional<DataClause>>(exitOp)
+ .Case<ACC_DATA_EXIT_OPS>([&](auto exit) { return exit.getDataClause(); })
+ .Default([&](Operation *) { return std::nullopt; });
+}
+
+bool hasCopyOutSibling(Operation *entryOp) {
+ auto getMappedVar = [](Operation *op) {
+ Value var = getVar(op);
+ return var ? var : getVarPtr(op);
+ };
+ Value var = getMappedVar(entryOp);
+ if (!var)
+ return false;
+
+ auto copiesOutOnly = [&](Value sibling) {
+ Operation *siblingOp = sibling.getDefiningOp();
+ if (!siblingOp || getMappedVar(siblingOp) != var)
+ return false;
+ if (std::optional<MapFlags> siblingFlags = getMapFlags(siblingOp))
+ return bitEnumContainsAny(*siblingFlags, MapFlags::from) &&
+ !bitEnumContainsAny(*siblingFlags, MapFlags::to);
+ std::optional<DataClause> siblingClause = getDataClause(siblingOp);
+ return siblingClause && (*siblingClause == DataClause::acc_copyout ||
+ *siblingClause == DataClause::acc_copyout_zero);
+ };
+
+ Value entryResult = entryOp->getResult(0);
+ auto mapsSameVarOnConstruct = [&](auto construct) {
+ return llvm::any_of(construct.getDataClauseOperands(), [&](Value sibling) {
+ return sibling != entryResult && copiesOutOnly(sibling);
+ });
+ };
+ return llvm::any_of(entryResult.getUsers(), [&](Operation *user) {
+ return llvm::TypeSwitch<Operation *, bool>(user)
+ .Case<KernelEnvironmentOp, KernelsOp, ParallelOp, SerialOp, DataOp>(
+ mapsSameVarOnConstruct)
+ .Default(false);
+ });
+}
+
+static DataClauseModifier getEntryModifiers(Operation *entryOp) {
+ return llvm::TypeSwitch<Operation *, DataClauseModifier>(entryOp)
+ .Case<ACC_DATA_ENTRY_OPS>(
+ [&](auto entry) { return entry.getModifiers(); })
+ .Default([&](Operation *) { return DataClauseModifier::none; });
+}
+
+MapFlags computePrivatizeMapFlags(PrivatizeOp privatizeOp,
+ const ACCToGPUMappingPolicy &policy) {
+ MapFlags flags = MapFlags::private_;
+
+ // Storage is private without being replicated per parallel level when the
+ // privatization does not name any parallel dimension.
+ GPUParallelDimsAttr parDims = privatizeOp.getParDimsAttr();
+ if (!parDims)
+ return flags;
+
+ for (GPUParallelDimAttr parDim : parDims.getArray()) {
+ if (policy.isGang(parDim))
+ flags = flags | MapFlags::gang_private;
+ else if (policy.isWorker(parDim))
+ flags = flags | MapFlags::worker_private;
+ else if (policy.isVector(parDim))
+ flags = flags | MapFlags::vector_private;
+ }
+ return flags;
+}
+
+MapFlags computeDataClauseMapFlags(Operation *entryOp, bool ptrAndObj) {
+ MapFlags flags = MapFlags::none;
+ std::optional<DataClause> enterClause = getDataClause(entryOp);
+ if (!enterClause)
+ return flags;
+
+ switch (*enterClause) {
+ case DataClause::acc_create:
+ case DataClause::acc_copyout:
+ case DataClause::acc_present:
+ case DataClause::acc_private:
+ case DataClause::acc_firstprivate:
+ case DataClause::acc_delete:
+ case DataClause::acc_update_host:
+ case DataClause::acc_update_self:
+ case DataClause::acc_declare_device_resident:
+ if (*enterClause == DataClause::acc_declare_device_resident)
+ flags = flags | MapFlags::device_resident;
+ if (*enterClause == DataClause::acc_present)
+ flags = flags | MapFlags::present;
+ if (*enterClause == DataClause::acc_private ||
+ *enterClause == DataClause::acc_firstprivate)
+ flags = flags | MapFlags::private_;
+ if (*enterClause == DataClause::acc_firstprivate)
+ flags = flags | MapFlags::to;
+ break;
+ case DataClause::acc_deviceptr:
+ flags = flags | MapFlags::devptr;
+ break;
+ case DataClause::acc_create_zero:
+ case DataClause::acc_copyout_zero:
+ flags = flags | MapFlags::init_zero;
+ break;
+ case DataClause::acc_copy:
+ case DataClause::acc_copyin:
+ case DataClause::acc_copyin_readonly:
+ case DataClause::acc_reduction:
+ case DataClause::acc_update_device:
+ flags = flags | MapFlags::to;
+ break;
+ case DataClause::acc_no_create:
+ flags = flags | MapFlags::no_create;
+ break;
+ case DataClause::acc_attach:
+ break;
+ default:
+ break;
+ }
+ if (*enterClause == DataClause::acc_reduction)
+ flags = flags | MapFlags::reduction;
+
+ std::optional<DataClause> exitClause;
+ if (Operation *exitOp = findCorrespondingDataExit(entryOp->getResult(0)))
+ exitClause = getExitDataClause(exitOp);
+ if (exitClause) {
+ switch (*exitClause) {
+ case DataClause::acc_copy:
+ case DataClause::acc_reduction:
+ case DataClause::acc_copyout:
+ case DataClause::acc_copyout_zero:
+ case DataClause::acc_update_host:
+ case DataClause::acc_update_self:
+ flags = flags | MapFlags::from;
+ break;
+ case DataClause::acc_declare_device_resident:
+ flags = flags | MapFlags::device_resident;
+ break;
+ case DataClause::acc_present:
+ flags = flags | MapFlags::present;
+ break;
+ // `delete` only decrements the dynamic reference counter, so it must not
+ // request a forced unmap: the device copy has to survive while an
+ // enclosing region still references it. Only `finalize` zeroes the counter,
+ // and that is handled from the exit_data op below.
+ case DataClause::acc_delete:
+ break;
+ // An exit that repeats its entry clause only releases the device copy.
+ case DataClause::acc_create:
+ case DataClause::acc_create_zero:
+ case DataClause::acc_copyin:
+ case DataClause::acc_copyin_readonly:
+ if (hasCopyOutSibling(entryOp))
+ flags = flags | MapFlags::from;
+ break;
+ default:
+ break;
+ }
+ if (*exitClause == DataClause::acc_reduction)
+ flags = flags | MapFlags::reduction;
+ }
+
+ if (ptrAndObj)
+ flags = flags | MapFlags::ptr_and_obj;
+ if (getImplicitFlag(entryOp))
+ flags = flags | MapFlags::implicit;
+ if (bitEnumContainsAny(getEntryModifiers(entryOp), DataClauseModifier::zero))
+ flags = flags | MapFlags::init_zero;
+
+ for (OpOperand &use : entryOp->getResult(0).getUses()) {
+ if (auto exitDataOp = dyn_cast<ExitDataOp>(use.getOwner())) {
+ if (exitDataOp.getFinalize())
+ flags = flags | MapFlags::delete_;
+ }
+ if (auto updateOp = dyn_cast<UpdateOp>(use.getOwner())) {
+ if (updateOp.getIfPresent())
+ flags = flags | MapFlags::if_present;
+ }
+ }
+
+ return flags;
+}
+
+int64_t computeMapInfoSizeBytes(Value var, Type varType, DataDescKind descKind,
+ ValueRange bounds, const DataLayout &dataLayout,
+ OpenACCSupport *support) {
+ // Bounds-driven and descriptor-driven maps report size 0: the extents and
+ // element size already state the size, and restating it here could disagree.
+ if (!bounds.empty() || descKind != DataDescKind::none)
+ return 0;
+
+ ModuleOp module;
+ if (Operation *def = var.getDefiningOp())
+ module = def->getParentOfType<ModuleOp>();
+ else if (Region *region = var.getParentRegion())
+ if (Operation *parent = region->getParentOp())
+ module = parent->getParentOfType<ModuleOp>();
+ if (!module)
+ return -1;
+
+ auto tryUtilsSize = [&](Type ty) -> std::optional<int64_t> {
+ std::optional<TypeSizeAndAlignment> sizeAndAlign =
+ getTypeSizeAndAlignment(ty, module, dataLayout, support, var);
+ if (!sizeAndAlign || sizeAndAlign->first.isScalable())
+ return std::nullopt;
+ return static_cast<int64_t>(sizeAndAlign->first.getFixedValue());
+ };
+ if (std::optional<int64_t> size = tryUtilsSize(varType))
+ return *size;
+ if (std::optional<int64_t> size = tryUtilsSize(var.getType()))
+ return *size;
+
+ return -1;
+}
+
+void populateSourceExtents(ValueRange bounds, ArrayRef<int64_t> shape,
+ OpBuilder &builder) {
+ if (shape.size() != bounds.size())
+ return;
+ for (auto [boundValue, extent] : llvm::zip_equal(bounds, shape)) {
+ auto bound = boundValue.getDefiningOp<DataBoundsOp>();
+ if (!bound || bound.getSourceExtent() || extent < 0)
+ continue;
+ OpBuilder::InsertionGuard guard(builder);
+ builder.setInsertionPoint(bound);
+ Value sourceExtent =
+ arith::ConstantIndexOp::create(builder, bound.getLoc(), extent);
+ bound.getSourceExtentMutable().assign(sourceExtent);
+ }
+}
+
} // namespace acc
} // namespace mlir
diff --git a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsType.cpp b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsType.cpp
index debffac22e84a..617598a0ccd27 100644
--- a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsType.cpp
+++ b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsType.cpp
@@ -30,7 +30,7 @@ getTypeSizeAndAlignmentHelper(Type ty, ModuleOp module, const DataLayout &dl,
std::optional<TypeSizeAndAlignment>
getTypeSizeAndAlignment(Type ty, ModuleOp module, const DataLayout &dl,
- OpenACCSupport *support) {
+ OpenACCSupport *support, Value var) {
if (ty.isIntOrIndexOrFloat() ||
isa<ComplexType, VectorType, DataLayoutTypeInterface>(ty))
return TypeSizeAndAlignment{
@@ -79,15 +79,30 @@ getTypeSizeAndAlignment(Type ty, ModuleOp module, const DataLayout &dl,
return getTypeSizeAndAlignmentHelper(
LLVM::LLVMPointerType::get(ty.getContext()), module, dl, support);
+ // Mapped-object size for MappableType when a value is available.
+ if (var) {
+ if (auto mappableTy = dyn_cast<MappableType>(ty)) {
+ std::optional<llvm::TypeSize> size =
+ mappableTy.getSizeInBytes(var, /*accBounds=*/{}, dl);
+ if (!size || size->isScalable())
+ return std::nullopt;
+ llvm::TypeSize alignment = llvm::TypeSize::getFixed(1);
+ if (ty.isIntOrIndexOrFloat() || isa<DataLayoutTypeInterface>(ty))
+ alignment = llvm::TypeSize::getFixed(dl.getTypeABIAlignment(ty));
+ return TypeSizeAndAlignment{*size, alignment};
+ }
+ }
+
return std::nullopt;
}
std::optional<TypeSizeAndAlignment>
-getTypeSizeAndAlignment(Type ty, ModuleOp module, OpenACCSupport *support) {
+getTypeSizeAndAlignment(Type ty, ModuleOp module, OpenACCSupport *support,
+ Value var) {
std::optional<DataLayout> dl = getDataLayout(module);
if (!dl)
return std::nullopt;
- return getTypeSizeAndAlignment(ty, module, *dl, support);
+ return getTypeSizeAndAlignment(ty, module, *dl, support, var);
}
Value castPointerLikeTypeIfNeeded(OpBuilder &builder, Location loc, Value value,
diff --git a/mlir/test/Dialect/OpenACC/invalid-cg-mapinfo.mlir b/mlir/test/Dialect/OpenACC/invalid-cg-mapinfo.mlir
new file mode 100644
index 0000000000000..b2ee455e5bde6
--- /dev/null
+++ b/mlir/test/Dialect/OpenACC/invalid-cg-mapinfo.mlir
@@ -0,0 +1,44 @@
+// RUN: mlir-opt -split-input-file -verify-diagnostics %s
+
+// -----
+
+func.func @map_info_var_type_is_pointer(%a: memref<10xf32>) {
+ // expected-error at +1 {{'acc.map_info' op varType must capture the element type of var}}
+ %map = acc.map_info varPtr(%a : memref<10xf32>) varType(memref<10xf32>)
+ descKind(none) mapFlags(to) -> memref<10xf32>
+ return
+}
+
+// -----
+
+func.func @map_info_desc_without_kind(%a: memref<10xf32>, %desc: memref<i64>) {
+ // expected-error at +1 {{'acc.map_info' op desc requires a descKind other than none}}
+ %map = acc.map_info varPtr(%a : memref<10xf32>) varType(tensor<10xf32>)
+ desc(%desc : memref<i64>) descKind(none) mapFlags(to) -> memref<10xf32>
+ return
+}
+
+// -----
+
+func.func @map_info_bounds_without_openacc_kind(%a: memref<?xf32>) {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %c4 = arith.constant 4 : index
+ %bounds = acc.bounds lowerbound(%c0 : index) upperbound(%c4 : index)
+ extent(%c4 : index) stride(%c1 : index) startIdx(%c0 : index)
+ // expected-error at +1 {{'acc.map_info' op bounds require descKind openacc}}
+ %map = acc.map_info varPtr(%a : memref<?xf32>) varType(tensor<?xf32>)
+ bounds(%bounds) elementSize(4) descKind(cfi) mapFlags(to)
+ -> memref<?xf32>
+ return
+}
+
+// -----
+
+func.func @map_info_negative_size(%a: memref<10xf32>) {
+ %size = arith.constant -2 : i64
+ // expected-error at +1 {{'acc.map_info' op size must be -1, 0, or a positive byte count}}
+ %map = acc.map_info varPtr(%a : memref<10xf32>) varType(tensor<10xf32>)
+ size(%size : i64) descKind(none) mapFlags(to) -> memref<10xf32>
+ return
+}
diff --git a/mlir/test/Dialect/OpenACC/ops-cg-mapinfo.mlir b/mlir/test/Dialect/OpenACC/ops-cg-mapinfo.mlir
new file mode 100644
index 0000000000000..d63e9fe4d01f0
--- /dev/null
+++ b/mlir/test/Dialect/OpenACC/ops-cg-mapinfo.mlir
@@ -0,0 +1,143 @@
+// RUN: mlir-opt -split-input-file %s | FileCheck %s --check-prefixes=CHECK
+// Verify the printed output can be parsed.
+// RUN: mlir-opt -split-input-file %s | mlir-opt -split-input-file | FileCheck %s --check-prefixes=CHECK
+// Verify the generic form can be parsed.
+// RUN: mlir-opt -split-input-file -mlir-print-op-generic %s | mlir-opt -split-input-file | FileCheck %s --check-prefixes=CHECK
+
+// -----
+
+// The whole object is mapped, so its byte size is stated on the operation and
+// no descriptor is involved.
+// CHECK-LABEL: func @map_info_whole_object
+func.func @map_info_whole_object(%a: memref<10xf32>) {
+ %size = arith.constant 40 : i64
+ %map = acc.map_info varPtr(%a : memref<10xf32>) varType(tensor<10xf32>)
+ size(%size : i64) elementSize(4) name("a")
+ descKind(none) mapFlags(to, from) -> memref<10xf32>
+ acc.data dataOperands(%map : memref<10xf32>) {
+ acc.terminator
+ }
+ return
+}
+// CHECK: %[[SIZE:.*]] = arith.constant 40 : i64
+// CHECK: acc.map_info varPtr(%{{.*}} : memref<10xf32>) varType(tensor<10xf32>)
+// CHECK-SAME: size(%[[SIZE]] : i64)
+// CHECK-SAME: elementSize(4)
+// CHECK-SAME: name("a")
+// CHECK-SAME: descKind(none)
+// CHECK-SAME: mapFlags(to,from)
+// CHECK-SAME: -> memref<10xf32>
+
+// -----
+
+// A partial array map carries bounds instead of a byte size, and the source
+// extent records how large the whole array is so a strided transfer can be
+// described.
+// CHECK-LABEL: func @map_info_bounds
+func.func @map_info_bounds(%a: memref<?xf32>) {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %c4 = arith.constant 4 : index
+ %c10 = arith.constant 10 : index
+ %size = arith.constant 0 : i64
+ %bounds = acc.bounds lowerbound(%c0 : index) upperbound(%c4 : index)
+ extent(%c4 : index) stride(%c1 : index) startIdx(%c0 : index)
+ sourceExtent(%c10 : index)
+ %map = acc.map_info varPtr(%a : memref<?xf32>) varType(tensor<?xf32>)
+ bounds(%bounds) size(%size : i64) elementSize(4)
+ descKind(openacc) mapFlags(to) -> memref<?xf32>
+ acc.data dataOperands(%map : memref<?xf32>) {
+ acc.terminator
+ }
+ return
+}
+// CHECK: %[[BOUNDS:.*]] = acc.bounds
+// CHECK-SAME: sourceExtent(%{{[^)]*}})
+// CHECK: acc.map_info varPtr(%{{.*}} : memref<?xf32>) varType(tensor<?xf32>)
+// CHECK-SAME: bounds(%[[BOUNDS]])
+// CHECK-SAME: descKind(openacc)
+// CHECK-SAME: mapFlags(to)
+
+// -----
+
+// A descriptor-backed map names the attach point and the descriptor that holds
+// it, and defers the mapped size to that descriptor.
+// CHECK-LABEL: func @map_info_descriptor
+func.func @map_info_descriptor(%a: memref<10xf32>, %slot: memref<i64>,
+ %desc: memref<i64>) {
+ %size = arith.constant 0 : i64
+ %map = acc.map_info varPtr(%a : memref<10xf32>) varType(tensor<10xf32>)
+ varPtrPtr(%slot : memref<i64>) desc(%desc : memref<i64>)
+ size(%size : i64) name("p") exitLoc(loc("p.f90":7:3))
+ descKind(cfi) mapFlags(to, from, ptr_and_obj) -> memref<10xf32>
+ acc.data dataOperands(%map : memref<10xf32>) {
+ acc.terminator
+ }
+ return
+}
+// CHECK: acc.map_info varPtr(%{{[^)]*}}) varType(tensor<10xf32>)
+// CHECK-SAME: varPtrPtr(%{{[^)]*}})
+// CHECK-SAME: desc(%{{[^)]*}})
+// CHECK-SAME: exitLoc(
+// CHECK-SAME: descKind(cfi)
+// CHECK-SAME: mapFlags(to,from,ptr_and_obj)
+
+// -----
+
+// A nested descriptor names every layout that describes the object.
+// CHECK-LABEL: func @map_info_nested_desc_kind
+func.func @map_info_nested_desc_kind(%a: memref<?xf32>, %desc: memref<i64>) {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %c4 = arith.constant 4 : index
+ %bounds = acc.bounds lowerbound(%c0 : index) upperbound(%c4 : index)
+ extent(%c4 : index) stride(%c1 : index) startIdx(%c0 : index)
+ %map = acc.map_info varPtr(%a : memref<?xf32>) varType(tensor<?xf32>)
+ desc(%desc : memref<i64>) bounds(%bounds) elementSize(4)
+ descKind(cfi, openacc) mapFlags(to) -> memref<?xf32>
+ acc.data dataOperands(%map : memref<?xf32>) {
+ acc.terminator
+ }
+ return
+}
+// CHECK: acc.map_info
+// CHECK-SAME: descKind(cfi,openacc)
+
+// -----
+
+// A size that is not known at compile time is stated as -1, and a size that is
+// only known at run time is supplied as a value.
+// CHECK-LABEL: func @map_info_unknown_and_runtime_size
+func.func @map_info_unknown_and_runtime_size(%a: memref<10xf32>,
+ %runtime: i64) {
+ %unknown = arith.constant -1 : i64
+ %map = acc.map_info varPtr(%a : memref<10xf32>) varType(tensor<10xf32>)
+ size(%unknown : i64) descKind(none) mapFlags(to) -> memref<10xf32>
+ %dynamic = acc.map_info varPtr(%a : memref<10xf32>) varType(tensor<10xf32>)
+ size(%runtime : i64) descKind(none) mapFlags(to) -> memref<10xf32>
+ acc.data dataOperands(%map, %dynamic : memref<10xf32>, memref<10xf32>) {
+ acc.terminator
+ }
+ return
+}
+// CHECK: %[[UNKNOWN:.*]] = arith.constant -1 : i64
+// CHECK: acc.map_info
+// CHECK-SAME: size(%[[UNKNOWN]] : i64)
+// CHECK: acc.map_info
+// CHECK-SAME: size(%{{[^)]*}})
+
+// -----
+
+// A map with no flags describes an entry whose transfer behavior is decided by
+// the construct that uses it.
+// CHECK-LABEL: func @map_info_no_flags
+func.func @map_info_no_flags(%a: memref<10xf32>) {
+ %map = acc.map_info varPtr(%a : memref<10xf32>) varType(tensor<10xf32>)
+ descKind(none) mapFlags(none) -> memref<10xf32>
+ %token = acc.declare_enter dataOperands(%map : memref<10xf32>)
+ acc.declare_exit token(%token) dataOperands(%map : memref<10xf32>)
+ return
+}
+// CHECK: acc.map_info varPtr(%{{[^)]*}}) varType(tensor<10xf32>)
+// CHECK-SAME: descKind(none)
+// CHECK-SAME: mapFlags(none)
diff --git a/mlir/test/Dialect/OpenACC/ops.mlir b/mlir/test/Dialect/OpenACC/ops.mlir
index faae027158df5..8eff15138bdee 100644
--- a/mlir/test/Dialect/OpenACC/ops.mlir
+++ b/mlir/test/Dialect/OpenACC/ops.mlir
@@ -1420,6 +1420,13 @@ func.func @teststructureddataclauseops(%a: memref<10xf32>, %b: memref<memref<10x
}
acc.copyout accPtr(%copyinpartial : memref<10xf32>) bounds(%bounds1partial) to varPtr(%a : memref<10xf32>) varType(tensor<10xf32>) dataClause(acc_copy)
+ // A slice of a larger array records how far apart consecutive slice elements
+ // are in the original array.
+ %bounds1source = acc.bounds lowerbound(%c4 : index) upperbound(%c9 : index) extent(%c4 : index) sourceExtent(%c10 : index) stride(%c1 : index) startIdx(%c0 : index)
+ %copyinsource = acc.copyin varPtr(%a : memref<10xf32>) varType(tensor<10xf32>) bounds(%bounds1source) -> memref<10xf32>
+ acc.parallel dataOperands(%copyinsource : memref<10xf32>) {
+ }
+
return
}
@@ -1462,6 +1469,7 @@ func.func @teststructureddataclauseops(%a: memref<10xf32>, %b: memref<memref<10x
// CHECK-DAG: [[CON1:%.*]] = arith.constant 1 : index
// CHECK-DAG: [[CON4:%.*]] = arith.constant 4 : index
// CHECK-DAG: [[CON9:%.*]] = arith.constant 9 : index
+// CHECK-DAG: [[CON10:%.*]] = arith.constant 10 : index
// CHECK-DAG: [[CON20:%.*]] = arith.constant 20 : index
// CHECK: [[BOUNDS1F:%.*]] = acc.bounds lowerbound([[CON0]] : index) upperbound([[CON9]] : index) stride([[CON1]] : index)
// CHECK-NEXT: [[COPYINF1:%.*]] = acc.copyin varPtr([[ARGA]] : memref<10xf32>) varType(tensor<10xf32>) bounds([[BOUNDS1F]]) name("arrayA[0:9]") -> memref<10xf32>
@@ -1474,6 +1482,10 @@ func.func @teststructureddataclauseops(%a: memref<10xf32>, %b: memref<memref<10x
// CHECK-NEXT: acc.parallel dataOperands([[COPYINPART]] : memref<10xf32>) {
// CHECK-NEXT: }
// CHECK-NEXT: acc.copyout accPtr([[COPYINPART]] : memref<10xf32>) bounds([[BOUNDS1P]]) to varPtr([[ARGA]] : memref<10xf32>) varType(tensor<10xf32>) dataClause(acc_copy)
+// CHECK-NEXT: [[BOUNDS1S:%.*]] = acc.bounds lowerbound([[CON4]] : index) upperbound([[CON9]] : index) extent([[CON4]] : index) sourceExtent([[CON10]] : index) stride([[CON1]] : index) startIdx([[CON0]] : index)
+// CHECK-NEXT: [[COPYINSRC:%.*]] = acc.copyin varPtr([[ARGA]] : memref<10xf32>) varType(tensor<10xf32>) bounds([[BOUNDS1S]]) -> memref<10xf32>
+// CHECK-NEXT: acc.parallel dataOperands([[COPYINSRC]] : memref<10xf32>) {
+// CHECK-NEXT: }
// -----
More information about the flang-commits
mailing list