| //===- AliasAnalysis.cpp - Alias Analysis 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 |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "flang/Optimizer/Analysis/AliasAnalysis.h" |
| #include "flang/Optimizer/Dialect/CUF/CUFOps.h" |
| #include "flang/Optimizer/Dialect/FIROps.h" |
| #include "flang/Optimizer/Dialect/FIROpsSupport.h" |
| #include "flang/Optimizer/Dialect/FIRType.h" |
| #include "flang/Optimizer/Dialect/FortranVariableInterface.h" |
| #include "flang/Optimizer/HLFIR/HLFIROps.h" |
| #include "flang/Optimizer/Support/InternalNames.h" |
| #include "flang/Optimizer/Support/Utils.h" |
| #include "mlir/Analysis/AliasAnalysis.h" |
| #include "mlir/Dialect/OpenACC/OpenACC.h" |
| #include "mlir/Dialect/OpenACC/OpenACCUtils.h" |
| #include "mlir/Dialect/OpenMP/OpenMPDialect.h" |
| #include "mlir/Dialect/OpenMP/OpenMPInterfaces.h" |
| #include "mlir/IR/BuiltinOps.h" |
| #include "mlir/IR/Value.h" |
| #include "mlir/Interfaces/SideEffectInterfaces.h" |
| #include "llvm/ADT/TypeSwitch.h" |
| #include "llvm/Support/Casting.h" |
| #include "llvm/Support/CommandLine.h" |
| #include "llvm/Support/Debug.h" |
| #include <optional> |
| |
| using namespace mlir; |
| |
| #define DEBUG_TYPE "fir-alias-analysis" |
| |
| llvm::cl::opt<bool> supportCrayPointers( |
| "unsafe-cray-pointers", |
| llvm::cl::desc("Support Cray POINTERs that ALIAS with non-TARGET data"), |
| llvm::cl::init(false)); |
| |
| // Inspect for value-scoped Allocate effects and determine whether |
| // 'result' is a new allocation. Returns SourceKind::Allocate if a |
| // MemAlloc effect is attached |
| static fir::AliasAnalysis::SourceKind |
| classifyAllocateFromEffects(OpResult result) { |
| std::optional<bool> isNewAllocation = fir::isNewAllocationResult(result); |
| return isNewAllocation.value_or(false) |
| ? fir::AliasAnalysis::SourceKind::Allocate |
| : fir::AliasAnalysis::SourceKind::Unknown; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // AliasAnalysis: alias |
| //===----------------------------------------------------------------------===// |
| |
| static fir::AliasAnalysis::Source::Attributes |
| getAttrsFromVariable(fir::FortranVariableOpInterface var) { |
| fir::AliasAnalysis::Source::Attributes attrs; |
| if (var.isTarget()) |
| attrs.set(fir::AliasAnalysis::Attribute::Target); |
| if (var.isPointer()) |
| attrs.set(fir::AliasAnalysis::Attribute::Pointer); |
| if (var.isIntentIn()) |
| attrs.set(fir::AliasAnalysis::Attribute::IntentIn); |
| if (var.isCrayPointer()) |
| attrs.set(fir::AliasAnalysis::Attribute::CrayPointer); |
| if (var.isCrayPointee()) |
| attrs.set(fir::AliasAnalysis::Attribute::CrayPointee); |
| |
| return attrs; |
| } |
| |
| bool fir::AliasAnalysis::symbolMayHaveTargetAttr(mlir::SymbolRefAttr symbol, |
| mlir::Operation *from) { |
| assert(from); |
| |
| // If we cannot find the nearest SymbolTable assume the worst. |
| const mlir::SymbolTable *symTab = getNearestSymbolTable(from); |
| if (!symTab) |
| return true; |
| |
| if (auto globalOp = symTab->lookup<fir::GlobalOp>(symbol.getLeafReference())) |
| return globalOp.getTarget().value_or(false); |
| |
| // If the symbol is not defined by fir.global assume the worst. |
| return true; |
| } |
| |
| static bool isEvaluateInMemoryBlockArg(mlir::Value v) { |
| if (auto evalInMem = llvm::dyn_cast_or_null<hlfir::EvaluateInMemoryOp>( |
| v.getParentRegion()->getParentOp())) |
| return evalInMem.getMemory() == v; |
| return false; |
| } |
| |
| template <typename OMPTypeOp, typename DeclTypeOp> |
| static bool isPrivateArg(omp::BlockArgOpenMPOpInterface &argIface, |
| OMPTypeOp &op, DeclTypeOp &declOp) { |
| if (!op.getPrivateSyms().has_value()) |
| return false; |
| for (auto [opSym, blockArg] : |
| llvm::zip_equal(*op.getPrivateSyms(), argIface.getPrivateBlockArgs())) { |
| if (blockArg == declOp.getMemref()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /// Classify `mappedValue` when defined by OpenACC mapping op `accOp`. |
| /// Private-like ops use `SourceKind::Allocate`; other data clauses use |
| /// `getSourceFn` on the mapped host variable (`mlir::acc::getVar`). |
| static fir::AliasAnalysis::Source getSourceForACCMappedValue( |
| mlir::Value mappedValue, mlir::Operation *accOp, |
| llvm::function_ref<fir::AliasAnalysis::Source(mlir::Value)> getSourceFn, |
| bool originIsData, |
| fir::AliasAnalysis::Source::Attributes accumulatedAttrs) { |
| assert(accOp && "OpenACC mapping op required"); |
| // Private-like ops use SourceKind::Allocate. |
| if (mlir::isa<mlir::acc::ReductionInitOp, mlir::acc::PrivateOp, |
| mlir::acc::FirstprivateOp, mlir::acc::FirstprivateMapInitialOp>( |
| accOp)) |
| return {{mappedValue, nullptr, originIsData}, |
| fir::AliasAnalysis::SourceKind::Allocate, |
| mappedValue.getType(), |
| accumulatedAttrs, |
| /*approximateSource=*/false, |
| /*accessPath=*/{}, |
| /*isCapturedInInternalProcedure=*/false}; |
| |
| // Not private-like: classify using the corresponding host variable's source. |
| // |
| // Caveat: with discrete device memory, host and device copies do not alias |
| // even when this path makes them look related. Alias analysis here is usually |
| // about two values *inside* a compute region, not host-vs-device pointer |
| // queries, so using the host source remains a reasonable tradeoff for |
| // disambiguating in-region uses. Finer modeling would require extending |
| // AliasAnalysis::Source (with address space) and teaching AA to use it. |
| fir::AliasAnalysis::Source source = getSourceFn(mlir::acc::getVar(accOp)); |
| source.attributes |= accumulatedAttrs; |
| return source; |
| } |
| |
| namespace fir { |
| |
| void AliasAnalysis::Source::AccessPath::print(llvm::raw_ostream &os) const { |
| os << "["; |
| for (auto it = steps.begin(); it != steps.end(); ++it) { |
| if (it != steps.begin()) |
| os << ", "; |
| switch (it->kind) { |
| case PathStep::Kind::Component: |
| os << "Component(\"" << it->component.getValue() << "\")"; |
| break; |
| case PathStep::Kind::PointerDeref: |
| os << "PointerDeref"; |
| break; |
| case PathStep::Kind::AllocDeref: |
| os << "AllocDeref"; |
| break; |
| } |
| } |
| os << "]"; |
| if (isApproximate) |
| os << "(~)"; |
| } |
| |
| void AliasAnalysis::Source::print(llvm::raw_ostream &os) const { |
| if (auto v = llvm::dyn_cast<mlir::Value>(origin.u)) |
| os << v; |
| else if (auto gbl = llvm::dyn_cast<mlir::SymbolRefAttr>(origin.u)) |
| os << gbl; |
| os << " SourceKind: " << EnumToString(kind); |
| os << " Type: " << valueType << " "; |
| if (origin.isData) { |
| os << " following data "; |
| } else { |
| os << " following box reference "; |
| } |
| os << " AccessPath: "; |
| accessPath.print(os); |
| os << " "; |
| attributes.Dump(os, EnumToString); |
| } |
| |
| bool AliasAnalysis::isRecordWithPointerComponent(mlir::Type ty) { |
| auto eleTy = fir::dyn_cast_ptrEleTy(ty); |
| if (!eleTy) |
| return false; |
| // TO DO: Look for pointer components |
| return mlir::isa<fir::RecordType>(eleTy); |
| } |
| |
| bool AliasAnalysis::isPointerReference(mlir::Type ty) { |
| auto eleTy = fir::dyn_cast_ptrEleTy(ty); |
| if (!eleTy) |
| return false; |
| |
| return fir::isPointerType(eleTy) || mlir::isa<fir::PointerType>(eleTy); |
| } |
| |
| bool AliasAnalysis::Source::isTargetOrPointer() const { |
| return attributes.test(Attribute::Pointer) || |
| attributes.test(Attribute::Target); |
| } |
| |
| bool AliasAnalysis::Source::isTarget() const { |
| return attributes.test(Attribute::Target); |
| } |
| |
| bool AliasAnalysis::Source::isPointer() const { |
| return attributes.test(Attribute::Pointer); |
| } |
| |
| bool AliasAnalysis::Source::isCrayPointee() const { |
| return attributes.test(Attribute::CrayPointee); |
| } |
| |
| bool AliasAnalysis::Source::isCrayPointer() const { |
| return attributes.test(Attribute::CrayPointer); |
| } |
| |
| bool AliasAnalysis::Source::isCrayPointerOrPointee() const { |
| return isCrayPointer() || isCrayPointee(); |
| } |
| |
| bool AliasAnalysis::Source::isDummyArgument() const { |
| if (auto v = origin.u.dyn_cast<mlir::Value>()) { |
| return fir::isDummyArgument(v); |
| } |
| return false; |
| } |
| |
| bool AliasAnalysis::Source::isData() const { return origin.isData; } |
| bool AliasAnalysis::Source::isBoxData() const { |
| return mlir::isa<fir::BaseBoxType>(fir::unwrapRefType(valueType)) && |
| origin.isData; |
| } |
| |
| bool AliasAnalysis::Source::isFortranUserVariable() const { |
| if (!origin.instantiationPoint) |
| return false; |
| return llvm::TypeSwitch<mlir::Operation *, bool>(origin.instantiationPoint) |
| .template Case<fir::DeclareOp, hlfir::DeclareOp>([&](auto declOp) { |
| return fir::NameUniquer::deconstruct(declOp.getUniqName()).first == |
| fir::NameUniquer::NameKind::VARIABLE; |
| }) |
| .Default([&](auto op) { return false; }); |
| } |
| |
| bool AliasAnalysis::Source::mayBeDummyArgOrHostAssoc() const { |
| return kind != SourceKind::Allocate && kind != SourceKind::Global; |
| } |
| |
| bool AliasAnalysis::Source::mayBePtrDummyArgOrHostAssoc() const { |
| // Must alias like dummy arg (or HostAssoc). |
| if (!mayBeDummyArgOrHostAssoc()) |
| return false; |
| // Must be address of the dummy arg not of a dummy arg component. |
| if (isRecordWithPointerComponent(valueType)) |
| return false; |
| // Must be address *of* (not *in*) a pointer. |
| return attributes.test(Attribute::Pointer) && !isData(); |
| } |
| |
| bool AliasAnalysis::Source::mayBeActualArg() const { |
| return kind != SourceKind::Allocate; |
| } |
| |
| bool AliasAnalysis::Source::mayBeActualArgWithPtr( |
| const mlir::Value *val) const { |
| // Must not be local. |
| if (!mayBeActualArg()) |
| return false; |
| // Can be address *of* (not *in*) a pointer. |
| if (attributes.test(Attribute::Pointer) && !isData()) |
| return true; |
| // Can be address of a composite with a pointer component. |
| if (isRecordWithPointerComponent(val->getType())) |
| return true; |
| return false; |
| } |
| |
| // Return true if the two locations cannot alias based |
| // on the access data type, e.g. an address of a descriptor |
| // cannot alias with an address of data (unless the data |
| // may contain a descriptor). |
| static bool noAliasBasedOnType(mlir::Value lhs, mlir::Value rhs) { |
| mlir::Type lhsType = lhs.getType(); |
| mlir::Type rhsType = rhs.getType(); |
| if (!fir::isa_ref_type(lhsType) || !fir::isa_ref_type(rhsType)) |
| return false; |
| mlir::Type lhsElemType = fir::unwrapRefType(lhsType); |
| mlir::Type rhsElemType = fir::unwrapRefType(rhsType); |
| if (mlir::isa<fir::BaseBoxType>(lhsElemType) != |
| mlir::isa<fir::BaseBoxType>(rhsElemType)) { |
| // One of the types is fir.box and another is not. |
| mlir::Type nonBoxType; |
| if (mlir::isa<fir::BaseBoxType>(lhsElemType)) |
| nonBoxType = rhsElemType; |
| else |
| nonBoxType = lhsElemType; |
| |
| if (!fir::isRecordWithDescriptorMember(nonBoxType)) { |
| LLVM_DEBUG(llvm::dbgs() << " no alias based on the access types\n"); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /// Return true if two access paths from the same origin variable diverge at |
| /// a named component step, meaning they address disjoint subobjects of the |
| /// root variable. For example, paths [Component("a")] and [Component("b")] |
| /// diverge immediately, while [Component("a"), Component("x")] and |
| /// [Component("a"), Component("y")] share a common prefix "a" and diverge |
| /// at the second step. |
| /// |
| /// When either path continues through a PointerDeref or AllocDeref after |
| /// the divergence point, the runtime address could potentially reach a |
| /// sibling subobject only if that sibling is a valid pointer target. |
| /// A subobject has TARGET when the root variable has the TARGET attribute |
| /// (Fortran 2018 8.5.7), or when we arrived at the current level through |
| /// a PointerDeref (the pointer target carries TARGET by definition). |
| /// When neither condition holds, the pointer cannot be associated with a |
| /// sibling subobject and the addresses are still disjoint. Note that the |
| /// source's POINTER attribute reflects the component traversed during the |
| /// walk, not the root variable, so we check only TARGET on the source. |
| /// |
| /// One exception: if BOTH sides end with a PointerDeref, the two pointers |
| /// could independently be associated with the same third-party TARGET |
| /// variable, so we conservatively return false. |
| static bool pathsDivergeAtComponent(const fir::AliasAnalysis::Source &lhsSrc, |
| const fir::AliasAnalysis::Source &rhsSrc) { |
| using PathStep = fir::AliasAnalysis::Source::PathStep; |
| auto &lhsSteps = lhsSrc.accessPath.steps; |
| auto &rhsSteps = rhsSrc.accessPath.steps; |
| size_t minLen = std::min(lhsSteps.size(), rhsSteps.size()); |
| for (size_t i = 0; i < minLen; ++i) { |
| if (lhsSteps[i].kind == PathStep::Kind::Component && |
| rhsSteps[i].kind == PathStep::Kind::Component && |
| lhsSteps[i].component != rhsSteps[i].component) { |
| auto hasPtrDerefAfter = [](llvm::ArrayRef<PathStep> steps, size_t from) { |
| for (size_t j = from; j < steps.size(); ++j) |
| if (steps[j].kind == PathStep::Kind::PointerDeref) |
| return true; |
| return false; |
| }; |
| bool lhsHasPtrDeref = hasPtrDerefAfter(lhsSteps, i + 1); |
| bool rhsHasPtrDeref = hasPtrDerefAfter(rhsSteps, i + 1); |
| if (lhsHasPtrDeref && rhsHasPtrDeref) |
| return false; |
| if (lhsHasPtrDeref || rhsHasPtrDeref) { |
| for (size_t j = 0; j < i; ++j) |
| if (lhsSteps[j].kind == PathStep::Kind::PointerDeref) |
| return false; |
| if (lhsSrc.isTarget() || rhsSrc.isTarget()) |
| return false; |
| } |
| return true; |
| } |
| if (lhsSteps[i] != rhsSteps[i]) |
| break; |
| } |
| return false; |
| } |
| |
| AliasResult AliasAnalysis::alias(mlir::Value lhs, mlir::Value rhs) { |
| // A wrapper around alias(Source lhsSrc, Source rhsSrc, mlir::Value lhs, |
| // mlir::Value rhs) This allows a user to provide Source that may be obtained |
| // through other dialects |
| auto lhsSrc = getSource(lhs); |
| auto rhsSrc = getSource(rhs); |
| return alias(lhsSrc, rhsSrc, lhs, rhs); |
| } |
| |
| AliasResult AliasAnalysis::alias(Source lhsSrc, Source rhsSrc, mlir::Value lhs, |
| mlir::Value rhs) { |
| // TODO: alias() has to be aware of the function scopes. |
| // After MLIR inlining, the current implementation may |
| // not recognize non-aliasing entities. |
| bool approximateSource = lhsSrc.approximateSource || rhsSrc.approximateSource; |
| LLVM_DEBUG(llvm::dbgs() << "\nAliasAnalysis::alias\n"; |
| llvm::dbgs() << " lhs: " << lhs << "\n"; |
| llvm::dbgs() << " lhsSrc: " << lhsSrc << "\n"; |
| llvm::dbgs() << " rhs: " << rhs << "\n"; |
| llvm::dbgs() << " rhsSrc: " << rhsSrc << "\n";); |
| |
| // Disambiguate data and descriptors addresses. |
| if (noAliasBasedOnType(lhs, rhs)) |
| return AliasResult::NoAlias; |
| |
| // Indirect case currently not handled. Conservatively assume |
| // it aliases with everything |
| if (lhsSrc.kind >= SourceKind::Indirect || |
| rhsSrc.kind >= SourceKind::Indirect) { |
| LLVM_DEBUG(llvm::dbgs() << " aliasing because of indirect access\n"); |
| return AliasResult::MayAlias; |
| } |
| |
| // After a POINTER dereference the actual address is determined at runtime |
| // by pointer association (Fortran 2018 8.5.7, 15.5.2.13). A POINTER can |
| // only be associated with a TARGET or another POINTER, so the dereferenced |
| // address may alias any source that carries the TARGET or POINTER attribute. |
| // When both sides trace to the same origin variable, the pointer deref |
| // does not introduce cross-variable aliasing, so this check is skipped |
| // (the normal same-origin logic handles that case). |
| if (lhsSrc.origin.u != rhsSrc.origin.u && |
| ((lhsSrc.accessPath.hasPointerDeref() && rhsSrc.isTargetOrPointer()) || |
| (rhsSrc.accessPath.hasPointerDeref() && lhsSrc.isTargetOrPointer()))) { |
| LLVM_DEBUG(llvm::dbgs() |
| << " aliasing because pointer dereference may reach " |
| << "target/pointer\n"); |
| return AliasResult::MayAlias; |
| } |
| |
| // Cray pointers/pointees can alias with anything via LOC. |
| if (supportCrayPointers) { |
| if (lhsSrc.isCrayPointerOrPointee() || rhsSrc.isCrayPointerOrPointee()) { |
| LLVM_DEBUG(llvm::dbgs() |
| << " aliasing because of Cray pointer/pointee\n"); |
| return AliasResult::MayAlias; |
| } |
| } |
| |
| if (lhsSrc.kind == rhsSrc.kind) { |
| // If the kinds and origins are the same, then lhs and rhs must alias unless |
| // either source is approximate. Approximate sources are for parts of the |
| // origin, but we don't have info here on which parts and whether they |
| // overlap, so we normally return MayAlias in that case. |
| if (lhsSrc.origin == rhsSrc.origin) { |
| LLVM_DEBUG(llvm::dbgs() |
| << " aliasing because same source kind and origin\n"); |
| if (approximateSource) { |
| if (pathsDivergeAtComponent(lhsSrc, rhsSrc)) { |
| LLVM_DEBUG(llvm::dbgs() |
| << " no alias: different components of same origin\n"); |
| return AliasResult::NoAlias; |
| } |
| return AliasResult::MayAlias; |
| } |
| // One should be careful about relying on MustAlias. |
| // The LLVM definition implies that the two MustAlias |
| // memory objects start at exactly the same location. |
| // With Fortran array slices two objects may have |
| // the same starting location, but otherwise represent |
| // partially overlapping memory locations, e.g.: |
| // integer :: a(10) |
| // ... a(5:1:-1) ! starts at a(5) and addresses a(5), ..., a(1) |
| // ... a(5:10:1) ! starts at a(5) and addresses a(5), ..., a(10) |
| // The current implementation of FIR alias analysis will always |
| // return MayAlias for such cases. |
| return AliasResult::MustAlias; |
| } |
| // If one value is the address of a composite, and if the other value is the |
| // address of a pointer/allocatable component of that composite, their |
| // origins compare unequal because the latter has !isData(). As for the |
| // address of any component vs. the address of the composite, a store to one |
| // can affect a load from the other, so the result should be MayAlias. To |
| // catch this case, we conservatively return MayAlias when one value is the |
| // address of a composite, the other value is non-data, and they have the |
| // same origin value. |
| // |
| // TODO: That logic does not check that the latter is actually a component |
| // of the former, so it can return MayAlias when unnecessary. For example, |
| // they might both be addresses of components of a larger composite. |
| // |
| // FIXME: Actually, we should generalize from isRecordWithPointerComponent |
| // to any composite because a component with !isData() is not always a |
| // pointer. However, Source::isRecordWithPointerComponent currently doesn't |
| // actually check for pointer components, so it's fine for now. |
| if (lhsSrc.origin.u == rhsSrc.origin.u && |
| ((isRecordWithPointerComponent(lhs.getType()) && !rhsSrc.isData()) || |
| (isRecordWithPointerComponent(rhs.getType()) && !lhsSrc.isData()))) { |
| if (pathsDivergeAtComponent(lhsSrc, rhsSrc)) { |
| LLVM_DEBUG(llvm::dbgs() |
| << " no alias: different components of same origin\n"); |
| return AliasResult::NoAlias; |
| } |
| LLVM_DEBUG(llvm::dbgs() |
| << " aliasing between composite and non-data component with " |
| << "same source kind and origin value\n"); |
| return AliasResult::MayAlias; |
| } |
| |
| // Two host associated accesses may overlap due to an equivalence. |
| if (lhsSrc.kind == SourceKind::HostAssoc) { |
| LLVM_DEBUG(llvm::dbgs() << " aliasing because of host association\n"); |
| return AliasResult::MayAlias; |
| } |
| } |
| |
| Source *src1, *src2; |
| mlir::Value *val1, *val2; |
| if (lhsSrc.kind < rhsSrc.kind) { |
| src1 = &lhsSrc; |
| src2 = &rhsSrc; |
| val1 = &lhs; |
| val2 = &rhs; |
| } else { |
| src1 = &rhsSrc; |
| src2 = &lhsSrc; |
| val1 = &rhs; |
| val2 = &lhs; |
| } |
| |
| if (src1->kind == SourceKind::Argument && |
| src2->kind == SourceKind::HostAssoc) { |
| // Treat the host entity as TARGET for the purpose of disambiguating |
| // it with a dummy access. It is required for this particular case: |
| // subroutine test |
| // integer :: x(10) |
| // call inner(x) |
| // contains |
| // subroutine inner(y) |
| // integer, target :: y(:) |
| // x(1) = y(1) |
| // end subroutine inner |
| // end subroutine test |
| // |
| // F18 15.5.2.13 (4) (b) allows 'x' and 'y' to address the same object. |
| // 'y' has an explicit TARGET attribute, but 'x' has neither TARGET |
| // nor POINTER. |
| src2->attributes.set(Attribute::Target); |
| } |
| |
| // Two TARGET/POINTERs may alias. The logic here focuses on data. Handling |
| // of non-data is included below. |
| if (src1->isTargetOrPointer() && src2->isTargetOrPointer() && |
| src1->isData() && src2->isData()) { |
| // Two distinct TARGET globals may not alias. |
| if (!src1->isPointer() && !src2->isPointer() && |
| src1->kind == SourceKind::Global && src2->kind == SourceKind::Global && |
| src1->origin.u != src2->origin.u) { |
| return AliasResult::NoAlias; |
| } |
| LLVM_DEBUG(llvm::dbgs() << " aliasing because of target or pointer\n"); |
| return AliasResult::MayAlias; |
| } |
| |
| // Aliasing for dummy arg with target attribute. |
| // |
| // The address of a dummy arg (or HostAssoc) may alias the address of a |
| // non-local (global or another dummy arg) when both have target attributes. |
| // If either is a composite, addresses of components may alias as well. |
| // |
| // The previous "if" calling isTargetOrPointer casts a very wide net and so |
| // reports MayAlias for many such cases that would otherwise be reported here. |
| // It specifically skips such cases where one or both values have !isData() |
| // (e.g., address *of* pointer/allocatable component vs. address of |
| // composite), so this "if" catches those cases. |
| if (src1->attributes.test(Attribute::Target) && |
| src2->attributes.test(Attribute::Target) && |
| ((src1->mayBeDummyArgOrHostAssoc() && src2->mayBeActualArg()) || |
| (src2->mayBeDummyArgOrHostAssoc() && src1->mayBeActualArg()))) { |
| LLVM_DEBUG(llvm::dbgs() |
| << " aliasing between targets where one is a dummy arg\n"); |
| return AliasResult::MayAlias; |
| } |
| |
| // Aliasing for dummy arg that is a pointer. |
| // |
| // The address of a pointer dummy arg (but not a pointer component of a dummy |
| // arg) may alias the address of either (1) a non-local pointer or (2) thus a |
| // non-local composite with a pointer component. A non-local might be a |
| // global or another dummy arg. The following is an example of the global |
| // composite case: |
| // |
| // module m |
| // type t |
| // real, pointer :: p |
| // end type |
| // type(t) :: a |
| // type(t) :: b |
| // contains |
| // subroutine test(p) |
| // real, pointer :: p |
| // p = 42 |
| // a = b |
| // print *, p |
| // end subroutine |
| // end module |
| // program main |
| // use m |
| // real, target :: x1 = 1 |
| // real, target :: x2 = 2 |
| // a%p => x1 |
| // b%p => x2 |
| // call test(a%p) |
| // end |
| // |
| // The dummy argument p is an alias for a%p, even for the purposes of pointer |
| // association during the assignment a = b. Thus, the program should print 2. |
| // |
| // The same is true when p is HostAssoc. For example, we might replace the |
| // test subroutine above with: |
| // |
| // subroutine test(p) |
| // real, pointer :: p |
| // call internal() |
| // contains |
| // subroutine internal() |
| // p = 42 |
| // a = b |
| // print *, p |
| // end subroutine |
| // end subroutine |
| if ((src1->mayBePtrDummyArgOrHostAssoc() && |
| src2->mayBeActualArgWithPtr(val2)) || |
| (src2->mayBePtrDummyArgOrHostAssoc() && |
| src1->mayBeActualArgWithPtr(val1))) { |
| LLVM_DEBUG(llvm::dbgs() |
| << " aliasing between pointer dummy arg and either pointer or " |
| << "composite with pointer component\n"); |
| return AliasResult::MayAlias; |
| } |
| |
| return AliasResult::NoAlias; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // AliasAnalysis: getModRef |
| //===----------------------------------------------------------------------===// |
| |
| static bool isSavedLocal(const fir::AliasAnalysis::Source &src) { |
| if (auto symRef = llvm::dyn_cast<mlir::SymbolRefAttr>(src.origin.u)) { |
| auto [nameKind, deconstruct] = |
| fir::NameUniquer::deconstruct(symRef.getLeafReference().getValue()); |
| return nameKind == fir::NameUniquer::NameKind::VARIABLE && |
| !deconstruct.procs.empty(); |
| } |
| return false; |
| } |
| |
| bool AliasAnalysis::isCallToFortranUserProcedure(Operation *op) { |
| fir::CallOp call = dyn_cast<fir::CallOp>(op); |
| if (!call) |
| return false; |
| |
| // TODO: indirect calls are excluded by these checks. Maybe some attribute is |
| // needed to flag user calls in this case. |
| if (fir::hasBindcAttr(call)) |
| return true; |
| if (std::optional<SymbolRefAttr> callee = call.getCallee()) { |
| if (fir::NameUniquer::deconstruct(callee->getLeafReference().getValue()) |
| .first == fir::NameUniquer::NameKind::PROCEDURE) |
| return true; |
| |
| const SymbolTable *symTab = getNearestSymbolTable(call); |
| if (!symTab) |
| return false; |
| |
| if (auto funcOp = |
| symTab->lookup<FunctionOpInterface>(callee->getLeafReference())) |
| if (auto name = funcOp->getAttrOfType<StringAttr>( |
| fir::getInternalFuncNameAttrName())) |
| if (fir::NameUniquer::deconstruct(name.getValue()).first == |
| fir::NameUniquer::NameKind::PROCEDURE) |
| return true; |
| } |
| return false; |
| } |
| |
| ModRefResult AliasAnalysis::getCallModRef(Operation *op, Value var) { |
| auto call = dyn_cast<fir::CallOp>(op); |
| if (!call) |
| return ModRefResult::getModAndRef(); |
| |
| // TODO: limit to Fortran functions?? |
| // 1. Detect variables that can be accessed indirectly. |
| fir::AliasAnalysis aliasAnalysis; |
| fir::AliasAnalysis::Source varSrc = |
| aliasAnalysis.getSource(var, /*getLastInstantiationPoint=*/true); |
| // If the variable is not a user variable, we cannot safely assume that |
| // Fortran semantics apply (e.g., a bare alloca/allocmem result may very well |
| // be placed in an allocatable/pointer descriptor and escape). |
| |
| // All the logic below is based on Fortran semantics and only holds if this |
| // is a call to a procedure from the Fortran source and this is a variable |
| // from the Fortran source. Compiler generated temporaries or functions may |
| // not adhere to this semantic. |
| // TODO: add some opt-in or op-out mechanism for compiler generated temps. |
| // An example of something currently problematic is the allocmem generated for |
| // ALLOCATE of allocatable target. It currently does not have the target |
| // attribute, which would lead this analysis to believe it cannot escape. |
| if (!varSrc.isFortranUserVariable() || !isCallToFortranUserProcedure(call)) |
| return ModRefResult::getModAndRef(); |
| // Pointer and target may have been captured. |
| if (varSrc.isTargetOrPointer()) |
| return ModRefResult::getModAndRef(); |
| // Host associated variables may be addressed indirectly via an internal |
| // function call, whether the call is in the parent or an internal procedure. |
| // Note that the host associated/internal procedure may be referenced |
| // indirectly inside calls to non internal procedure. This is because internal |
| // procedures may be captured or passed. As this is tricky to analyze, always |
| // consider such variables may be accessed in any calls. |
| if (varSrc.kind == fir::AliasAnalysis::SourceKind::HostAssoc || |
| varSrc.isCapturedInInternalProcedure) |
| return ModRefResult::getModAndRef(); |
| // At that stage, it has been ruled out that local (including the saved ones) |
| // and dummy cannot be indirectly accessed in the call. |
| if (varSrc.kind != fir::AliasAnalysis::SourceKind::Allocate && |
| varSrc.kind != fir::AliasAnalysis::SourceKind::Argument && |
| !varSrc.isDummyArgument()) { |
| if (varSrc.kind != fir::AliasAnalysis::SourceKind::Global || |
| !isSavedLocal(varSrc)) |
| return ModRefResult::getModAndRef(); |
| } |
| // 2. Check if the variable is passed via the arguments. |
| for (auto arg : call.getArgs()) { |
| if (fir::conformsWithPassByRef(arg.getType()) && |
| !aliasAnalysis.alias(arg, var).isNo()) { |
| // TODO: intent(in) would allow returning Ref here. This can be obtained |
| // in the func.func attributes for direct calls, but the module lookup is |
| // linear with the number of MLIR symbols, which would introduce a pseudo |
| // quadratic behavior num_calls * num_func. |
| return ModRefResult::getModAndRef(); |
| } |
| } |
| // The call cannot access the variable. |
| return ModRefResult::getNoModRef(); |
| } |
| |
| /// This is mostly inspired by MLIR::LocalAliasAnalysis, except that |
| /// fir.call's are handled in a special way. |
| ModRefResult AliasAnalysis::getModRef(Operation *op, Value location) { |
| if (auto call = llvm::dyn_cast<fir::CallOp>(op)) { |
| ModRefResult result = getCallModRef(call, location); |
| if (result != ModRefResult::getModAndRef()) |
| return result; |
| // Proceed to MemoryEffectOpInterface analysis in case one |
| // is attached for fir.call. |
| } |
| |
| // Build a ModRefResult by merging the behavior of the effects of this |
| // operation. |
| ModRefResult result = ModRefResult::getNoModRef(); |
| MemoryEffectOpInterface interface = dyn_cast<MemoryEffectOpInterface>(op); |
| if (op->hasTrait<mlir::OpTrait::HasRecursiveMemoryEffects>()) { |
| for (mlir::Region ®ion : op->getRegions()) { |
| result = result.merge(getModRef(region, location)); |
| if (result.isModAndRef()) |
| break; |
| } |
| |
| // In MLIR, RecursiveMemoryEffects can be combined with |
| // MemoryEffectOpInterface to describe extra effects on top of the |
| // effects of the nested operations. However, the presence of |
| // RecursiveMemoryEffects and the absence of MemoryEffectOpInterface |
| // implies the operation has no other memory effects than the one of its |
| // nested operations. |
| if (!interface) |
| return result; |
| } |
| |
| if (!interface || result.isModAndRef()) |
| return ModRefResult::getModAndRef(); |
| |
| SmallVector<MemoryEffects::EffectInstance> effects; |
| interface.getEffects(effects); |
| |
| for (const MemoryEffects::EffectInstance &effect : effects) { |
| // MemAlloc and MemFree are not mod-ref effects. |
| if (isa<MemoryEffects::Allocate, MemoryEffects::Free>(effect.getEffect())) |
| continue; |
| |
| // An effect on a non-addressable resource cannot affect |
| // memory pointed to by 'location'. |
| mlir::SideEffects::Resource *resource = effect.getResource(); |
| if (!resource->isAddressable()) |
| continue; |
| |
| // Check for an alias between the effect and our memory location. |
| AliasResult aliasResult = AliasResult::MayAlias; |
| if (Value effectValue = effect.getValue()) |
| aliasResult = alias(effectValue, location); |
| |
| // If we don't alias, ignore this effect. |
| if (aliasResult.isNo()) |
| continue; |
| |
| // Merge in the corresponding mod or ref for this effect. |
| if (isa<MemoryEffects::Read>(effect.getEffect())) |
| result = result.merge(ModRefResult::getRef()); |
| else |
| result = result.merge(ModRefResult::getMod()); |
| |
| if (result.isModAndRef()) |
| break; |
| } |
| return result; |
| } |
| |
| ModRefResult AliasAnalysis::getModRef(mlir::Region ®ion, |
| mlir::Value location) { |
| ModRefResult result = ModRefResult::getNoModRef(); |
| for (mlir::Operation &op : region.getOps()) { |
| result = result.merge(getModRef(&op, location)); |
| if (result.isModAndRef()) |
| return result; |
| } |
| return result; |
| } |
| |
| AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v, |
| bool getLastInstantiationPoint) { |
| auto *defOp = v.getDefiningOp(); |
| SourceKind type{SourceKind::Unknown}; |
| mlir::Type ty; |
| bool breakFromLoop{false}; |
| bool approximateSource{false}; |
| bool isCapturedInInternalProcedure{false}; |
| bool followBoxData{mlir::isa<fir::BaseBoxType>(v.getType())}; |
| bool isBoxRef{fir::isa_ref_type(v.getType()) && |
| mlir::isa<fir::BaseBoxType>(fir::unwrapRefType(v.getType()))}; |
| bool followingData = !isBoxRef; |
| mlir::SymbolRefAttr global; |
| Source::Attributes attributes; |
| mlir::Operation *instantiationPoint{nullptr}; |
| |
| // Access path steps collected during the backward walk (leaf-to-root order). |
| // Reversed into the final AccessPath at the end, unless the box-load branch |
| // composes the full path directly. |
| llvm::SmallVector<Source::PathStep, 4> pathSteps; |
| Source::AccessPath accessPath; |
| bool accessPathFinalized{false}; |
| while (defOp && !breakFromLoop) { |
| // Operations may have multiple results, so we need to analyze |
| // the result for which the source is queried. |
| auto opResult = mlir::cast<OpResult>(v); |
| assert(opResult.getOwner() == defOp && "v must be a result of defOp"); |
| // Value-scoped allocation detection via effects. |
| if (classifyAllocateFromEffects(opResult) == SourceKind::Allocate) { |
| type = SourceKind::Allocate; |
| break; |
| } |
| ty = opResult.getType(); |
| std::optional<AliasAnalysis::Source> accSourceReturn; |
| llvm::TypeSwitch<Operation *>(defOp) |
| .Case([&](hlfir::AsExprOp op) { |
| // TODO: we should probably always report hlfir.as_expr |
| // as a unique source, and let the codegen decide whether |
| // to use the original buffer or create a copy. |
| v = op.getVar(); |
| defOp = v.getDefiningOp(); |
| }) |
| .Case([&](hlfir::AssociateOp op) { |
| assert(opResult != op.getMustFreeStrorageFlag() && |
| "MustFreeStorageFlag result is not an aliasing candidate"); |
| |
| mlir::Value source = op.getSource(); |
| if (fir::isa_trivial(source.getType())) { |
| // Trivial values will always use distinct temp memory, |
| // so we can classify this as Allocate and stop. |
| type = SourceKind::Allocate; |
| breakFromLoop = true; |
| } else { |
| // AssociateOp may reuse the expression storage, |
| // so we have to trace further. |
| v = source; |
| defOp = v.getDefiningOp(); |
| } |
| }) |
| .Case([&](fir::PackArrayOp op) { |
| // The packed array is not distinguishable from the original |
| // array, so skip PackArrayOp and track further through |
| // the array operand. |
| v = op.getArray(); |
| defOp = v.getDefiningOp(); |
| approximateSource = true; |
| }) |
| .Case([&](fir::LoadOp op) { |
| // If load is inside target and it points to mapped item, |
| // continue tracking. |
| Operation *loadMemrefOp = op.getMemref().getDefiningOp(); |
| bool isDeclareOp = |
| llvm::isa_and_present<fir::DeclareOp>(loadMemrefOp) || |
| llvm::isa_and_present<hlfir::DeclareOp>(loadMemrefOp); |
| if (isDeclareOp && |
| llvm::isa<omp::TargetOp>(loadMemrefOp->getParentOp())) { |
| v = op.getMemref(); |
| defOp = v.getDefiningOp(); |
| return; |
| } |
| |
| // Loading a box value from memory (e.g. a pointer/allocatable |
| // component's descriptor). Trace the memref so derived-type |
| // component accesses reach their [hl]fir.declare instead of |
| // SourceKind::Indirect (which forces MayAlias broadly in alias()). |
| // The access path records a PointerDeref or AllocDeref step here |
| // so that alias() can distinguish pointer-dereferenced addresses |
| // from statically known ones. |
| if (auto boxTy = mlir::dyn_cast<fir::BaseBoxType>(ty); boxTy) { |
| |
| bool isPointerBox = mlir::isa<fir::PointerType>(boxTy.getEleTy()); |
| if (isPointerBox) |
| attributes.set(Attribute::Pointer); |
| |
| auto boxSrc = getSource(op.getMemref()); |
| attributes |= boxSrc.attributes; |
| approximateSource |= boxSrc.approximateSource; |
| isCapturedInInternalProcedure |= |
| boxSrc.isCapturedInInternalProcedure; |
| |
| if (getLastInstantiationPoint) { |
| if (!instantiationPoint) |
| instantiationPoint = boxSrc.origin.instantiationPoint; |
| } else { |
| instantiationPoint = boxSrc.origin.instantiationPoint; |
| } |
| |
| // Compose the access path: inner path (root to this load point) |
| // + deref step + outer path (this load to the queried value). |
| accessPath.steps = boxSrc.accessPath.steps; |
| Source::PathStep derefStep; |
| derefStep.kind = isPointerBox ? Source::PathStep::Kind::PointerDeref |
| : Source::PathStep::Kind::AllocDeref; |
| derefStep.component = {}; |
| accessPath.steps.push_back(derefStep); |
| for (int i = pathSteps.size() - 1; i >= 0; --i) |
| accessPath.steps.push_back(pathSteps[i]); |
| accessPath.isApproximate = |
| boxSrc.accessPath.isApproximate || approximateSource; |
| accessPathFinalized = true; |
| |
| global = llvm::dyn_cast<mlir::SymbolRefAttr>(boxSrc.origin.u); |
| if (global) { |
| type = SourceKind::Global; |
| } else { |
| auto def = llvm::cast<mlir::Value>(boxSrc.origin.u); |
| bool classified = false; |
| if (auto defAsOpResult = mlir::dyn_cast<OpResult>(def)) { |
| if (classifyAllocateFromEffects(defAsOpResult) == |
| SourceKind::Allocate) { |
| v = def; |
| defOp = defAsOpResult.getOwner(); |
| type = SourceKind::Allocate; |
| classified = true; |
| } |
| } |
| if (!classified) { |
| if (boxSrc.kind == SourceKind::Allocate) { |
| type = SourceKind::Allocate; |
| v = def; |
| defOp = nullptr; |
| } else if (isDummyArgument(def)) { |
| defOp = nullptr; |
| v = def; |
| } else { |
| type = SourceKind::Indirect; |
| } |
| } |
| } |
| breakFromLoop = true; |
| return; |
| } |
| // No further tracking for addresses loaded from memory for now. |
| type = SourceKind::Indirect; |
| breakFromLoop = true; |
| }) |
| .Case<fir::AddrOfOp, cuf::DeviceAddressOp>([&](auto op) { |
| // Address of a global scope object. |
| ty = v.getType(); |
| type = SourceKind::Global; |
| // TODO: Take followBoxData into account when setting the pointer |
| // attribute |
| if (isPointerReference(ty)) |
| attributes.set(Attribute::Pointer); |
| |
| if constexpr (std::is_same_v<std::decay_t<decltype(op)>, |
| fir::AddrOfOp>) |
| global = op.getSymbol(); |
| else if constexpr (std::is_same_v<std::decay_t<decltype(op)>, |
| cuf::DeviceAddressOp>) |
| global = op.getHostSymbol(); |
| else |
| llvm_unreachable("unexpected operation"); |
| |
| if (symbolMayHaveTargetAttr(global, op)) |
| attributes.set(Attribute::Target); |
| |
| breakFromLoop = true; |
| }) |
| .Case<hlfir::DeclareOp, fir::DeclareOp>([&](auto op) { |
| // The declare operations support FortranObjectViewOpInterface, |
| // but their handling is more complex. Maybe we can find better |
| // abstractions to handle them in a general fashion. |
| bool isPrivateItem = false; |
| if (omp::BlockArgOpenMPOpInterface argIface = |
| dyn_cast<omp::BlockArgOpenMPOpInterface>(op->getParentOp())) { |
| Value ompValArg; |
| llvm::TypeSwitch<Operation *>(op->getParentOp()) |
| .Case([&](omp::TargetOp targetOp) { |
| // If declare operation is inside omp target region, |
| // continue alias analysis outside the target region |
| for (auto [opArg, blockArg] : llvm::zip_equal( |
| targetOp.getMapVars(), argIface.getMapBlockArgs())) { |
| if (blockArg == op.getMemref()) { |
| omp::MapInfoOp mapInfo = |
| llvm::cast<omp::MapInfoOp>(opArg.getDefiningOp()); |
| ompValArg = mapInfo.getVarPtr(); |
| return; |
| } |
| } |
| // If given operation does not reflect mapping item, |
| // check private clause |
| isPrivateItem = isPrivateArg(argIface, targetOp, op); |
| }) |
| .template Case<omp::DistributeOp, omp::ParallelOp, |
| omp::SectionsOp, omp::SimdOp, omp::SingleOp, |
| omp::TaskloopContextOp, omp::TaskOp, |
| omp::WsloopOp>([&](auto privateOp) { |
| isPrivateItem = isPrivateArg(argIface, privateOp, op); |
| }); |
| if (ompValArg) { |
| v = ompValArg; |
| defOp = ompValArg.getDefiningOp(); |
| return; |
| } |
| } |
| auto varIf = llvm::cast<fir::FortranVariableOpInterface>(defOp); |
| // While going through a declare operation collect |
| // the variable attributes from it. Right now, some |
| // of the attributes are duplicated, e.g. a TARGET dummy |
| // argument has the target attribute both on its declare |
| // operation and on the entry block argument. |
| // In case of host associated use, the declare operation |
| // is the only carrier of the variable attributes, |
| // so we have to collect them here. |
| attributes |= getAttrsFromVariable(varIf); |
| isCapturedInInternalProcedure |= |
| varIf.isCapturedInInternalProcedure(); |
| if (varIf.isHostAssoc()) { |
| // Do not track past such DeclareOp, because it does not |
| // currently provide any useful information. The host associated |
| // access will end up dereferencing the host association tuple, |
| // so we may as well stop right now. |
| v = opResult; |
| // TODO: if the host associated variable is a dummy argument |
| // of the host, I think, we can treat it as SourceKind::Argument |
| // for the purpose of alias analysis inside the internal procedure. |
| type = SourceKind::HostAssoc; |
| breakFromLoop = true; |
| return; |
| } |
| if (getLastInstantiationPoint) { |
| // Fetch only the innermost instantiation point. |
| if (!instantiationPoint) |
| instantiationPoint = op; |
| |
| if (op.getDummyScope()) { |
| // Do not track past DeclareOp that has the dummy_scope |
| // operand. This DeclareOp is known to represent |
| // a dummy argument for some runtime instantiation |
| // of a procedure. |
| type = SourceKind::Argument; |
| breakFromLoop = true; |
| return; |
| } |
| } else { |
| instantiationPoint = op; |
| } |
| if (isPrivateItem) { |
| type = SourceKind::Allocate; |
| breakFromLoop = true; |
| return; |
| } |
| // TODO: Look for the fortran attributes present on the operation |
| // Track further through the operand |
| v = op.getMemref(); |
| defOp = v.getDefiningOp(); |
| }) |
| .Case([&](fir::FortranObjectViewOpInterface op) { |
| // This case must be located after the cases for concrete |
| // operations that support FortraObjectViewOpInterface, |
| // so that their special handling kicks in. |
| |
| // fir.embox/rebox case: this is the only case where we check |
| // for followBoxData. |
| // TODO: it looks like we do not have LIT tests that fail |
| // upon removal of the followBoxData code. We should come up |
| // with a test or remove this code. |
| if (!followBoxData && |
| (mlir::isa<fir::EmboxOp>(op) || mlir::isa<fir::ReboxOp>(op))) { |
| breakFromLoop = true; |
| return; |
| } |
| |
| // Record component access steps for the access path. |
| // |
| // hlfir.designate carries the component name directly as a |
| // StringAttr, e.g. hlfir.designate %x{"fieldName"}. |
| if (auto designateOp = mlir::dyn_cast<hlfir::DesignateOp>(defOp)) { |
| if (auto comp = designateOp.getComponent()) { |
| Source::PathStep step; |
| step.kind = Source::PathStep::Kind::Component; |
| step.component = *comp; |
| pathSteps.push_back(step); |
| } |
| } else if (auto coordOp = mlir::dyn_cast<fir::CoordinateOp>(defOp)) { |
| // fir.coordinate_of encodes field accesses as integer indices |
| // into the record type's field list (the field_indices attr). |
| // A single coordinate_of may access multiple nested fields, |
| // e.g. fir.coordinate_of %obj, inner, a has field_indices |
| // [inner_idx, a_idx]. Walk the type hierarchy to recover |
| // the field name for each static index. Dynamic indices |
| // (kDynamicIndex) correspond to array subscripts, not named |
| // components, so they only advance the type through the |
| // array dimension. |
| std::optional<llvm::ArrayRef<int32_t>> fieldIndices = |
| coordOp.getFieldIndices(); |
| if (fieldIndices) { |
| mlir::Type currentTy = |
| fir::dyn_cast_ptrOrBoxEleTy(coordOp.getRef().getType()); |
| llvm::SmallVector<mlir::StringAttr, 4> componentNames; |
| unsigned dimension = 0; |
| for (int32_t idx : *fieldIndices) { |
| if (idx == fir::CoordinateOp::kDynamicIndex) { |
| if (dimension == 0) { |
| if (auto seqTy = |
| mlir::dyn_cast<fir::SequenceType>(currentTy)) |
| dimension = seqTy.getDimension(); |
| } |
| if (dimension) { |
| if (--dimension == 0) |
| currentTy = mlir::cast<fir::SequenceType>(currentTy) |
| .getElementType(); |
| } |
| continue; |
| } |
| auto recTy = mlir::dyn_cast<fir::RecordType>(currentTy); |
| if (!recTy) { |
| // Unexpected type structure; discard any partially |
| // collected names so the access path stays conservative |
| // rather than recording a misleading partial path. |
| componentNames.clear(); |
| break; |
| } |
| auto typeList = recTy.getTypeList(); |
| if (idx < 0 || static_cast<size_t>(idx) >= typeList.size()) { |
| // Out-of-bounds field index; same conservative treatment. |
| componentNames.clear(); |
| break; |
| } |
| componentNames.push_back(mlir::StringAttr::get( |
| defOp->getContext(), typeList[idx].first)); |
| currentTy = typeList[idx].second; |
| } |
| // pathSteps is in leaf-to-root order (reversed at the end), |
| // so push innermost component first. |
| for (auto it = componentNames.rbegin(); |
| it != componentNames.rend(); ++it) { |
| Source::PathStep step; |
| step.kind = Source::PathStep::Kind::Component; |
| step.component = *it; |
| pathSteps.push_back(step); |
| } |
| } |
| } |
| |
| // Collect attributes from FortranVariableOpInterface operations. |
| if (auto varIf = |
| mlir::dyn_cast<fir::FortranVariableOpInterface>(defOp)) |
| attributes |= getAttrsFromVariable(varIf); |
| // Set Pointer attribute based on the reference type. |
| if (isPointerReference(ty)) |
| attributes.set(Attribute::Pointer); |
| |
| // Update v to point to the operand that represents the object |
| // referenced by the operation's result. |
| v = op.getViewSource(opResult); |
| defOp = v.getDefiningOp(); |
| // If the input the resulting object references are offsetted, |
| // then set approximateSource. |
| auto offset = op.getViewOffset(opResult); |
| if (!offset || *offset != 0) |
| approximateSource = true; |
| |
| // If the source is a box, and the result is not a box, |
| // then this is one of the box "unpacking" operations, |
| // so we should set followBoxData. |
| if (mlir::isa<fir::BaseBoxType>(v.getType()) && |
| !mlir::isa<fir::BaseBoxType>(ty)) |
| followBoxData = true; |
| }) |
| .Case<ACC_DATA_ENTRY_AND_INIT_OPS>([&](auto op) { |
| accSourceReturn = getSourceForACCMappedValue( |
| v, op.getOperation(), |
| [&](mlir::Value x) { |
| return getSource(x, getLastInstantiationPoint); |
| }, |
| followingData, attributes); |
| breakFromLoop = true; |
| }) |
| .Default([&](auto op) { |
| defOp = nullptr; |
| breakFromLoop = true; |
| }); |
| if (accSourceReturn) |
| return *accSourceReturn; |
| } |
| if (!defOp && type == SourceKind::Unknown) { |
| // Check if the memory source is coming through a dummy argument. |
| if (isDummyArgument(v)) { |
| type = SourceKind::Argument; |
| ty = v.getType(); |
| if (fir::valueHasFirAttribute(v, fir::getTargetAttrName())) |
| attributes.set(Attribute::Target); |
| |
| if (isPointerReference(ty)) |
| attributes.set(Attribute::Pointer); |
| } else if (isEvaluateInMemoryBlockArg(v)) { |
| // hlfir.eval_in_mem block operands is allocated by the operation. |
| type = SourceKind::Allocate; |
| ty = v.getType(); |
| } else if (mlir::Operation *accOp = |
| mlir::acc::getACCDataClauseOpForBlockArg(v)) { |
| return getSourceForACCMappedValue( |
| v, accOp, |
| [&](mlir::Value x) { |
| return getSource(x, getLastInstantiationPoint); |
| }, |
| followingData, attributes); |
| } |
| } |
| |
| // Finalize the access path if not already done by the box-load branch. |
| if (!accessPathFinalized) { |
| std::reverse(pathSteps.begin(), pathSteps.end()); |
| accessPath.steps = std::move(pathSteps); |
| accessPath.isApproximate = approximateSource; |
| } |
| |
| if (type == SourceKind::Global) { |
| return {{global, instantiationPoint, followingData}, |
| type, |
| ty, |
| attributes, |
| approximateSource, |
| accessPath, |
| isCapturedInInternalProcedure}; |
| } |
| return {{v, instantiationPoint, followingData}, |
| type, |
| ty, |
| attributes, |
| approximateSource, |
| accessPath, |
| isCapturedInInternalProcedure}; |
| } |
| |
| const mlir::SymbolTable * |
| fir::AliasAnalysis::getNearestSymbolTable(mlir::Operation *from) { |
| assert(from); |
| Operation *symTabOp = mlir::SymbolTable::getNearestSymbolTable(from); |
| if (!symTabOp) |
| return nullptr; |
| auto it = symTabMap.find(symTabOp); |
| if (it != symTabMap.end()) |
| return &it->second; |
| return &symTabMap.try_emplace(symTabOp, symTabOp).first->second; |
| } |
| |
| } // namespace fir |