[flang-commits] [flang] [flang] Respect record type changes in FIR alias analysis access paths (PR #231655)
Slava Zakharin via flang-commits
flang-commits at lists.llvm.org
Mon Oct 19 09:05:43 PDT 2026
https://github.com/vzakhari updated https://github.com/llvm/llvm-project/pull/231655
>From 61357df380229be6759d666eac15a1cd6fa56458 Mon Sep 17 00:00:00 2001
From: Slava Zakharin <szakharin at nvidia.com>
Date: Tue, 13 Oct 2026 13:56:31 -0700
Subject: [PATCH 1/3] [flang] Respect record type changes in FIR alias analysis
access paths
pathsDivergeAtComponent compares the component names on the access
paths of two accesses to the same variable. The paths are collected
while walking through views of the variable, and views that change the
type of the addressed object did not adjust them, so the paths of two
accesses to the same memory could diverge:
select type (a => this) ! this is class(base)
type is (child)
a%v(2:4) = this%v(1:3) ! a%v is a%base%v
end select
The paths of a%v and this%v were [base, v] and [v], so alias analysis
returned NoAlias, and InlineHLFIRAssign turned the assignment into an
in-place loop.
Component steps now record the record type whose field they select,
and component names are only compared between steps that select fields
of the same type. When the walk crosses a view with a different type:
- an upcast to an ancestor type, e.g. passing an extended type object
to a CLASS(base) dummy, adds the steps selecting the parent
components;
- the steps collected through a SELECT TYPE downcast of a polymorphic
object stay relative to the extension type;
- the component path of a fir.slice, e.g. for x%in, adds its component
steps;
- other views, e.g. the views of EQUIVALENCE or COMMON storage, or the
views of a descriptor as data, make the path opaque: an opaque path
never proves that accesses are disjoint.
A view may also address memory outside of its source, e.g. an element
of an array component viewed as an array through sequence association,
or a fir.create_box with arbitrary byte strides. The component steps
selecting the subobject that contains the source of such a view do not
contain the accessed memory, so they make the path opaque too.
An opaque path also disables the rules that tell data and descriptor
accesses apart based on their types. The snapshots of a path taken at
declarations for the scope-aware refinement inherit the opacity of the
views crossed after them.
Assisted-by: Cursor
---
.../flang/Optimizer/Analysis/AliasAnalysis.h | 30 +-
.../lib/Optimizer/Analysis/AliasAnalysis.cpp | 397 +++++++++++++-
...alias-analysis-access-path-type-change.fir | 507 ++++++++++++++++++
.../HLFIR/inline-hlfir-assign-select-type.fir | 51 ++
4 files changed, 967 insertions(+), 18 deletions(-)
create mode 100644 flang/test/Analysis/AliasAnalysis/alias-analysis-access-path-type-change.fir
create mode 100644 flang/test/HLFIR/inline-hlfir-assign-select-type.fir
diff --git a/flang/include/flang/Optimizer/Analysis/AliasAnalysis.h b/flang/include/flang/Optimizer/Analysis/AliasAnalysis.h
index a429c3c33bed4..ecf504385a181 100644
--- a/flang/include/flang/Optimizer/Analysis/AliasAnalysis.h
+++ b/flang/include/flang/Optimizer/Analysis/AliasAnalysis.h
@@ -185,9 +185,16 @@ struct AliasAnalysis {
/// attribute or fir.coordinate_of's field_indices (mapped through the
/// record type). Null for non-Component steps.
mlir::StringAttr component;
+ /// For Component steps: the record type that `component` is a field
+ /// of. Component names are only comparable between steps that select
+ /// fields of the same record type: the same object may be accessed
+ /// through views of different types (e.g. CLASS(base) views of an
+ /// extended type object). Null for non-Component steps.
+ mlir::Type recordType;
bool operator==(const PathStep &o) const {
- return kind == o.kind && component == o.component;
+ return kind == o.kind && component == o.component &&
+ recordType == o.recordType;
}
bool operator!=(const PathStep &o) const { return !(*this == o); }
};
@@ -211,6 +218,24 @@ struct AliasAnalysis {
/// MustAlias.
bool isApproximate{false};
+ /// Whether the component steps of the path may not contain the
+ /// queried memory location, so they cannot prove that two accesses
+ /// are disjoint. This happens when the path goes through:
+ /// - a view that reinterprets memory as an unrelated type, e.g.
+ /// storage association through EQUIVALENCE or COMMON, so that the
+ /// steps collected after the view do not describe the layout of
+ /// the root variable;
+ /// - a view that may address memory outside of its source, e.g. an
+ /// array element viewed as an array (sequence association) or a
+ /// box with arbitrary byte strides, followed by steps selecting
+ /// the subobject of the root variable that contains the source;
+ /// - a view that reinterprets a descriptor as data or vice versa, so
+ /// that the location may not be told apart from a descriptor based
+ /// on its type.
+ /// The snapshots of the path taken at declarations (see ScopedOrigin)
+ /// inherit the opacity of the views crossed after them.
+ bool isOpaque{false};
+
/// Return true if any step is a PointerDeref.
bool hasPointerDeref() const {
return llvm::any_of(steps, [](const PathStep &s) {
@@ -219,7 +244,8 @@ struct AliasAnalysis {
}
bool operator==(const AccessPath &o) const {
- return isApproximate == o.isApproximate && steps == o.steps;
+ return isApproximate == o.isApproximate && isOpaque == o.isOpaque &&
+ steps == o.steps;
}
bool operator!=(const AccessPath &o) const { return !(*this == o); }
diff --git a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
index 9e568af786910..b5b0e691b9c15 100644
--- a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
+++ b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
@@ -321,6 +321,13 @@ static fir::AliasAnalysis::Source mergeRegionBranchPredecessorSources(
} else {
mergedPath = {};
mergedPath.isApproximate = true;
+ // The snapshots taken before reaching the region-branch op are merged
+ // with this path, so they need to know that some predecessor path is
+ // opaque.
+ mergedPath.isOpaque =
+ llvm::any_of(sources, [](const fir::AliasAnalysis::Source &s) {
+ return s.accessPath.isOpaque;
+ });
}
mlir::Type mergedTy = allTypesSame ? sources[0].valueType : fallbackType;
@@ -367,6 +374,8 @@ static fir::AliasAnalysis::Source mergeRegionBranchPredecessorSources(
mergedScopedOrigin.approximateSource |= scopedOrigin.approximateSource;
mergedScopedOrigin.accessPath.isApproximate |=
scopedOrigin.accessPath.isApproximate;
+ mergedScopedOrigin.accessPath.isOpaque |=
+ scopedOrigin.accessPath.isOpaque;
seenThisPred[idx] = true;
}
// Drop entries this predecessor did not match. Iterate in reverse
@@ -415,6 +424,8 @@ void AliasAnalysis::Source::AccessPath::print(llvm::raw_ostream &os) const {
os << "]";
if (isApproximate)
os << "(~)";
+ if (isOpaque)
+ os << "(opaque)";
}
void AliasAnalysis::Source::print(llvm::raw_ostream &os) const {
@@ -578,7 +589,8 @@ static bool noAliasBasedOnType(mlir::Value lhs, mlir::Value rhs) {
/// 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.
+/// at the second step. Component names are only compared between steps
+/// selecting fields of the same record type, and opaque paths never diverge.
///
/// When either path continues through a PointerDeref or AllocDeref after
/// the divergence point, the runtime address could potentially reach a
@@ -597,12 +609,16 @@ static bool noAliasBasedOnType(mlir::Value lhs, mlir::Value rhs) {
static bool pathsDivergeAtComponent(const fir::AliasAnalysis::Source &lhsSrc,
const fir::AliasAnalysis::Source &rhsSrc) {
using PathStep = fir::AliasAnalysis::Source::PathStep;
+ if (lhsSrc.accessPath.isOpaque || rhsSrc.accessPath.isOpaque)
+ return false;
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].recordType &&
+ lhsSteps[i].recordType == rhsSteps[i].recordType &&
lhsSteps[i].component != rhsSteps[i].component) {
auto hasPtrDerefAfter = [](llvm::ArrayRef<PathStep> steps, size_t from) {
for (size_t j = from; j < steps.size(); ++j)
@@ -783,8 +799,10 @@ AliasResult AliasAnalysis::alias(Source lhsSrc, Source rhsSrc, mlir::Value lhs,
llvm::dbgs() << " rhs: " << rhs << "\n";
llvm::dbgs() << " rhsSrc: " << rhsSrc << "\n";);
- // Disambiguate data and descriptors addresses.
- if (noAliasBasedOnType(lhs, rhs))
+ // Disambiguate data and descriptors addresses. An opaque access path may
+ // reinterpret a descriptor as data or vice versa.
+ if (!lhsSrc.accessPath.isOpaque && !rhsSrc.accessPath.isOpaque &&
+ noAliasBasedOnType(lhs, rhs))
return AliasResult::NoAlias;
// A null address aliases nothing. Same-value pairs already returned
@@ -887,6 +905,17 @@ AliasResult AliasAnalysis::alias(Source lhsSrc, Source rhsSrc, mlir::Value lhs,
return AliasResult::MayAlias;
}
+ // Whether a value addresses data or a descriptor is based on its type,
+ // which an opaque access path may have reinterpreted.
+ if (lhsSrc.origin.u == rhsSrc.origin.u &&
+ lhsSrc.origin.isData != rhsSrc.origin.isData &&
+ (lhsSrc.accessPath.isOpaque || rhsSrc.accessPath.isOpaque)) {
+ LLVM_DEBUG(llvm::dbgs()
+ << " aliasing between data and non-data with same source "
+ << "kind and origin value through an opaque access path\n");
+ return AliasResult::MayAlias;
+ }
+
// Two host-associated accesses may overlap through EQUIVALENCE, so return
// MayAlias conservatively. The exception is distinct data accesses where
// at least one is allocatable, since allocatables cannot be EQUIVALENCE
@@ -1507,6 +1536,271 @@ static mlir::Value walkBlockArgPassThroughs(mlir::Value v) {
return v;
}
+/// Return the type of the elements of the memory addressed by a value of
+/// type \p ty: the element type of the data described by a box, or of the
+/// memory pointed to by a reference. A reference to a box addresses the
+/// descriptor, so the box type is returned in that case. Return a null type
+/// if \p ty is not an address.
+static mlir::Type getAddressedElementType(mlir::Type ty) {
+ if (mlir::Type eleTy = fir::dyn_cast_ptrOrBoxEleTy(ty))
+ return fir::unwrapSequenceType(eleTy);
+ return {};
+}
+
+/// Return true if a view from \p srcTy to \p resTy maps every element of the
+/// source to an element of the result at the same address. Boxes carry the
+/// byte strides of the data they describe, so this holds for any box-to-box
+/// view. Otherwise, it only holds when neither side addresses an array,
+/// because indexing an array reference uses the size of its static element
+/// type.
+static bool isElementPreservingView(mlir::Type srcTy, mlir::Type resTy) {
+ if (mlir::isa<fir::BaseBoxType>(srcTy) && mlir::isa<fir::BaseBoxType>(resTy))
+ return true;
+ auto addressesArray = [](mlir::Type ty) {
+ return mlir::isa_and_present<fir::SequenceType>(
+ fir::dyn_cast_ptrOrBoxEleTy(ty));
+ };
+ return !addressesArray(srcTy) && !addressesArray(resTy);
+}
+
+/// If the subobject of type \p inner at offset zero of an \p outer object is
+/// reached by repeatedly selecting the first component (e.g. the parent
+/// component of a type extension), append the Component steps selecting it
+/// to \p chain (outermost first) and return true.
+static bool getFirstComponentChain(
+ fir::RecordType outer, mlir::Type inner,
+ llvm::SmallVectorImpl<AliasAnalysis::Source::PathStep> &chain) {
+ size_t initialSize = chain.size();
+ for (fir::RecordType recTy = outer; recTy && recTy.getNumFields() != 0;) {
+ const auto &[name, fieldTy] = recTy.getTypeList().front();
+ AliasAnalysis::Source::PathStep step;
+ step.kind = AliasAnalysis::Source::PathStep::Kind::Component;
+ step.component = mlir::StringAttr::get(recTy.getContext(), name);
+ step.recordType = recTy;
+ chain.push_back(step);
+ if (fieldTy == inner)
+ return true;
+ recTy = mlir::dyn_cast<fir::RecordType>(fieldTy);
+ }
+ chain.truncate(initialSize);
+ return false;
+}
+
+/// Return true if \p ty is a record type whose objects contain a \p baseTy
+/// subobject reached through first components, e.g. if \p ty is an
+/// extension of \p baseTy.
+static bool hasFirstComponentChainTo(mlir::Type ty, mlir::Type baseTy) {
+ auto recTy = mlir::dyn_cast_if_present<fir::RecordType>(ty);
+ llvm::SmallVector<AliasAnalysis::Source::PathStep, 2> chain;
+ return recTy && getFirstComponentChain(recTy, baseTy, chain);
+}
+
+/// Return true if access path steps selecting fields of \p frameTy are
+/// relative to an object of declared type \p eleTy. This is the case if the
+/// types are the same, or if the object is polymorphic and \p frameTy is an
+/// extension of \p eleTy: such steps come from a SELECT TYPE downcast, which
+/// is only executed when the dynamic type of the object is an extension of
+/// \p frameTy.
+static bool isPathFrameOf(mlir::Type frameTy, mlir::Type eleTy,
+ bool isPolymorphic) {
+ if (frameTy == eleTy)
+ return true;
+ if (!isPolymorphic)
+ return false;
+ return mlir::isa_and_present<mlir::NoneType>(eleTy) ||
+ hasFirstComponentChainTo(frameTy, eleTy);
+}
+
+/// Return true if a view from a value addressing elements of type
+/// \p srcEleTy to a value addressing elements of type \p resEleTy
+/// reinterprets a descriptor as describing data of an unrelated record type,
+/// reinterprets a descriptor as data, or reinterprets a derived type object
+/// as a descriptor. Untyped storage, e.g. of a COMMON block, may hold a
+/// descriptor, so viewing it as a descriptor does not reinterpret anything.
+static bool isDescriptorReinterpretation(mlir::Type srcEleTy,
+ mlir::Type resEleTy) {
+ auto srcBoxTy = mlir::dyn_cast_if_present<fir::BaseBoxType>(srcEleTy);
+ auto resBoxTy = mlir::dyn_cast_if_present<fir::BaseBoxType>(resEleTy);
+ if (srcBoxTy && resBoxTy) {
+ mlir::Type srcDataTy = getAddressedElementType(srcBoxTy);
+ mlir::Type resDataTy = getAddressedElementType(resBoxTy);
+ return srcDataTy != resDataTy &&
+ mlir::isa_and_present<fir::RecordType>(srcDataTy) &&
+ mlir::isa_and_present<fir::RecordType>(resDataTy);
+ }
+ if (srcBoxTy)
+ return true;
+ if (resBoxTy)
+ return mlir::isa_and_present<fir::RecordType>(srcEleTy);
+ return false;
+}
+
+/// Return true if all the memory addressed by the result of type \p resTy of
+/// the view \p op lies within the object addressed by its source of type
+/// \p srcTy. \p srcEleTy and \p resEleTy are the types of the elements
+/// addressed by the source, after applying the component path of the view
+/// slice, and by the result. Selecting elements or sections of the source,
+/// describing them with a box, and viewing an object as one of its ancestor
+/// types stay within the source. Any other change of the addressed type is
+/// assumed to reach outside of the source, even when the Fortran rules bound
+/// it, e.g. for the sequence association of an array element with a dummy
+/// array.
+static bool isContainedView(mlir::Operation *op, mlir::Type srcTy,
+ mlir::Type resTy, mlir::Type srcEleTy,
+ mlir::Type resEleTy) {
+ if (mlir::isa<fir::CreateBoxOp>(op))
+ return false;
+ if (mlir::isa<fir::ArrayCoorOp, fir::BoxAddrOp>(op))
+ return true;
+ if (mlir::isa<fir::BaseBoxType>(srcTy) && mlir::isa<fir::BaseBoxType>(resTy))
+ return true;
+ if (mlir::isa<fir::EmboxOp>(op))
+ return srcEleTy == resEleTy ||
+ mlir::isa_and_present<mlir::NoneType>(resEleTy) ||
+ hasFirstComponentChainTo(srcEleTy, resEleTy);
+ mlir::Type srcObjTy = fir::dyn_cast_ptrOrBoxEleTy(srcTy);
+ mlir::Type resObjTy = fir::dyn_cast_ptrOrBoxEleTy(resTy);
+ if (!srcObjTy || !resObjTy)
+ return false;
+ // A view of a descriptor as another descriptor. The views of the data
+ // described with an unrelated type are handled by
+ // isDescriptorReinterpretation.
+ if (mlir::isa<fir::BaseBoxType>(srcObjTy) &&
+ mlir::isa<fir::BaseBoxType>(resObjTy))
+ return true;
+ return srcObjTy == resObjTy || hasFirstComponentChainTo(srcObjTy, resObjTy);
+}
+
+/// Return true if \p path has a Component step.
+static bool hasComponentStep(const AliasAnalysis::Source::AccessPath &path) {
+ return llvm::any_of(path.steps, [](const AliasAnalysis::Source::PathStep &s) {
+ return s.kind == AliasAnalysis::Source::PathStep::Kind::Component;
+ });
+}
+
+/// Return the fir.slice operand of a view operation, if any.
+static mlir::Value getViewSlice(mlir::Operation *op) {
+ return llvm::TypeSwitch<mlir::Operation *, mlir::Value>(op)
+ .Case<fir::EmboxOp, fir::ReboxOp, fir::ArrayCoorOp>(
+ [](auto sliceableOp) -> mlir::Value {
+ return sliceableOp.getSlice();
+ })
+ .Default([](mlir::Operation *) { return mlir::Value{}; });
+}
+
+/// Append to \p steps (outermost first) the Component steps of the component
+/// path of \p slice, applied to elements of type \p eleTy, and update
+/// \p eleTy to the type of the selected subobjects. Return false if the path
+/// cannot be decoded.
+static bool
+decodeSlicePath(mlir::Value slice, mlir::Type &eleTy,
+ llvm::SmallVectorImpl<AliasAnalysis::Source::PathStep> &steps) {
+ auto sliceOp = slice.getDefiningOp<fir::SliceOp>();
+ if (!sliceOp)
+ return false;
+ unsigned dimension = 0;
+ for (mlir::Value field : sliceOp.getFields()) {
+ if (mlir::isa<fir::FieldType>(field.getType())) {
+ auto fieldOp = field.getDefiningOp<fir::FieldIndexOp>();
+ auto recTy = mlir::dyn_cast_if_present<fir::RecordType>(eleTy);
+ if (!fieldOp || !recTy || dimension != 0)
+ return false;
+ mlir::Type fieldTy = recTy.getType(fieldOp.getFieldName());
+ if (!fieldTy)
+ return false;
+ AliasAnalysis::Source::PathStep step;
+ step.kind = AliasAnalysis::Source::PathStep::Kind::Component;
+ step.component =
+ mlir::StringAttr::get(recTy.getContext(), fieldOp.getFieldName());
+ step.recordType = recTy;
+ steps.push_back(step);
+ eleTy = fieldTy;
+ continue;
+ }
+ // A subscript of an array component.
+ if (dimension == 0) {
+ auto seqTy = mlir::dyn_cast_if_present<fir::SequenceType>(eleTy);
+ if (!seqTy)
+ return false;
+ dimension = seqTy.getDimension();
+ }
+ if (--dimension == 0)
+ eleTy = mlir::cast<fir::SequenceType>(eleTy).getElementType();
+ }
+ if (dimension != 0)
+ return false;
+ eleTy = fir::unwrapSequenceType(eleTy);
+ return true;
+}
+
+/// getSourceImpl collects Component steps (\p pathSteps, leaf-to-root order)
+/// relative to the record type of the object addressed by the value being
+/// walked. When the walk crosses \p op from \p result to its view source,
+/// update \p pathSteps so that they stay relative to the object addressed by
+/// the view source:
+/// - a component path of a slice selects subobjects of the source
+/// elements, so its steps are appended;
+/// - an upcast view (e.g. CLASS(base) of an extended type object) implies
+/// the steps selecting the parent components, so they are appended;
+/// - the steps collected through a SELECT TYPE downcast of a polymorphic
+/// object stay relative to the extension type (see isPathFrameOf).
+/// \p crossedUncontainedView is set once the walk crosses a view that may
+/// address memory outside of its source (see isContainedView). The steps
+/// selecting subobjects of the source of such a view do not contain the
+/// memory addressed through it.
+/// Return false if the steps cannot be related to the source object, e.g.
+/// when the view reinterprets storage (EQUIVALENCE, COMMON).
+static bool adjustPathStepsForView(
+ fir::FortranObjectViewOpInterface op, mlir::OpResult result,
+ llvm::SmallVectorImpl<AliasAnalysis::Source::PathStep> &pathSteps,
+ bool &crossedUncontainedView) {
+ using PathStep = AliasAnalysis::Source::PathStep;
+ mlir::Type srcTy = op.getViewSource(result).getType();
+ mlir::Type resTy = result.getType();
+ mlir::Type srcEleTy = getAddressedElementType(srcTy);
+ mlir::Type resEleTy = getAddressedElementType(resTy);
+ if (isDescriptorReinterpretation(srcEleTy, resEleTy))
+ return false;
+ // The byte strides of fir.create_box may map its elements to arbitrary
+ // offsets within the source elements. They are not analyzed, because the
+ // only producer of the operation passes the strides of an existing
+ // descriptor.
+ if (mlir::isa<fir::CreateBoxOp>(op) && !pathSteps.empty())
+ return false;
+
+ llvm::SmallVector<PathStep, 2> sliceSteps;
+ if (mlir::Value slice = getViewSlice(op))
+ if (!decodeSlicePath(slice, srcEleTy, sliceSteps))
+ return false;
+ bool isContained = isContainedView(op, srcTy, resTy, srcEleTy, resEleTy);
+ size_t numSteps = pathSteps.size();
+
+ // The steps collected so far are relative to the elements addressed by the
+ // view result, or to an extension of their type (see isPathFrameOf).
+ if (!pathSteps.empty() && pathSteps.back().recordType != srcEleTy &&
+ (srcEleTy != resEleTy || !sliceSteps.empty())) {
+ mlir::Type frameTy = pathSteps.back().recordType;
+ if (!isElementPreservingView(srcTy, resTy))
+ return false;
+ llvm::SmallVector<PathStep, 2> chain;
+ auto srcRecTy = mlir::dyn_cast_if_present<fir::RecordType>(srcEleTy);
+ // Subobjects selected by a slice path are not polymorphic.
+ bool isPolymorphic = mlir::isa<fir::ClassType>(srcTy) && sliceSteps.empty();
+ if (srcRecTy && getFirstComponentChain(srcRecTy, frameTy, chain))
+ pathSteps.append(chain.rbegin(), chain.rend());
+ else if (!isPathFrameOf(frameTy, srcEleTy, isPolymorphic))
+ return false;
+ }
+ pathSteps.append(sliceSteps.rbegin(), sliceSteps.rend());
+ if (pathSteps.size() != numSteps &&
+ (crossedUncontainedView || !isContained)) {
+ pathSteps.truncate(numSteps);
+ return false;
+ }
+ crossedUncontainedView |= !isContained;
+ return true;
+}
+
void AliasAnalysis::enableSourceCache() { sourceCacheEnabled = true; }
void AliasAnalysis::disableSourceCache() {
@@ -1567,6 +1861,12 @@ AliasAnalysis::getSourceImpl(mlir::Value v, bool getLastInstantiationPoint,
// Reversed into the final AccessPath at the end, unless the box-load branch
// composes the full path directly.
llvm::SmallVector<Source::PathStep, 4> pathSteps;
+ // Set when pathSteps cannot be related to the object reached by the walk,
+ // e.g. after crossing a view that reinterprets storage.
+ bool pathOpaque{false};
+ // Set after crossing a view that may address memory outside of its source.
+ // The Component steps collected from then on do not contain the leaf.
+ bool crossedUncontainedView{false};
Source::AccessPath accessPath;
bool accessPathFinalized{false};
@@ -1576,6 +1876,14 @@ AliasAnalysis::getSourceImpl(mlir::Value v, bool getLastInstantiationPoint,
// the final Source. Gated on collectScopedOrigins (suppressed when
// buildSourceAtDeclare reuses getSource purely for declare classification).
llvm::SmallVector<Source::ScopedOrigin, 4> scopedOrigins;
+ // Make the path opaque, including the snapshots of it taken so far:
+ // buildSourceAtDeclare pairs a snapshot with the origin reached past its
+ // declare, so the views crossed after the snapshot apply to it too.
+ auto markPathOpaque = [&]() {
+ pathOpaque = true;
+ for (Source::ScopedOrigin &scopedOrigin : scopedOrigins)
+ scopedOrigin.accessPath.isOpaque = true;
+ };
while (defOp && !breakFromLoop) {
// Operations may have multiple results, so we need to analyze
// the result for which the source is queried.
@@ -1709,6 +2017,9 @@ AliasAnalysis::getSourceImpl(mlir::Value v, bool getLastInstantiationPoint,
accessPath.steps.push_back(pathSteps[i]);
accessPath.isApproximate =
boxSrc.accessPath.isApproximate || approximateSource;
+ if (boxSrc.accessPath.isOpaque)
+ markPathOpaque();
+ accessPath.isOpaque = pathOpaque;
accessPathFinalized = true;
// Rebase each forwarded ScopedOrigin from the inner walk's
@@ -1721,6 +2032,7 @@ AliasAnalysis::getSourceImpl(mlir::Value v, bool getLastInstantiationPoint,
for (int i = pathSteps.size() - 1; i >= 0; --i)
scopedOrigin.accessPath.steps.push_back(pathSteps[i]);
scopedOrigin.accessPath.isApproximate |= approximateSource;
+ scopedOrigin.accessPath.isOpaque |= pathOpaque;
if (isPointerBox)
scopedOrigin.attributes.set(Attribute::Pointer);
scopedOrigin.approximateSource |= approximateSource;
@@ -1874,6 +2186,7 @@ AliasAnalysis::getSourceImpl(mlir::Value v, bool getLastInstantiationPoint,
scopedOrigin.accessPath.steps.assign(pathSteps.rbegin(),
pathSteps.rend());
scopedOrigin.accessPath.isApproximate = approximateSource;
+ scopedOrigin.accessPath.isOpaque = pathOpaque;
scopedOrigin.attributes = attributes;
scopedOrigin.approximateSource = approximateSource;
scopedOrigin.isData = followingData;
@@ -1938,6 +2251,27 @@ AliasAnalysis::getSourceImpl(mlir::Value v, bool getLastInstantiationPoint,
// Record component access steps for the access path.
//
+ // The steps collected so far select subobjects of the object
+ // designated by hlfir.designate and fir.coordinate_of, unless
+ // the designated object is viewed with a different type in
+ // between.
+ if (mlir::isa<hlfir::DesignateOp, fir::CoordinateOp>(defOp) &&
+ !pathSteps.empty()) {
+ // An element of a polymorphic array is polymorphic.
+ bool isPolymorphic = mlir::isa<fir::ClassType>(ty);
+ if (auto coordOp = mlir::dyn_cast<fir::CoordinateOp>(defOp))
+ isPolymorphic =
+ mlir::isa<fir::ClassType>(coordOp.getRef().getType()) &&
+ llvm::all_of(coordOp.getFieldIndices().value_or(
+ llvm::ArrayRef<int32_t>{}),
+ [](int32_t idx) {
+ return idx == fir::CoordinateOp::kDynamicIndex;
+ });
+ if (!isPathFrameOf(pathSteps.back().recordType,
+ getAddressedElementType(ty), isPolymorphic))
+ markPathOpaque();
+ }
+
// 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)) {
@@ -1945,7 +2279,11 @@ AliasAnalysis::getSourceImpl(mlir::Value v, bool getLastInstantiationPoint,
Source::PathStep step;
step.kind = Source::PathStep::Kind::Component;
step.component = *comp;
+ step.recordType =
+ getAddressedElementType(designateOp.getMemref().getType());
pathSteps.push_back(step);
+ if (crossedUncontainedView)
+ markPathOpaque();
}
} else if (auto coordOp = mlir::dyn_cast<fir::CoordinateOp>(defOp)) {
// fir.coordinate_of encodes field accesses as integer indices
@@ -1962,7 +2300,8 @@ AliasAnalysis::getSourceImpl(mlir::Value v, bool getLastInstantiationPoint,
if (fieldIndices) {
mlir::Type currentTy =
fir::dyn_cast_ptrOrBoxEleTy(coordOp.getRef().getType());
- llvm::SmallVector<mlir::StringAttr, 4> componentNames;
+ llvm::SmallVector<Source::PathStep, 4> componentSteps;
+ bool decoded = true;
unsigned dimension = 0;
for (int32_t idx : *fieldIndices) {
if (idx == fir::CoordinateOp::kDynamicIndex) {
@@ -1983,29 +2322,39 @@ AliasAnalysis::getSourceImpl(mlir::Value v, bool getLastInstantiationPoint,
// Unexpected type structure; discard any partially
// collected names so the access path stays conservative
// rather than recording a misleading partial path.
- componentNames.clear();
+ decoded = false;
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();
+ decoded = false;
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);
+ step.component = mlir::StringAttr::get(defOp->getContext(),
+ typeList[idx].first);
+ step.recordType = recTy;
+ componentSteps.push_back(step);
+ currentTy = typeList[idx].second;
+ }
+ if (decoded) {
+ // pathSteps is in leaf-to-root order (reversed at the end),
+ // so push innermost component first.
+ pathSteps.append(componentSteps.rbegin(),
+ componentSteps.rend());
+ if (crossedUncontainedView && !componentSteps.empty())
+ markPathOpaque();
+ } else if (!pathSteps.empty()) {
+ // Dropping the names would misalign the steps collected
+ // so far with the steps collected from now on.
+ markPathOpaque();
}
}
+ } else if (!adjustPathStepsForView(op, opResult, pathSteps,
+ crossedUncontainedView)) {
+ markPathOpaque();
}
// Collect attributes from FortranVariableOpInterface operations.
@@ -2041,6 +2390,13 @@ AliasAnalysis::getSourceImpl(mlir::Value v, bool getLastInstantiationPoint,
collectScopedOrigins);
},
followingData, attributes);
+ if (crossedUncontainedView &&
+ hasComponentStep(accSourceReturn->accessPath))
+ markPathOpaque();
+ accSourceReturn->accessPath.isOpaque |= pathOpaque;
+ for (Source::ScopedOrigin &scopedOrigin :
+ accSourceReturn->scopedOrigins)
+ scopedOrigin.accessPath.isOpaque |= pathOpaque;
breakFromLoop = true;
})
.Case([&](mlir::RegionBranchOpInterface branch) {
@@ -2072,6 +2428,14 @@ AliasAnalysis::getSourceImpl(mlir::Value v, bool getLastInstantiationPoint,
regionBranchReturn->approximateSource |= approximateSource;
regionBranchReturn->isCapturedInInternalProcedure |=
isCapturedInInternalProcedure;
+ if (regionBranchReturn->accessPath.isOpaque ||
+ (crossedUncontainedView &&
+ hasComponentStep(regionBranchReturn->accessPath)))
+ markPathOpaque();
+ regionBranchReturn->accessPath.isOpaque |= pathOpaque;
+ for (Source::ScopedOrigin &scopedOrigin :
+ regionBranchReturn->scopedOrigins)
+ scopedOrigin.accessPath.isOpaque |= pathOpaque;
// Prepend the outer (leaf-closer) scopedOrigins -- declares
// already crossed between leaf and this region-branch op --
// to the merged predecessors' snapshots. The inner snapshots'
@@ -2130,6 +2494,7 @@ AliasAnalysis::getSourceImpl(mlir::Value v, bool getLastInstantiationPoint,
std::reverse(pathSteps.begin(), pathSteps.end());
accessPath.steps = std::move(pathSteps);
accessPath.isApproximate = approximateSource;
+ accessPath.isOpaque = pathOpaque;
}
if (type == SourceKind::Global) {
diff --git a/flang/test/Analysis/AliasAnalysis/alias-analysis-access-path-type-change.fir b/flang/test/Analysis/AliasAnalysis/alias-analysis-access-path-type-change.fir
new file mode 100644
index 0000000000000..c58079cce1608
--- /dev/null
+++ b/flang/test/Analysis/AliasAnalysis/alias-analysis-access-path-type-change.fir
@@ -0,0 +1,507 @@
+// Tests for access paths in fir::AliasAnalysis through views that change the
+// type of the addressed object: CLASS views of objects of extended types,
+// SELECT TYPE, storage association, component paths of array slices, and
+// descriptor casts.
+
+// RUN: fir-opt %s -split-input-file -o /dev/null --mlir-disable-threading \
+// RUN: -pass-pipeline='builtin.module(func.func(test-fir-alias-analysis))' \
+// RUN: 2>&1 | FileCheck -match-full-lines %s
+
+// -----
+
+// A CLASS(t1) view of an object of extended type t2, as created when a
+// type-bound procedure with a CLASS(t1) passed-object dummy is inlined.
+// view_a is obj%t1%a(1) reached through the view.
+
+// CHECK-LABEL: Testing : "_QPparent_view"
+// CHECK-DAG: direct_a#0 <-> direct_b#0: NoAlias
+// CHECK-DAG: direct_a#0 <-> view_a#0: MayAlias
+// CHECK-DAG: direct_b#0 <-> view_a#0: NoAlias
+
+!t1 = !fir.type<_QMmTt1{a:!fir.array<4xf32>}>
+!t2 = !fir.type<_QMmTt2{t1:!t1,b:f32}>
+func.func @_QPparent_view() {
+ %c1 = arith.constant 1 : index
+ %c4 = arith.constant 4 : index
+ %sh = fir.shape %c4 : (index) -> !fir.shape<1>
+ %0 = fir.alloca !t2 {bindc_name = "obj", uniq_name = "_QFparent_viewEobj"}
+ %obj = fir.declare %0 uniq_name("_QFparent_viewEobj") : (!fir.ref<!t2>) -> !fir.ref<!t2>
+ %parent = fir.coordinate_of %obj, t1 : (!fir.ref<!t2>) -> !fir.ref<!t1>
+ %pa = fir.coordinate_of %parent, a : (!fir.ref<!t1>) -> !fir.ref<!fir.array<4xf32>>
+ %direct_a = fir.array_coor %pa(%sh) %c1 {test.ptr = "direct_a"} : (!fir.ref<!fir.array<4xf32>>, !fir.shape<1>, index) -> !fir.ref<f32>
+ %direct_b = fir.coordinate_of %obj, b {test.ptr = "direct_b"} : (!fir.ref<!t2>) -> !fir.ref<f32>
+ %box = fir.embox %obj : (!fir.ref<!t2>) -> !fir.box<!t2>
+ %view = fir.convert %box : (!fir.box<!t2>) -> !fir.class<!t1>
+ %ds = fir.dummy_scope : !fir.dscope
+ %this = fir.declare %view dummy_scope %ds arg 1 uniq_name("_QMmFgetEthis") : (!fir.class<!t1>, !fir.dscope) -> !fir.class<!t1>
+ %va = fir.coordinate_of %this, a : (!fir.class<!t1>) -> !fir.ref<!fir.array<4xf32>>
+ %view_a = fir.array_coor %va(%sh) %c1 {test.ptr = "view_a"} : (!fir.ref<!fir.array<4xf32>>, !fir.shape<1>, index) -> !fir.ref<f32>
+ return
+}
+
+// -----
+
+// CLASS(t1) array views of an array of objects of extended type t2.
+
+// CHECK-LABEL: Testing : "_QParray_view"
+// CHECK-DAG: direct_a#0 <-> direct_b#0: NoAlias
+// CHECK-DAG: direct_a#0 <-> view_a#0: MayAlias
+// CHECK-DAG: direct_b#0 <-> view_a#0: NoAlias
+// CHECK-DAG: direct_a#0 <-> rebox_a#0: MayAlias
+// CHECK-DAG: direct_b#0 <-> rebox_a#0: NoAlias
+
+!t1 = !fir.type<_QMmTt1{a:!fir.array<4xf32>}>
+!t2 = !fir.type<_QMmTt2{t1:!t1,b:f32}>
+func.func @_QParray_view(%i: index) {
+ %c10 = arith.constant 10 : index
+ %sh = fir.shape %c10 : (index) -> !fir.shape<1>
+ %0 = fir.alloca !fir.array<10x!t2> {bindc_name = "arr", uniq_name = "_QFarray_viewEarr"}
+ %arr = fir.declare %0(%sh) uniq_name("_QFarray_viewEarr") : (!fir.ref<!fir.array<10x!t2>>, !fir.shape<1>) -> !fir.ref<!fir.array<10x!t2>>
+ %elt = fir.array_coor %arr(%sh) %i : (!fir.ref<!fir.array<10x!t2>>, !fir.shape<1>, index) -> !fir.ref<!t2>
+ %direct_a = fir.coordinate_of %elt, t1, a {test.ptr = "direct_a"} : (!fir.ref<!t2>) -> !fir.ref<!fir.array<4xf32>>
+ %direct_b = fir.coordinate_of %elt, b {test.ptr = "direct_b"} : (!fir.ref<!t2>) -> !fir.ref<f32>
+ %box = fir.embox %arr(%sh) : (!fir.ref<!fir.array<10x!t2>>, !fir.shape<1>) -> !fir.box<!fir.array<10x!t2>>
+ %view = fir.convert %box : (!fir.box<!fir.array<10x!t2>>) -> !fir.class<!fir.array<?x!t1>>
+ %velt = fir.array_coor %view %i : (!fir.class<!fir.array<?x!t1>>, index) -> !fir.ref<!t1>
+ %view_a = fir.coordinate_of %velt, a {test.ptr = "view_a"} : (!fir.ref<!t1>) -> !fir.ref<!fir.array<4xf32>>
+ %rebox = fir.rebox %box : (!fir.box<!fir.array<10x!t2>>) -> !fir.class<!fir.array<?x!t1>>
+ %relt = fir.array_coor %rebox %i : (!fir.class<!fir.array<?x!t1>>, index) -> !fir.ref<!t1>
+ %rebox_a = fir.coordinate_of %relt, a {test.ptr = "rebox_a"} : (!fir.ref<!t1>) -> !fir.ref<!fir.array<4xf32>>
+ return
+}
+
+// -----
+
+// A CLASS(t1) view of an object of type t3, which extends t2, which extends
+// t1. view_a is obj%t2%t1%a.
+
+// CHECK-LABEL: Testing : "_QPtwo_level"
+// CHECK-DAG: direct_a#0 <-> view_a#0: MayAlias
+// CHECK-DAG: direct_b#0 <-> view_a#0: NoAlias
+// CHECK-DAG: direct_c#0 <-> view_a#0: NoAlias
+
+!t1 = !fir.type<_QMmTt1{a:!fir.array<4xf32>}>
+!t2 = !fir.type<_QMmTt2{t1:!t1,b:f32}>
+!t3 = !fir.type<_QMmTt3{t2:!t2,c:f32}>
+func.func @_QPtwo_level() {
+ %0 = fir.alloca !t3 {bindc_name = "obj", uniq_name = "_QFtwo_levelEobj"}
+ %obj = fir.declare %0 uniq_name("_QFtwo_levelEobj") : (!fir.ref<!t3>) -> !fir.ref<!t3>
+ %direct_a = fir.coordinate_of %obj, t2, t1, a {test.ptr = "direct_a"} : (!fir.ref<!t3>) -> !fir.ref<!fir.array<4xf32>>
+ %direct_b = fir.coordinate_of %obj, t2, b {test.ptr = "direct_b"} : (!fir.ref<!t3>) -> !fir.ref<f32>
+ %direct_c = fir.coordinate_of %obj, c {test.ptr = "direct_c"} : (!fir.ref<!t3>) -> !fir.ref<f32>
+ %box = fir.embox %obj : (!fir.ref<!t3>) -> !fir.box<!t3>
+ %view = fir.convert %box : (!fir.box<!t3>) -> !fir.class<!t1>
+ %view_a = fir.coordinate_of %view, a {test.ptr = "view_a"} : (!fir.class<!t1>) -> !fir.ref<!fir.array<4xf32>>
+ return
+}
+
+// -----
+
+// SELECT TYPE with TYPE IS (t2) on the CLASS(t1) view of an object of type
+// t2, as created when the type-bound procedure is inlined. The selector
+// addresses the whole object again, so view_b is obj%b, and view_a is
+// obj%t1%a.
+
+// CHECK-LABEL: Testing : "_QPup_down"
+// CHECK-DAG: direct_a#0 <-> view_b#0: NoAlias
+// CHECK-DAG: direct_b#0 <-> view_b#0: MayAlias
+// CHECK-DAG: direct_a#0 <-> view_a#0: MayAlias
+// CHECK-DAG: direct_b#0 <-> view_a#0: NoAlias
+// CHECK-DAG: view_b#0 <-> view_a#0: NoAlias
+
+!t1 = !fir.type<_QMmTt1{a:!fir.array<4xf32>}>
+!t2 = !fir.type<_QMmTt2{t1:!t1,b:f32}>
+func.func @_QPup_down() {
+ %0 = fir.alloca !t2 {bindc_name = "obj", uniq_name = "_QFup_downEobj"}
+ %obj = fir.declare %0 uniq_name("_QFup_downEobj") : (!fir.ref<!t2>) -> !fir.ref<!t2>
+ %direct_a = fir.coordinate_of %obj, t1, a {test.ptr = "direct_a"} : (!fir.ref<!t2>) -> !fir.ref<!fir.array<4xf32>>
+ %direct_b = fir.coordinate_of %obj, b {test.ptr = "direct_b"} : (!fir.ref<!t2>) -> !fir.ref<f32>
+ %box = fir.embox %obj : (!fir.ref<!t2>) -> !fir.box<!t2>
+ %view = fir.convert %box : (!fir.box<!t2>) -> !fir.class<!t1>
+ %ds = fir.dummy_scope : !fir.dscope
+ %this = fir.declare %view dummy_scope %ds arg 1 uniq_name("_QMmFgetEthis") : (!fir.class<!t1>, !fir.dscope) -> !fir.class<!t1>
+ %addr = fir.box_addr %this : (!fir.class<!t1>) -> !fir.ref<!t2>
+ %sel = fir.declare %addr uniq_name("_QMmFgetEsel") : (!fir.ref<!t2>) -> !fir.ref<!t2>
+ %view_b = fir.coordinate_of %sel, b {test.ptr = "view_b"} : (!fir.ref<!t2>) -> !fir.ref<f32>
+ %view_a = fir.coordinate_of %sel, t1, a {test.ptr = "view_a"} : (!fir.ref<!t2>) -> !fir.ref<!fir.array<4xf32>>
+ return
+}
+
+// -----
+
+// SELECT TYPE with TYPE IS (t2) on a CLASS(t1) dummy:
+// select type (sel => this)
+// type is (t2)
+// ... sel%a ... this%a ... sel%b
+// sel%a and this%a are the same component. this%a and sel%b are disjoint,
+// but the analysis does not relate the components of the two views when
+// the dynamic type of the dummy is unknown.
+
+// CHECK-LABEL: Testing : "_QPsel"
+// CHECK-DAG: this_a#0 <-> sel_a#0: MayAlias
+// CHECK-DAG: this_a#0 <-> sel_b#0: MayAlias
+// CHECK-DAG: sel_a#0 <-> sel_b#0: NoAlias
+
+!t1 = !fir.type<_QMmTt1{a:!fir.array<4xf32>}>
+!t2 = !fir.type<_QMmTt2{t1:!t1,b:f32}>
+func.func @_QPsel(%arg0: !fir.class<!t1> {fir.bindc_name = "this"}) {
+ %c4 = arith.constant 4 : index
+ %sh = fir.shape %c4 : (index) -> !fir.shape<1>
+ %ds = fir.dummy_scope : !fir.dscope
+ %this:2 = hlfir.declare %arg0 dummy_scope %ds arg 1 uniq_name("_QFselEthis") : (!fir.class<!t1>, !fir.dscope) -> (!fir.class<!t1>, !fir.class<!t1>)
+ %addr = fir.box_addr %this#1 : (!fir.class<!t1>) -> !fir.ref<!t2>
+ %sel:2 = hlfir.declare %addr uniq_name("_QFselEsel") : (!fir.ref<!t2>) -> (!fir.ref<!t2>, !fir.ref<!t2>)
+ %this_a = hlfir.designate %this#0{"a"} shape %sh {test.ptr = "this_a"} : (!fir.class<!t1>, !fir.shape<1>) -> !fir.ref<!fir.array<4xf32>>
+ %parent = hlfir.designate %sel#0{"t1"} : (!fir.ref<!t2>) -> !fir.ref<!t1>
+ %sel_a = hlfir.designate %parent{"a"} shape %sh {test.ptr = "sel_a"} : (!fir.ref<!t1>, !fir.shape<1>) -> !fir.ref<!fir.array<4xf32>>
+ %sel_b = hlfir.designate %sel#0{"b"} {test.ptr = "sel_b"} : (!fir.ref<!t2>) -> !fir.ref<f32>
+ return
+}
+
+// -----
+
+// CLASS(*) views of an object of type t2, and SELECT TYPE on them with
+// TYPE IS (t2) (view_b is obj%b) and CLASS IS (t1) (view_a is obj%t1%a).
+
+// CHECK-LABEL: Testing : "_QPunlimited"
+// CHECK-DAG: direct_a#0 <-> view_b#0: NoAlias
+// CHECK-DAG: direct_b#0 <-> view_b#0: MayAlias
+// CHECK-DAG: direct_a#0 <-> view_a#0: MayAlias
+// CHECK-DAG: direct_b#0 <-> view_a#0: NoAlias
+// CHECK-DAG: view_b#0 <-> view_a#0: NoAlias
+
+!t1 = !fir.type<_QMmTt1{a:!fir.array<4xf32>}>
+!t2 = !fir.type<_QMmTt2{t1:!t1,b:f32}>
+func.func @_QPunlimited() {
+ %0 = fir.alloca !t2 {bindc_name = "obj", uniq_name = "_QFunlimitedEobj"}
+ %obj = fir.declare %0 uniq_name("_QFunlimitedEobj") : (!fir.ref<!t2>) -> !fir.ref<!t2>
+ %direct_a = fir.coordinate_of %obj, t1, a {test.ptr = "direct_a"} : (!fir.ref<!t2>) -> !fir.ref<!fir.array<4xf32>>
+ %direct_b = fir.coordinate_of %obj, b {test.ptr = "direct_b"} : (!fir.ref<!t2>) -> !fir.ref<f32>
+ %box = fir.embox %obj : (!fir.ref<!t2>) -> !fir.box<!t2>
+ %any = fir.convert %box : (!fir.box<!t2>) -> !fir.class<none>
+ %addr = fir.box_addr %any : (!fir.class<none>) -> !fir.ref<!t2>
+ %view_b = fir.coordinate_of %addr, b {test.ptr = "view_b"} : (!fir.ref<!t2>) -> !fir.ref<f32>
+ %cls = fir.convert %any : (!fir.class<none>) -> !fir.class<!t1>
+ %view_a = fir.coordinate_of %cls, a {test.ptr = "view_a"} : (!fir.class<!t1>) -> !fir.ref<!fir.array<4xf32>>
+ return
+}
+
+// -----
+
+// EQUIVALENCE of variables of different sequence types:
+// type(s1) :: x ! components a, b
+// type(s2) :: y ! components c, d
+// equivalence (x, y)
+// x%b and y%d are the same memory. The component paths of the two variables
+// cannot be related, so even x%a and x%b are not disambiguated.
+
+// CHECK-LABEL: Testing : "_QPequiv"
+// CHECK-DAG: x_a#0 <-> y_d#0: MayAlias
+// CHECK-DAG: x_a#0 <-> x_b#0: MayAlias
+// CHECK-DAG: y_d#0 <-> x_b#0: MayAlias
+
+!s1 = !fir.type<_QFequivTs1,sequence{a:f32,b:f32}>
+!s2 = !fir.type<_QFequivTs2,sequence{c:f32,d:f32}>
+func.func @_QPequiv() {
+ %c0 = arith.constant 0 : index
+ %0 = fir.alloca !fir.array<8xi8> {uniq_name = "_QFequivEx"}
+ %1 = fir.coordinate_of %0, %c0 : (!fir.ref<!fir.array<8xi8>>, index) -> !fir.ref<i8>
+ %2 = fir.convert %1 : (!fir.ref<i8>) -> !fir.ptr<!s1>
+ %x = fir.declare %2 storage(%0[0]) uniq_name("_QFequivEx") : (!fir.ptr<!s1>, !fir.ref<!fir.array<8xi8>>) -> !fir.ptr<!s1>
+ %3 = fir.coordinate_of %0, %c0 : (!fir.ref<!fir.array<8xi8>>, index) -> !fir.ref<i8>
+ %4 = fir.convert %3 : (!fir.ref<i8>) -> !fir.ptr<!s2>
+ %y = fir.declare %4 storage(%0[0]) uniq_name("_QFequivEy") : (!fir.ptr<!s2>, !fir.ref<!fir.array<8xi8>>) -> !fir.ptr<!s2>
+ %x_a = fir.coordinate_of %x, a {test.ptr = "x_a"} : (!fir.ptr<!s1>) -> !fir.ref<f32>
+ %y_d = fir.coordinate_of %y, d {test.ptr = "y_d"} : (!fir.ptr<!s2>) -> !fir.ref<f32>
+ %x_b = fir.coordinate_of %x, b {test.ptr = "x_b"} : (!fir.ptr<!s1>) -> !fir.ref<f32>
+ return
+}
+
+// -----
+
+// EQUIVALENCE of arrays of the same sequence type at different offsets:
+// type(s1) :: x(2), y(2) ! components a, b
+// real :: z(4)
+// equivalence (x(1), z(1)), (y(1), z(2))
+// x(1)%b and y(1)%a are the same memory.
+
+// CHECK-LABEL: Testing : "_QPequiv_shift"
+// CHECK-DAG: x_b#0 <-> y_a#0: MayAlias
+
+!s1 = !fir.type<_QFequiv_shiftTs1,sequence{a:f32,b:f32}>
+func.func @_QPequiv_shift() {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %c2 = arith.constant 2 : index
+ %c4 = arith.constant 4 : index
+ %0 = fir.alloca !fir.array<20xi8> {uniq_name = "_QFequiv_shiftEx"}
+ %sh = fir.shape %c2 : (index) -> !fir.shape<1>
+ %1 = fir.coordinate_of %0, %c0 : (!fir.ref<!fir.array<20xi8>>, index) -> !fir.ref<i8>
+ %2 = fir.convert %1 : (!fir.ref<i8>) -> !fir.ptr<!fir.array<2x!s1>>
+ %x = fir.declare %2(%sh) storage(%0[0]) uniq_name("_QFequiv_shiftEx") : (!fir.ptr<!fir.array<2x!s1>>, !fir.shape<1>, !fir.ref<!fir.array<20xi8>>) -> !fir.ptr<!fir.array<2x!s1>>
+ %3 = fir.coordinate_of %0, %c4 : (!fir.ref<!fir.array<20xi8>>, index) -> !fir.ref<i8>
+ %4 = fir.convert %3 : (!fir.ref<i8>) -> !fir.ptr<!fir.array<2x!s1>>
+ %y = fir.declare %4(%sh) storage(%0[4]) uniq_name("_QFequiv_shiftEy") : (!fir.ptr<!fir.array<2x!s1>>, !fir.shape<1>, !fir.ref<!fir.array<20xi8>>) -> !fir.ptr<!fir.array<2x!s1>>
+ %x1 = fir.array_coor %x(%sh) %c1 : (!fir.ptr<!fir.array<2x!s1>>, !fir.shape<1>, index) -> !fir.ref<!s1>
+ %x_b = fir.coordinate_of %x1, b {test.ptr = "x_b"} : (!fir.ref<!s1>) -> !fir.ref<f32>
+ %y1 = fir.array_coor %y(%sh) %c1 : (!fir.ptr<!fir.array<2x!s1>>, !fir.shape<1>, index) -> !fir.ref<!s1>
+ %y_a = fir.coordinate_of %y1, a {test.ptr = "y_a"} : (!fir.ref<!s1>) -> !fir.ref<f32>
+ return
+}
+
+// -----
+
+// A component reinterpreted as an object of a larger type. The
+// reinterpreted object overlaps the sibling component that follows: pun_r
+// and obj%d are the same memory.
+
+// CHECK-LABEL: Testing : "_QPpun"
+// CHECK-DAG: pun_r#0 <-> direct_d#0: MayAlias
+
+!r = !fir.type<_QMmTr{c:!fir.array<2xf32>,d:f32}>
+!s = !fir.type<_QMmTs{p:f32,q:f32,r:f32}>
+func.func @_QPpun() {
+ %0 = fir.alloca !r {bindc_name = "obj", uniq_name = "_QFpunEobj"}
+ %obj = fir.declare %0 uniq_name("_QFpunEobj") : (!fir.ref<!r>) -> !fir.ref<!r>
+ %c = fir.coordinate_of %obj, c : (!fir.ref<!r>) -> !fir.ref<!fir.array<2xf32>>
+ %pun = fir.convert %c : (!fir.ref<!fir.array<2xf32>>) -> !fir.ref<!s>
+ %pun_r = fir.coordinate_of %pun, r {test.ptr = "pun_r"} : (!fir.ref<!s>) -> !fir.ref<f32>
+ %direct_d = fir.coordinate_of %obj, d {test.ptr = "direct_d"} : (!fir.ref<!r>) -> !fir.ref<f32>
+ return
+}
+
+// -----
+
+// Views of the component section x%in of an array x of type outer:
+// slice_q and coor_q are x(k)%in%q.
+
+// CHECK-LABEL: Testing : "_QPslice_path"
+// CHECK-DAG: direct_q#0 <-> direct_j#0: NoAlias
+// CHECK-DAG: direct_q#0 <-> slice_q#0: MayAlias
+// CHECK-DAG: direct_j#0 <-> slice_q#0: NoAlias
+// CHECK-DAG: direct_p#0 <-> slice_q#0: NoAlias
+// CHECK-DAG: direct_q#0 <-> coor_q#0: MayAlias
+// CHECK-DAG: direct_j#0 <-> coor_q#0: NoAlias
+// CHECK-DAG: direct_p#0 <-> coor_q#0: NoAlias
+
+!inner = !fir.type<_QMmTinner{p:f32,q:f32}>
+!outer = !fir.type<_QMmTouter{in:!inner,j:f32}>
+func.func @_QPslice_path(%arg0: !fir.ref<!fir.array<10x!outer>> {fir.bindc_name = "x"}, %k: index) {
+ %c1 = arith.constant 1 : index
+ %c10 = arith.constant 10 : index
+ %sh = fir.shape %c10 : (index) -> !fir.shape<1>
+ %ds = fir.dummy_scope : !fir.dscope
+ %x = fir.declare %arg0(%sh) dummy_scope %ds arg 1 uniq_name("_QFslice_pathEx") : (!fir.ref<!fir.array<10x!outer>>, !fir.shape<1>, !fir.dscope) -> !fir.ref<!fir.array<10x!outer>>
+ %elt = fir.array_coor %x(%sh) %k : (!fir.ref<!fir.array<10x!outer>>, !fir.shape<1>, index) -> !fir.ref<!outer>
+ %direct_q = fir.coordinate_of %elt, in, q {test.ptr = "direct_q"} : (!fir.ref<!outer>) -> !fir.ref<f32>
+ %direct_j = fir.coordinate_of %elt, j {test.ptr = "direct_j"} : (!fir.ref<!outer>) -> !fir.ref<f32>
+ %direct_p = fir.coordinate_of %elt, in, p {test.ptr = "direct_p"} : (!fir.ref<!outer>) -> !fir.ref<f32>
+ %field = fir.field_index in, !outer
+ %slice = fir.slice %c1, %c10, %c1 path %field : (index, index, index, !fir.field) -> !fir.slice<1>
+ %box = fir.embox %x(%sh) [%slice] : (!fir.ref<!fir.array<10x!outer>>, !fir.shape<1>, !fir.slice<1>) -> !fir.box<!fir.array<10x!inner>>
+ %selt = fir.array_coor %box %k : (!fir.box<!fir.array<10x!inner>>, index) -> !fir.ref<!inner>
+ %slice_q = fir.coordinate_of %selt, q {test.ptr = "slice_q"} : (!fir.ref<!inner>) -> !fir.ref<f32>
+ %celt = fir.array_coor %x(%sh) [%slice] %k : (!fir.ref<!fir.array<10x!outer>>, !fir.shape<1>, !fir.slice<1>, index) -> !fir.ref<!inner>
+ %coor_q = fir.coordinate_of %celt, q {test.ptr = "coor_q"} : (!fir.ref<!inner>) -> !fir.ref<f32>
+ return
+}
+
+// -----
+
+// A pointer component descriptor reinterpreted as a descriptor of data of
+// an unrelated type.
+
+// CHECK-LABEL: Testing : "_QPdescriptor_cast"
+// CHECK-DAG: direct_a#0 <-> cast_y#0: MayAlias
+
+!t1 = !fir.type<_QMmTt1{a:!fir.array<4xf32>}>
+!u = !fir.type<_QMmTu{x:f32,y:f32}>
+!h = !fir.type<_QMmTh{p:!fir.box<!fir.ptr<!t1>>,k:f32}>
+func.func @_QPdescriptor_cast() {
+ %0 = fir.alloca !h {bindc_name = "obj", uniq_name = "_QFdescriptor_castEobj"}
+ %obj = fir.declare %0 uniq_name("_QFdescriptor_castEobj") : (!fir.ref<!h>) -> !fir.ref<!h>
+ %p = fir.coordinate_of %obj, p : (!fir.ref<!h>) -> !fir.ref<!fir.box<!fir.ptr<!t1>>>
+ %pbox = fir.load %p : !fir.ref<!fir.box<!fir.ptr<!t1>>>
+ %paddr = fir.box_addr %pbox : (!fir.box<!fir.ptr<!t1>>) -> !fir.ptr<!t1>
+ %direct_a = fir.coordinate_of %paddr, a {test.ptr = "direct_a"} : (!fir.ptr<!t1>) -> !fir.ref<!fir.array<4xf32>>
+ %cast = fir.convert %p : (!fir.ref<!fir.box<!fir.ptr<!t1>>>) -> !fir.ref<!fir.box<!fir.ptr<!u>>>
+ %cbox = fir.load %cast : !fir.ref<!fir.box<!fir.ptr<!u>>>
+ %caddr = fir.box_addr %cbox : (!fir.box<!fir.ptr<!u>>) -> !fir.ptr<!u>
+ %cast_y = fir.coordinate_of %caddr, y {test.ptr = "cast_y"} : (!fir.ptr<!u>) -> !fir.ref<f32>
+ return
+}
+
+// -----
+
+// The same EQUIVALENCE as in _QPequiv_shift after inlining, so that the
+// declarations of x and y are compared by the scope-aware refinement. The
+// views crossed after the declarations of x and y reinterpret the storage
+// for them too.
+
+// CHECK-LABEL: Testing : "_QPequiv_shift_scopes"
+// CHECK-DAG: x_b#0 <-> y_a#0: MayAlias
+
+!s1 = !fir.type<_QFequiv_shift_scopesTs1,sequence{a:f32,b:f32}>
+func.func @_QPequiv_shift_scopes() {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %c2 = arith.constant 2 : index
+ %c4 = arith.constant 4 : index
+ %ds = fir.dummy_scope : !fir.dscope
+ %0 = fir.alloca !fir.array<20xi8> {uniq_name = "_QFequiv_shift_scopesEx"}
+ %sh = fir.shape %c2 : (index) -> !fir.shape<1>
+ %1 = fir.coordinate_of %0, %c0 : (!fir.ref<!fir.array<20xi8>>, index) -> !fir.ref<i8>
+ %2 = fir.convert %1 : (!fir.ref<i8>) -> !fir.ptr<!fir.array<2x!s1>>
+ %x = fir.declare %2(%sh) storage(%0[0]) uniq_name("_QFequiv_shift_scopesEx") : (!fir.ptr<!fir.array<2x!s1>>, !fir.shape<1>, !fir.ref<!fir.array<20xi8>>) -> !fir.ptr<!fir.array<2x!s1>>
+ %3 = fir.coordinate_of %0, %c4 : (!fir.ref<!fir.array<20xi8>>, index) -> !fir.ref<i8>
+ %4 = fir.convert %3 : (!fir.ref<i8>) -> !fir.ptr<!fir.array<2x!s1>>
+ %y = fir.declare %4(%sh) storage(%0[4]) uniq_name("_QFequiv_shift_scopesEy") : (!fir.ptr<!fir.array<2x!s1>>, !fir.shape<1>, !fir.ref<!fir.array<20xi8>>) -> !fir.ptr<!fir.array<2x!s1>>
+ %ds_inlined = fir.dummy_scope : !fir.dscope
+ %x1 = fir.array_coor %x(%sh) %c1 : (!fir.ptr<!fir.array<2x!s1>>, !fir.shape<1>, index) -> !fir.ref<!s1>
+ %x_b = fir.coordinate_of %x1, b {test.ptr = "x_b"} : (!fir.ref<!s1>) -> !fir.ref<f32>
+ %y1 = fir.array_coor %y(%sh) %c1 : (!fir.ptr<!fir.array<2x!s1>>, !fir.shape<1>, index) -> !fir.ref<!s1>
+ %y_a = fir.coordinate_of %y1, a {test.ptr = "y_a"} : (!fir.ref<!s1>) -> !fir.ref<f32>
+ return
+}
+
+// -----
+
+// The two-element array component c viewed as a three-element array: the
+// third element of the view, view_3, is obj%d.
+
+// CHECK-LABEL: Testing : "_QPview_element"
+// CHECK-DAG: view_3#0 <-> direct_d#0: MayAlias
+// CHECK-DAG: direct_c2#0 <-> direct_d#0: NoAlias
+
+!r = !fir.type<_QMmTr{c:!fir.array<2xf32>,d:f32}>
+func.func @_QPview_element() {
+ %c2 = arith.constant 2 : index
+ %c3 = arith.constant 3 : index
+ %sh2 = fir.shape %c2 : (index) -> !fir.shape<1>
+ %sh3 = fir.shape %c3 : (index) -> !fir.shape<1>
+ %0 = fir.alloca !r {bindc_name = "obj", uniq_name = "_QFview_elementEobj"}
+ %obj = fir.declare %0 uniq_name("_QFview_elementEobj") : (!fir.ref<!r>) -> !fir.ref<!r>
+ %c = fir.coordinate_of %obj, c : (!fir.ref<!r>) -> !fir.ref<!fir.array<2xf32>>
+ %view = fir.convert %c : (!fir.ref<!fir.array<2xf32>>) -> !fir.ref<!fir.array<3xf32>>
+ %view_3 = fir.array_coor %view(%sh3) %c3 {test.ptr = "view_3"} : (!fir.ref<!fir.array<3xf32>>, !fir.shape<1>, index) -> !fir.ref<f32>
+ %direct_c2 = fir.array_coor %c(%sh2) %c2 {test.ptr = "direct_c2"} : (!fir.ref<!fir.array<2xf32>>, !fir.shape<1>, index) -> !fir.ref<f32>
+ %direct_d = fir.coordinate_of %obj, d {test.ptr = "direct_d"} : (!fir.ref<!r>) -> !fir.ref<f32>
+ return
+}
+
+// -----
+
+// The component c viewed as an object of a larger type: the whole view,
+// view, overlaps obj%d.
+
+// CHECK-LABEL: Testing : "_QPview_scalar"
+// CHECK-DAG: view#0 <-> direct_d#0: MayAlias
+
+!r = !fir.type<_QMmTr{c:f32,d:f32}>
+!bigger = !fir.type<_QMmTbigger{x:f32,y:f32}>
+func.func @_QPview_scalar() {
+ %0 = fir.alloca !r {bindc_name = "obj", uniq_name = "_QFview_scalarEobj"}
+ %obj = fir.declare %0 uniq_name("_QFview_scalarEobj") : (!fir.ref<!r>) -> !fir.ref<!r>
+ %c = fir.coordinate_of %obj, c : (!fir.ref<!r>) -> !fir.ref<f32>
+ %view = fir.convert %c {test.ptr = "view"} : (!fir.ref<f32>) -> !fir.ref<!bigger>
+ %direct_d = fir.coordinate_of %obj, d {test.ptr = "direct_d"} : (!fir.ref<!r>) -> !fir.ref<f32>
+ return
+}
+
+// -----
+
+// Sequence association of an element of an array component with an
+// explicit-shape dummy array, after inlining:
+// type r
+// real :: c(2), d
+// end type
+// call foo(obj%c(2))
+// subroutine foo(x)
+// real :: x(2)
+// x(2) is obj%d.
+
+// CHECK-LABEL: Testing : "_QPsequence_association"
+// CHECK-DAG: x_2#0 <-> obj_d#0: MayAlias
+
+!r = !fir.type<_QMmTr{c:!fir.array<2xf32>,d:f32}>
+func.func @_QPsequence_association() {
+ %c2 = arith.constant 2 : index
+ %0 = fir.alloca !r {bindc_name = "obj", uniq_name = "_QFsequence_associationEobj"}
+ %obj:2 = hlfir.declare %0 uniq_name("_QFsequence_associationEobj") : (!fir.ref<!r>) -> (!fir.ref<!r>, !fir.ref<!r>)
+ %sh = fir.shape %c2 : (index) -> !fir.shape<1>
+ %c_2 = hlfir.designate %obj#0{"c"} <%sh> (%c2) : (!fir.ref<!r>, !fir.shape<1>, index) -> !fir.ref<f32>
+ %actual = fir.convert %c_2 : (!fir.ref<f32>) -> !fir.ref<!fir.array<2xf32>>
+ %ds = fir.dummy_scope : !fir.dscope
+ %x:2 = hlfir.declare %actual(%sh) dummy_scope %ds arg 1 uniq_name("_QFfooEx") : (!fir.ref<!fir.array<2xf32>>, !fir.shape<1>, !fir.dscope) -> (!fir.ref<!fir.array<2xf32>>, !fir.ref<!fir.array<2xf32>>)
+ %x_2 = hlfir.designate %x#0 (%c2) {test.ptr = "x_2"} : (!fir.ref<!fir.array<2xf32>>, index) -> !fir.ref<f32>
+ %obj_d = hlfir.designate %obj#0{"d"} {test.ptr = "obj_d"} : (!fir.ref<!r>) -> !fir.ref<f32>
+ return
+}
+
+// -----
+
+// A pointer component descriptor reinterpreted as a derived type object:
+// view_word is the first word of the descriptor of obj%p.
+
+// CHECK-LABEL: Testing : "_QPdescriptor_view"
+// CHECK-DAG: desc#0 <-> view_word#0: MayAlias
+
+!t1 = !fir.type<_QMmTt1{a:!fir.array<4xf32>}>
+!w = !fir.type<_QMmTw{word:i64}>
+!h = !fir.type<_QMmTh{p:!fir.box<!fir.ptr<!t1>>,k:f32}>
+func.func @_QPdescriptor_view() {
+ %0 = fir.alloca !h {bindc_name = "obj", uniq_name = "_QFdescriptor_viewEobj"}
+ %obj = fir.declare %0 uniq_name("_QFdescriptor_viewEobj") : (!fir.ref<!h>) -> !fir.ref<!h>
+ %desc = fir.coordinate_of %obj, p {test.ptr = "desc"} : (!fir.ref<!h>) -> !fir.ref<!fir.box<!fir.ptr<!t1>>>
+ %view = fir.convert %desc : (!fir.ref<!fir.box<!fir.ptr<!t1>>>) -> !fir.ref<!w>
+ %view_word = fir.coordinate_of %view, word {test.ptr = "view_word"} : (!fir.ref<!w>) -> !fir.ref<i64>
+ return
+}
+
+// -----
+
+// A pointer component descriptor reinterpreted as an integer: word is the
+// first word of the descriptor of obj%p. It is reached through another
+// access to obj%p, so that it does not trivially address the same memory as
+// desc.
+
+// CHECK-LABEL: Testing : "_QPdescriptor_word"
+// CHECK-DAG: desc#0 <-> word#0: MayAlias
+
+!t1 = !fir.type<_QMmTt1{a:!fir.array<4xf32>}>
+!h = !fir.type<_QMmTh{p:!fir.box<!fir.ptr<!t1>>,k:f32}>
+func.func @_QPdescriptor_word() {
+ %0 = fir.alloca !h {bindc_name = "obj", uniq_name = "_QFdescriptor_wordEobj"}
+ %obj = fir.declare %0 uniq_name("_QFdescriptor_wordEobj") : (!fir.ref<!h>) -> !fir.ref<!h>
+ %desc = fir.coordinate_of %obj, p {test.ptr = "desc"} : (!fir.ref<!h>) -> !fir.ref<!fir.box<!fir.ptr<!t1>>>
+ %desc2 = fir.coordinate_of %obj, p : (!fir.ref<!h>) -> !fir.ref<!fir.box<!fir.ptr<!t1>>>
+ %word = fir.convert %desc2 {test.ptr = "word"} : (!fir.ref<!fir.box<!fir.ptr<!t1>>>) -> !fir.ref<i64>
+ return
+}
+
+// -----
+
+// A box with a byte stride smaller than the size of its elements: the
+// first component of its second element, box_a, is the second component of
+// the first element of the array, direct_b.
+
+// CHECK-LABEL: Testing : "_QPcreate_box_stride"
+// CHECK-DAG: box_a#0 <-> direct_b#0: MayAlias
+// CHECK-DAG: direct_a#0 <-> direct_b#0: NoAlias
+
+!s = !fir.type<_QMmTs{a:f32,b:f32}>
+func.func @_QPcreate_box_stride() {
+ %c1 = arith.constant 1 : index
+ %c2 = arith.constant 2 : index
+ %c4 = arith.constant 4 : index
+ %sh = fir.shape %c2 : (index) -> !fir.shape<1>
+ %0 = fir.alloca !fir.array<2x!s> {bindc_name = "arr", uniq_name = "_QFcreate_box_strideEarr"}
+ %arr = fir.declare %0(%sh) uniq_name("_QFcreate_box_strideEarr") : (!fir.ref<!fir.array<2x!s>>, !fir.shape<1>) -> !fir.ref<!fir.array<2x!s>>
+ %box = fir.create_box %arr lbs(%c1) extents(%c2) strides(%c4) : (!fir.ref<!fir.array<2x!s>>, index, index, index) -> !fir.box<!fir.array<2x!s>>
+ %belt = fir.array_coor %box %c2 : (!fir.box<!fir.array<2x!s>>, index) -> !fir.ref<!s>
+ %box_a = fir.coordinate_of %belt, a {test.ptr = "box_a"} : (!fir.ref<!s>) -> !fir.ref<f32>
+ %elt = fir.array_coor %arr(%sh) %c1 : (!fir.ref<!fir.array<2x!s>>, !fir.shape<1>, index) -> !fir.ref<!s>
+ %direct_a = fir.coordinate_of %elt, a {test.ptr = "direct_a"} : (!fir.ref<!s>) -> !fir.ref<f32>
+ %direct_b = fir.coordinate_of %elt, b {test.ptr = "direct_b"} : (!fir.ref<!s>) -> !fir.ref<f32>
+ return
+}
diff --git a/flang/test/HLFIR/inline-hlfir-assign-select-type.fir b/flang/test/HLFIR/inline-hlfir-assign-select-type.fir
new file mode 100644
index 0000000000000..1c064cc379951
--- /dev/null
+++ b/flang/test/HLFIR/inline-hlfir-assign-select-type.fir
@@ -0,0 +1,51 @@
+// Test that hlfir.assign is not inlined when its LHS and RHS are overlapping
+// parts of the same object accessed through views of different types.
+// RUN: fir-opt --inline-hlfir-assign %s | FileCheck %s
+
+// The assignment comes from the following source:
+// module m
+// type base
+// real :: v(4)
+// end type
+// type, extends(base) :: child
+// real :: extra(4)
+// end type
+// contains
+// subroutine shift(this)
+// class(base), intent(inout) :: this
+// select type (a => this)
+// type is (child)
+// a%v(2:4) = this%v(1:3)
+// end select
+// end subroutine
+// end module
+// a%v is a%base%v, which overlaps this%v.
+
+!base = !fir.type<_QMmTbase{v:!fir.array<4xf32>}>
+!child = !fir.type<_QMmTchild{base:!base,extra:!fir.array<4xf32>}>
+func.func @_QMmPshift(%arg0: !fir.class<!base> {fir.bindc_name = "this"}) {
+ %c1 = arith.constant 1 : index
+ %c2 = arith.constant 2 : index
+ %c3 = arith.constant 3 : index
+ %c4 = arith.constant 4 : index
+ %0 = fir.dummy_scope : !fir.dscope
+ %1:2 = hlfir.declare %arg0 dummy_scope %0 arg 1 uniq_name("_QMmFshiftEthis") fortran_attrs<intent_inout> : (!fir.class<!base>, !fir.dscope) -> (!fir.class<!base>, !fir.class<!base>)
+ fir.select_type %1#1 : !fir.class<!base> [#fir.type_is<!child>, ^bb1, unit, ^bb2]
+^bb1:
+ %2 = fir.box_addr %1#1 : (!fir.class<!base>) -> !fir.ref<!child>
+ %3:2 = hlfir.declare %2 uniq_name("_QMmFshiftEa") : (!fir.ref<!child>) -> (!fir.ref<!child>, !fir.ref<!child>)
+ %4 = fir.shape %c4 : (index) -> !fir.shape<1>
+ %5 = fir.shape %c3 : (index) -> !fir.shape<1>
+ %6 = hlfir.designate %1#0{"v"} <%4> (%c1:%c3:%c1) shape %5 : (!fir.class<!base>, !fir.shape<1>, index, index, index, !fir.shape<1>) -> !fir.ref<!fir.array<3xf32>>
+ %7 = hlfir.designate %3#0{"base"} : (!fir.ref<!child>) -> !fir.ref<!base>
+ %8 = hlfir.designate %7{"v"} <%4> (%c2:%c4:%c1) shape %5 : (!fir.ref<!base>, !fir.shape<1>, index, index, index, !fir.shape<1>) -> !fir.ref<!fir.array<3xf32>>
+ hlfir.assign %6 to %8 : !fir.ref<!fir.array<3xf32>>, !fir.ref<!fir.array<3xf32>>
+ cf.br ^bb2
+^bb2:
+ return
+}
+// CHECK-LABEL: func.func @_QMmPshift(
+// CHECK-NOT: fir.do_loop
+// CHECK: hlfir.assign %{{.*}} to %{{.*}} : !fir.ref<!fir.array<3xf32>>, !fir.ref<!fir.array<3xf32>>
+// CHECK-NOT: fir.do_loop
+// CHECK: return
>From 32125bd29ab5b0e4186402eddce6b69dd5cb43fd Mon Sep 17 00:00:00 2001
From: Slava Zakharin <szakharin at nvidia.com>
Date: Wed, 14 Oct 2026 10:05:09 -0700
Subject: [PATCH 2/3] [flang] Compare FIR alias analysis access path components
by record type
Replace the adjustment of the access paths through views and the opaque
paths with a comparison of the component steps by record type, as
suggested in review:
- components of the same record type are disjoint if their names
differ;
- components of record types related by type extension are disjoint if
neither type occurs in the component selected in the other type;
- components of other pairs of record types are not compared, since
SEQUENCE and BIND(C) types defined in different scopes may be the
same Fortran type.
Objects of the same type may only partially overlap through storage
association, so an access path also records the first EQUIVALENCE or
COMMON declaration crossed by the walk, and only paths crossing the
same declaration are compared.
The comparison assumes that the address of a component is never
converted to the address of an object extending beyond the component,
which holds for Fortran, so the tests of such conversions are removed.
The component path of a fir.slice is no longer decoded, so accesses
through such a view are not disambiguated from the other components of
the array elements.
Co-authored-by: Jean Perier <jperier at nvidia.com>
Assisted-by: Cursor
---
.../flang/Optimizer/Analysis/AliasAnalysis.h | 31 +-
.../lib/Optimizer/Analysis/AliasAnalysis.cpp | 556 +++++-------------
...lias-analysis-access-path-record-type.fir} | 357 +++++------
3 files changed, 312 insertions(+), 632 deletions(-)
rename flang/test/Analysis/AliasAnalysis/{alias-analysis-access-path-type-change.fir => alias-analysis-access-path-record-type.fir} (70%)
diff --git a/flang/include/flang/Optimizer/Analysis/AliasAnalysis.h b/flang/include/flang/Optimizer/Analysis/AliasAnalysis.h
index ecf504385a181..40a9032dbe102 100644
--- a/flang/include/flang/Optimizer/Analysis/AliasAnalysis.h
+++ b/flang/include/flang/Optimizer/Analysis/AliasAnalysis.h
@@ -186,10 +186,7 @@ struct AliasAnalysis {
/// record type). Null for non-Component steps.
mlir::StringAttr component;
/// For Component steps: the record type that `component` is a field
- /// of. Component names are only comparable between steps that select
- /// fields of the same record type: the same object may be accessed
- /// through views of different types (e.g. CLASS(base) views of an
- /// extended type object). Null for non-Component steps.
+ /// of. Null for non-Component steps.
mlir::Type recordType;
bool operator==(const PathStep &o) const {
@@ -218,23 +215,11 @@ struct AliasAnalysis {
/// MustAlias.
bool isApproximate{false};
- /// Whether the component steps of the path may not contain the
- /// queried memory location, so they cannot prove that two accesses
- /// are disjoint. This happens when the path goes through:
- /// - a view that reinterprets memory as an unrelated type, e.g.
- /// storage association through EQUIVALENCE or COMMON, so that the
- /// steps collected after the view do not describe the layout of
- /// the root variable;
- /// - a view that may address memory outside of its source, e.g. an
- /// array element viewed as an array (sequence association) or a
- /// box with arbitrary byte strides, followed by steps selecting
- /// the subobject of the root variable that contains the source;
- /// - a view that reinterprets a descriptor as data or vice versa, so
- /// that the location may not be told apart from a descriptor based
- /// on its type.
- /// The snapshots of the path taken at declarations (see ScopedOrigin)
- /// inherit the opacity of the views crossed after them.
- bool isOpaque{false};
+ /// The storage-associated (EQUIVALENCE/COMMON) declaration crossed by
+ /// the path, if any. Objects of the same type may partially overlap
+ /// only through storage association, so component steps are only
+ /// compared between paths crossing the same such declaration.
+ mlir::Operation *storageDecl{nullptr};
/// Return true if any step is a PointerDeref.
bool hasPointerDeref() const {
@@ -244,8 +229,8 @@ struct AliasAnalysis {
}
bool operator==(const AccessPath &o) const {
- return isApproximate == o.isApproximate && isOpaque == o.isOpaque &&
- steps == o.steps;
+ return isApproximate == o.isApproximate &&
+ storageDecl == o.storageDecl && steps == o.steps;
}
bool operator!=(const AccessPath &o) const { return !(*this == o); }
diff --git a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
index b5b0e691b9c15..5664e414b2bf0 100644
--- a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
+++ b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
@@ -321,13 +321,9 @@ static fir::AliasAnalysis::Source mergeRegionBranchPredecessorSources(
} else {
mergedPath = {};
mergedPath.isApproximate = true;
- // The snapshots taken before reaching the region-branch op are merged
- // with this path, so they need to know that some predecessor path is
- // opaque.
- mergedPath.isOpaque =
- llvm::any_of(sources, [](const fir::AliasAnalysis::Source &s) {
- return s.accessPath.isOpaque;
- });
+ for (const fir::AliasAnalysis::Source &s : sources)
+ if (!mergedPath.storageDecl)
+ mergedPath.storageDecl = s.accessPath.storageDecl;
}
mlir::Type mergedTy = allTypesSame ? sources[0].valueType : fallbackType;
@@ -374,8 +370,6 @@ static fir::AliasAnalysis::Source mergeRegionBranchPredecessorSources(
mergedScopedOrigin.approximateSource |= scopedOrigin.approximateSource;
mergedScopedOrigin.accessPath.isApproximate |=
scopedOrigin.accessPath.isApproximate;
- mergedScopedOrigin.accessPath.isOpaque |=
- scopedOrigin.accessPath.isOpaque;
seenThisPred[idx] = true;
}
// Drop entries this predecessor did not match. Iterate in reverse
@@ -424,8 +418,6 @@ void AliasAnalysis::Source::AccessPath::print(llvm::raw_ostream &os) const {
os << "]";
if (isApproximate)
os << "(~)";
- if (isOpaque)
- os << "(opaque)";
}
void AliasAnalysis::Source::print(llvm::raw_ostream &os) const {
@@ -584,16 +576,90 @@ static bool noAliasBasedOnType(mlir::Value lhs, mlir::Value rhs) {
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. Component names are only compared between steps
-/// selecting fields of the same record type, and opaque paths never diverge.
+/// Return true if an object of type \p ty contains, or is, an object of type
+/// \p subTy (through array elements and components, not through
+/// descriptors).
+static bool typeContains(mlir::Type ty, mlir::Type subTy) {
+ if (ty == subTy)
+ return true;
+ if (auto seqTy = mlir::dyn_cast<fir::SequenceType>(ty))
+ return typeContains(seqTy.getElementType(), subTy);
+ if (auto recTy = mlir::dyn_cast<fir::RecordType>(ty))
+ return llvm::any_of(recTy.getTypeList(), [&](const auto &field) {
+ return typeContains(field.second, subTy);
+ });
+ return false;
+}
+
+/// Return the type of the parent component of \p recTy, if any. The parent
+/// component is the first component, named after the parent type.
+static fir::RecordType getParentType(fir::RecordType recTy) {
+ if (recTy.getNumFields() == 0)
+ return {};
+ const auto &[name, fieldTy] = recTy.getTypeList().front();
+ auto parentTy = mlir::dyn_cast<fir::RecordType>(fieldTy);
+ if (!parentTy ||
+ fir::NameUniquer::deconstruct(parentTy.getName()).second.name != name)
+ return {};
+ return parentTy;
+}
+
+/// Return true if \p ty is an extension of \p ancestor. Extensible types
+/// are never SEQUENCE or BIND(C) types, so they are only the same Fortran
+/// type when they are the same FIR type.
+static bool isProperExtensionOf(fir::RecordType ty, fir::RecordType ancestor) {
+ for (fir::RecordType parent = getParentType(ty); parent;
+ parent = getParentType(parent))
+ if (parent == ancestor)
+ return true;
+ return false;
+}
+
+/// Return true if the component \p lhs of an object A and the component
+/// \p rhs of an object B are disjoint. Without storage association, two
+/// objects are either disjoint, or one is a subobject of the other, or they
+/// are the same object (only if they have the same type). So the components
+/// are disjoint if they are different components of the same type, or if
+/// neither of A and B may be a subobject of the component of the other.
+/// This assumes that the address of a component is never converted to the
+/// address of an object extending beyond the component.
+static bool
+componentsAreDisjoint(const fir::AliasAnalysis::Source::PathStep &lhs,
+ const fir::AliasAnalysis::Source::PathStep &rhs) {
+ if (!lhs.recordType || !rhs.recordType)
+ return false;
+ if (lhs.recordType == rhs.recordType)
+ return lhs.component != rhs.component;
+ auto lhsRecTy = mlir::dyn_cast<fir::RecordType>(lhs.recordType);
+ auto rhsRecTy = mlir::dyn_cast<fir::RecordType>(rhs.recordType);
+ // Different FIR types may be the same SEQUENCE or BIND(C) Fortran type.
+ if (!lhsRecTy || !rhsRecTy ||
+ (!isProperExtensionOf(lhsRecTy, rhsRecTy) &&
+ !isProperExtensionOf(rhsRecTy, lhsRecTy)))
+ return false;
+ mlir::Type lhsFieldTy = lhsRecTy.getType(lhs.component.getValue());
+ mlir::Type rhsFieldTy = rhsRecTy.getType(rhs.component.getValue());
+ if (!lhsFieldTy || !rhsFieldTy)
+ return false;
+ return !typeContains(lhsFieldTy, rhs.recordType) &&
+ !typeContains(rhsFieldTy, lhs.recordType);
+}
+
+/// Return true if two access paths from the same origin variable address
+/// disjoint subobjects of the root variable. The paths are not compared step
+/// by step: views of the root (e.g. CLASS views of an object of extended
+/// type, or SELECT TYPE selectors) change the type of the addressed object,
+/// so steps at the same index may be components of different types. Instead,
+/// the paths are disjoint if a component step of one path is disjoint from a
+/// component step of the other path (see componentsAreDisjoint). For
+/// example, paths [Component("a")] and [Component("b")] of the same record
+/// type are disjoint, while [Component("a"), Component("x")] and
+/// [Component("a"), Component("y")] are disjoint at their second steps.
+/// Paths crossing different storage-associated declarations are not
+/// compared.
///
/// When either path continues through a PointerDeref or AllocDeref after
-/// the divergence point, the runtime address could potentially reach a
+/// the disjoint steps, 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
@@ -603,44 +669,43 @@ static bool noAliasBasedOnType(mlir::Value lhs, mlir::Value rhs) {
/// 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.
+/// One exception: if BOTH sides continue through a PointerDeref, the two
+/// pointers could independently be associated with the same third-party
+/// TARGET variable, so such steps do not prove disjointness.
static bool pathsDivergeAtComponent(const fir::AliasAnalysis::Source &lhsSrc,
const fir::AliasAnalysis::Source &rhsSrc) {
using PathStep = fir::AliasAnalysis::Source::PathStep;
- if (lhsSrc.accessPath.isOpaque || rhsSrc.accessPath.isOpaque)
+ if (lhsSrc.accessPath.storageDecl != rhsSrc.accessPath.storageDecl)
return false;
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].recordType &&
- lhsSteps[i].recordType == rhsSteps[i].recordType &&
- 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);
+ auto hasPtrDeref = [](llvm::ArrayRef<PathStep> steps) {
+ return llvm::any_of(steps, [](const PathStep &s) {
+ return s.kind == PathStep::Kind::PointerDeref;
+ });
+ };
+ for (size_t i = 0; i < lhsSteps.size(); ++i) {
+ if (lhsSteps[i].kind != PathStep::Kind::Component)
+ continue;
+ for (size_t j = 0; j < rhsSteps.size(); ++j) {
+ if (rhsSteps[j].kind != PathStep::Kind::Component ||
+ !componentsAreDisjoint(lhsSteps[i], rhsSteps[j]))
+ continue;
+ bool lhsHasPtrDeref =
+ hasPtrDeref(llvm::ArrayRef<PathStep>(lhsSteps).drop_front(i + 1));
+ bool rhsHasPtrDeref =
+ hasPtrDeref(llvm::ArrayRef<PathStep>(rhsSteps).drop_front(j + 1));
if (lhsHasPtrDeref && rhsHasPtrDeref)
- return false;
+ continue;
if (lhsHasPtrDeref || rhsHasPtrDeref) {
- for (size_t j = 0; j < i; ++j)
- if (lhsSteps[j].kind == PathStep::Kind::PointerDeref)
- return false;
+ if (hasPtrDeref(llvm::ArrayRef<PathStep>(lhsSteps).take_front(i)) ||
+ hasPtrDeref(llvm::ArrayRef<PathStep>(rhsSteps).take_front(j)))
+ continue;
if (lhsSrc.isTarget() || rhsSrc.isTarget())
- return false;
+ continue;
}
return true;
}
- if (lhsSteps[i] != rhsSteps[i])
- break;
}
return false;
}
@@ -799,10 +864,8 @@ AliasResult AliasAnalysis::alias(Source lhsSrc, Source rhsSrc, mlir::Value lhs,
llvm::dbgs() << " rhs: " << rhs << "\n";
llvm::dbgs() << " rhsSrc: " << rhsSrc << "\n";);
- // Disambiguate data and descriptors addresses. An opaque access path may
- // reinterpret a descriptor as data or vice versa.
- if (!lhsSrc.accessPath.isOpaque && !rhsSrc.accessPath.isOpaque &&
- noAliasBasedOnType(lhs, rhs))
+ // Disambiguate data and descriptors addresses.
+ if (noAliasBasedOnType(lhs, rhs))
return AliasResult::NoAlias;
// A null address aliases nothing. Same-value pairs already returned
@@ -905,17 +968,6 @@ AliasResult AliasAnalysis::alias(Source lhsSrc, Source rhsSrc, mlir::Value lhs,
return AliasResult::MayAlias;
}
- // Whether a value addresses data or a descriptor is based on its type,
- // which an opaque access path may have reinterpreted.
- if (lhsSrc.origin.u == rhsSrc.origin.u &&
- lhsSrc.origin.isData != rhsSrc.origin.isData &&
- (lhsSrc.accessPath.isOpaque || rhsSrc.accessPath.isOpaque)) {
- LLVM_DEBUG(llvm::dbgs()
- << " aliasing between data and non-data with same source "
- << "kind and origin value through an opaque access path\n");
- return AliasResult::MayAlias;
- }
-
// Two host-associated accesses may overlap through EQUIVALENCE, so return
// MayAlias conservatively. The exception is distinct data accesses where
// at least one is allocatable, since allocatables cannot be EQUIVALENCE
@@ -1536,271 +1588,6 @@ static mlir::Value walkBlockArgPassThroughs(mlir::Value v) {
return v;
}
-/// Return the type of the elements of the memory addressed by a value of
-/// type \p ty: the element type of the data described by a box, or of the
-/// memory pointed to by a reference. A reference to a box addresses the
-/// descriptor, so the box type is returned in that case. Return a null type
-/// if \p ty is not an address.
-static mlir::Type getAddressedElementType(mlir::Type ty) {
- if (mlir::Type eleTy = fir::dyn_cast_ptrOrBoxEleTy(ty))
- return fir::unwrapSequenceType(eleTy);
- return {};
-}
-
-/// Return true if a view from \p srcTy to \p resTy maps every element of the
-/// source to an element of the result at the same address. Boxes carry the
-/// byte strides of the data they describe, so this holds for any box-to-box
-/// view. Otherwise, it only holds when neither side addresses an array,
-/// because indexing an array reference uses the size of its static element
-/// type.
-static bool isElementPreservingView(mlir::Type srcTy, mlir::Type resTy) {
- if (mlir::isa<fir::BaseBoxType>(srcTy) && mlir::isa<fir::BaseBoxType>(resTy))
- return true;
- auto addressesArray = [](mlir::Type ty) {
- return mlir::isa_and_present<fir::SequenceType>(
- fir::dyn_cast_ptrOrBoxEleTy(ty));
- };
- return !addressesArray(srcTy) && !addressesArray(resTy);
-}
-
-/// If the subobject of type \p inner at offset zero of an \p outer object is
-/// reached by repeatedly selecting the first component (e.g. the parent
-/// component of a type extension), append the Component steps selecting it
-/// to \p chain (outermost first) and return true.
-static bool getFirstComponentChain(
- fir::RecordType outer, mlir::Type inner,
- llvm::SmallVectorImpl<AliasAnalysis::Source::PathStep> &chain) {
- size_t initialSize = chain.size();
- for (fir::RecordType recTy = outer; recTy && recTy.getNumFields() != 0;) {
- const auto &[name, fieldTy] = recTy.getTypeList().front();
- AliasAnalysis::Source::PathStep step;
- step.kind = AliasAnalysis::Source::PathStep::Kind::Component;
- step.component = mlir::StringAttr::get(recTy.getContext(), name);
- step.recordType = recTy;
- chain.push_back(step);
- if (fieldTy == inner)
- return true;
- recTy = mlir::dyn_cast<fir::RecordType>(fieldTy);
- }
- chain.truncate(initialSize);
- return false;
-}
-
-/// Return true if \p ty is a record type whose objects contain a \p baseTy
-/// subobject reached through first components, e.g. if \p ty is an
-/// extension of \p baseTy.
-static bool hasFirstComponentChainTo(mlir::Type ty, mlir::Type baseTy) {
- auto recTy = mlir::dyn_cast_if_present<fir::RecordType>(ty);
- llvm::SmallVector<AliasAnalysis::Source::PathStep, 2> chain;
- return recTy && getFirstComponentChain(recTy, baseTy, chain);
-}
-
-/// Return true if access path steps selecting fields of \p frameTy are
-/// relative to an object of declared type \p eleTy. This is the case if the
-/// types are the same, or if the object is polymorphic and \p frameTy is an
-/// extension of \p eleTy: such steps come from a SELECT TYPE downcast, which
-/// is only executed when the dynamic type of the object is an extension of
-/// \p frameTy.
-static bool isPathFrameOf(mlir::Type frameTy, mlir::Type eleTy,
- bool isPolymorphic) {
- if (frameTy == eleTy)
- return true;
- if (!isPolymorphic)
- return false;
- return mlir::isa_and_present<mlir::NoneType>(eleTy) ||
- hasFirstComponentChainTo(frameTy, eleTy);
-}
-
-/// Return true if a view from a value addressing elements of type
-/// \p srcEleTy to a value addressing elements of type \p resEleTy
-/// reinterprets a descriptor as describing data of an unrelated record type,
-/// reinterprets a descriptor as data, or reinterprets a derived type object
-/// as a descriptor. Untyped storage, e.g. of a COMMON block, may hold a
-/// descriptor, so viewing it as a descriptor does not reinterpret anything.
-static bool isDescriptorReinterpretation(mlir::Type srcEleTy,
- mlir::Type resEleTy) {
- auto srcBoxTy = mlir::dyn_cast_if_present<fir::BaseBoxType>(srcEleTy);
- auto resBoxTy = mlir::dyn_cast_if_present<fir::BaseBoxType>(resEleTy);
- if (srcBoxTy && resBoxTy) {
- mlir::Type srcDataTy = getAddressedElementType(srcBoxTy);
- mlir::Type resDataTy = getAddressedElementType(resBoxTy);
- return srcDataTy != resDataTy &&
- mlir::isa_and_present<fir::RecordType>(srcDataTy) &&
- mlir::isa_and_present<fir::RecordType>(resDataTy);
- }
- if (srcBoxTy)
- return true;
- if (resBoxTy)
- return mlir::isa_and_present<fir::RecordType>(srcEleTy);
- return false;
-}
-
-/// Return true if all the memory addressed by the result of type \p resTy of
-/// the view \p op lies within the object addressed by its source of type
-/// \p srcTy. \p srcEleTy and \p resEleTy are the types of the elements
-/// addressed by the source, after applying the component path of the view
-/// slice, and by the result. Selecting elements or sections of the source,
-/// describing them with a box, and viewing an object as one of its ancestor
-/// types stay within the source. Any other change of the addressed type is
-/// assumed to reach outside of the source, even when the Fortran rules bound
-/// it, e.g. for the sequence association of an array element with a dummy
-/// array.
-static bool isContainedView(mlir::Operation *op, mlir::Type srcTy,
- mlir::Type resTy, mlir::Type srcEleTy,
- mlir::Type resEleTy) {
- if (mlir::isa<fir::CreateBoxOp>(op))
- return false;
- if (mlir::isa<fir::ArrayCoorOp, fir::BoxAddrOp>(op))
- return true;
- if (mlir::isa<fir::BaseBoxType>(srcTy) && mlir::isa<fir::BaseBoxType>(resTy))
- return true;
- if (mlir::isa<fir::EmboxOp>(op))
- return srcEleTy == resEleTy ||
- mlir::isa_and_present<mlir::NoneType>(resEleTy) ||
- hasFirstComponentChainTo(srcEleTy, resEleTy);
- mlir::Type srcObjTy = fir::dyn_cast_ptrOrBoxEleTy(srcTy);
- mlir::Type resObjTy = fir::dyn_cast_ptrOrBoxEleTy(resTy);
- if (!srcObjTy || !resObjTy)
- return false;
- // A view of a descriptor as another descriptor. The views of the data
- // described with an unrelated type are handled by
- // isDescriptorReinterpretation.
- if (mlir::isa<fir::BaseBoxType>(srcObjTy) &&
- mlir::isa<fir::BaseBoxType>(resObjTy))
- return true;
- return srcObjTy == resObjTy || hasFirstComponentChainTo(srcObjTy, resObjTy);
-}
-
-/// Return true if \p path has a Component step.
-static bool hasComponentStep(const AliasAnalysis::Source::AccessPath &path) {
- return llvm::any_of(path.steps, [](const AliasAnalysis::Source::PathStep &s) {
- return s.kind == AliasAnalysis::Source::PathStep::Kind::Component;
- });
-}
-
-/// Return the fir.slice operand of a view operation, if any.
-static mlir::Value getViewSlice(mlir::Operation *op) {
- return llvm::TypeSwitch<mlir::Operation *, mlir::Value>(op)
- .Case<fir::EmboxOp, fir::ReboxOp, fir::ArrayCoorOp>(
- [](auto sliceableOp) -> mlir::Value {
- return sliceableOp.getSlice();
- })
- .Default([](mlir::Operation *) { return mlir::Value{}; });
-}
-
-/// Append to \p steps (outermost first) the Component steps of the component
-/// path of \p slice, applied to elements of type \p eleTy, and update
-/// \p eleTy to the type of the selected subobjects. Return false if the path
-/// cannot be decoded.
-static bool
-decodeSlicePath(mlir::Value slice, mlir::Type &eleTy,
- llvm::SmallVectorImpl<AliasAnalysis::Source::PathStep> &steps) {
- auto sliceOp = slice.getDefiningOp<fir::SliceOp>();
- if (!sliceOp)
- return false;
- unsigned dimension = 0;
- for (mlir::Value field : sliceOp.getFields()) {
- if (mlir::isa<fir::FieldType>(field.getType())) {
- auto fieldOp = field.getDefiningOp<fir::FieldIndexOp>();
- auto recTy = mlir::dyn_cast_if_present<fir::RecordType>(eleTy);
- if (!fieldOp || !recTy || dimension != 0)
- return false;
- mlir::Type fieldTy = recTy.getType(fieldOp.getFieldName());
- if (!fieldTy)
- return false;
- AliasAnalysis::Source::PathStep step;
- step.kind = AliasAnalysis::Source::PathStep::Kind::Component;
- step.component =
- mlir::StringAttr::get(recTy.getContext(), fieldOp.getFieldName());
- step.recordType = recTy;
- steps.push_back(step);
- eleTy = fieldTy;
- continue;
- }
- // A subscript of an array component.
- if (dimension == 0) {
- auto seqTy = mlir::dyn_cast_if_present<fir::SequenceType>(eleTy);
- if (!seqTy)
- return false;
- dimension = seqTy.getDimension();
- }
- if (--dimension == 0)
- eleTy = mlir::cast<fir::SequenceType>(eleTy).getElementType();
- }
- if (dimension != 0)
- return false;
- eleTy = fir::unwrapSequenceType(eleTy);
- return true;
-}
-
-/// getSourceImpl collects Component steps (\p pathSteps, leaf-to-root order)
-/// relative to the record type of the object addressed by the value being
-/// walked. When the walk crosses \p op from \p result to its view source,
-/// update \p pathSteps so that they stay relative to the object addressed by
-/// the view source:
-/// - a component path of a slice selects subobjects of the source
-/// elements, so its steps are appended;
-/// - an upcast view (e.g. CLASS(base) of an extended type object) implies
-/// the steps selecting the parent components, so they are appended;
-/// - the steps collected through a SELECT TYPE downcast of a polymorphic
-/// object stay relative to the extension type (see isPathFrameOf).
-/// \p crossedUncontainedView is set once the walk crosses a view that may
-/// address memory outside of its source (see isContainedView). The steps
-/// selecting subobjects of the source of such a view do not contain the
-/// memory addressed through it.
-/// Return false if the steps cannot be related to the source object, e.g.
-/// when the view reinterprets storage (EQUIVALENCE, COMMON).
-static bool adjustPathStepsForView(
- fir::FortranObjectViewOpInterface op, mlir::OpResult result,
- llvm::SmallVectorImpl<AliasAnalysis::Source::PathStep> &pathSteps,
- bool &crossedUncontainedView) {
- using PathStep = AliasAnalysis::Source::PathStep;
- mlir::Type srcTy = op.getViewSource(result).getType();
- mlir::Type resTy = result.getType();
- mlir::Type srcEleTy = getAddressedElementType(srcTy);
- mlir::Type resEleTy = getAddressedElementType(resTy);
- if (isDescriptorReinterpretation(srcEleTy, resEleTy))
- return false;
- // The byte strides of fir.create_box may map its elements to arbitrary
- // offsets within the source elements. They are not analyzed, because the
- // only producer of the operation passes the strides of an existing
- // descriptor.
- if (mlir::isa<fir::CreateBoxOp>(op) && !pathSteps.empty())
- return false;
-
- llvm::SmallVector<PathStep, 2> sliceSteps;
- if (mlir::Value slice = getViewSlice(op))
- if (!decodeSlicePath(slice, srcEleTy, sliceSteps))
- return false;
- bool isContained = isContainedView(op, srcTy, resTy, srcEleTy, resEleTy);
- size_t numSteps = pathSteps.size();
-
- // The steps collected so far are relative to the elements addressed by the
- // view result, or to an extension of their type (see isPathFrameOf).
- if (!pathSteps.empty() && pathSteps.back().recordType != srcEleTy &&
- (srcEleTy != resEleTy || !sliceSteps.empty())) {
- mlir::Type frameTy = pathSteps.back().recordType;
- if (!isElementPreservingView(srcTy, resTy))
- return false;
- llvm::SmallVector<PathStep, 2> chain;
- auto srcRecTy = mlir::dyn_cast_if_present<fir::RecordType>(srcEleTy);
- // Subobjects selected by a slice path are not polymorphic.
- bool isPolymorphic = mlir::isa<fir::ClassType>(srcTy) && sliceSteps.empty();
- if (srcRecTy && getFirstComponentChain(srcRecTy, frameTy, chain))
- pathSteps.append(chain.rbegin(), chain.rend());
- else if (!isPathFrameOf(frameTy, srcEleTy, isPolymorphic))
- return false;
- }
- pathSteps.append(sliceSteps.rbegin(), sliceSteps.rend());
- if (pathSteps.size() != numSteps &&
- (crossedUncontainedView || !isContained)) {
- pathSteps.truncate(numSteps);
- return false;
- }
- crossedUncontainedView |= !isContained;
- return true;
-}
-
void AliasAnalysis::enableSourceCache() { sourceCacheEnabled = true; }
void AliasAnalysis::disableSourceCache() {
@@ -1861,12 +1648,6 @@ AliasAnalysis::getSourceImpl(mlir::Value v, bool getLastInstantiationPoint,
// Reversed into the final AccessPath at the end, unless the box-load branch
// composes the full path directly.
llvm::SmallVector<Source::PathStep, 4> pathSteps;
- // Set when pathSteps cannot be related to the object reached by the walk,
- // e.g. after crossing a view that reinterprets storage.
- bool pathOpaque{false};
- // Set after crossing a view that may address memory outside of its source.
- // The Component steps collected from then on do not contain the leaf.
- bool crossedUncontainedView{false};
Source::AccessPath accessPath;
bool accessPathFinalized{false};
@@ -1876,13 +1657,17 @@ AliasAnalysis::getSourceImpl(mlir::Value v, bool getLastInstantiationPoint,
// the final Source. Gated on collectScopedOrigins (suppressed when
// buildSourceAtDeclare reuses getSource purely for declare classification).
llvm::SmallVector<Source::ScopedOrigin, 4> scopedOrigins;
- // Make the path opaque, including the snapshots of it taken so far:
- // buildSourceAtDeclare pairs a snapshot with the origin reached past its
- // declare, so the views crossed after the snapshot apply to it too.
- auto markPathOpaque = [&]() {
- pathOpaque = true;
+ // The first storage-associated declaration crossed by the walk. The
+ // snapshots taken before crossing it describe storage-associated objects
+ // too.
+ mlir::Operation *storageDecl{nullptr};
+ auto setStorageDecl = [&](mlir::Operation *decl) {
+ if (storageDecl || !decl)
+ return;
+ storageDecl = decl;
for (Source::ScopedOrigin &scopedOrigin : scopedOrigins)
- scopedOrigin.accessPath.isOpaque = true;
+ if (!scopedOrigin.accessPath.storageDecl)
+ scopedOrigin.accessPath.storageDecl = decl;
};
while (defOp && !breakFromLoop) {
// Operations may have multiple results, so we need to analyze
@@ -2017,9 +1802,8 @@ AliasAnalysis::getSourceImpl(mlir::Value v, bool getLastInstantiationPoint,
accessPath.steps.push_back(pathSteps[i]);
accessPath.isApproximate =
boxSrc.accessPath.isApproximate || approximateSource;
- if (boxSrc.accessPath.isOpaque)
- markPathOpaque();
- accessPath.isOpaque = pathOpaque;
+ setStorageDecl(boxSrc.accessPath.storageDecl);
+ accessPath.storageDecl = storageDecl;
accessPathFinalized = true;
// Rebase each forwarded ScopedOrigin from the inner walk's
@@ -2032,7 +1816,6 @@ AliasAnalysis::getSourceImpl(mlir::Value v, bool getLastInstantiationPoint,
for (int i = pathSteps.size() - 1; i >= 0; --i)
scopedOrigin.accessPath.steps.push_back(pathSteps[i]);
scopedOrigin.accessPath.isApproximate |= approximateSource;
- scopedOrigin.accessPath.isOpaque |= pathOpaque;
if (isPointerBox)
scopedOrigin.attributes.set(Attribute::Pointer);
scopedOrigin.approximateSource |= approximateSource;
@@ -2175,6 +1958,10 @@ AliasAnalysis::getSourceImpl(mlir::Value v, bool getLastInstantiationPoint,
attributes |= getAttrsFromVariable(varIf);
isCapturedInInternalProcedure |=
varIf.isCapturedInInternalProcedure();
+ if (auto storageIf =
+ mlir::dyn_cast<fir::FortranVariableStorageOpInterface>(defOp))
+ if (storageIf.getStorage())
+ setStorageDecl(defOp);
// Snapshot a ScopedOrigin at this declare. The snapshot
// captures the path/attributes from the leaf to this declare
@@ -2186,7 +1973,7 @@ AliasAnalysis::getSourceImpl(mlir::Value v, bool getLastInstantiationPoint,
scopedOrigin.accessPath.steps.assign(pathSteps.rbegin(),
pathSteps.rend());
scopedOrigin.accessPath.isApproximate = approximateSource;
- scopedOrigin.accessPath.isOpaque = pathOpaque;
+ scopedOrigin.accessPath.storageDecl = storageDecl;
scopedOrigin.attributes = attributes;
scopedOrigin.approximateSource = approximateSource;
scopedOrigin.isData = followingData;
@@ -2251,27 +2038,6 @@ AliasAnalysis::getSourceImpl(mlir::Value v, bool getLastInstantiationPoint,
// Record component access steps for the access path.
//
- // The steps collected so far select subobjects of the object
- // designated by hlfir.designate and fir.coordinate_of, unless
- // the designated object is viewed with a different type in
- // between.
- if (mlir::isa<hlfir::DesignateOp, fir::CoordinateOp>(defOp) &&
- !pathSteps.empty()) {
- // An element of a polymorphic array is polymorphic.
- bool isPolymorphic = mlir::isa<fir::ClassType>(ty);
- if (auto coordOp = mlir::dyn_cast<fir::CoordinateOp>(defOp))
- isPolymorphic =
- mlir::isa<fir::ClassType>(coordOp.getRef().getType()) &&
- llvm::all_of(coordOp.getFieldIndices().value_or(
- llvm::ArrayRef<int32_t>{}),
- [](int32_t idx) {
- return idx == fir::CoordinateOp::kDynamicIndex;
- });
- if (!isPathFrameOf(pathSteps.back().recordType,
- getAddressedElementType(ty), isPolymorphic))
- markPathOpaque();
- }
-
// 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)) {
@@ -2280,10 +2046,9 @@ AliasAnalysis::getSourceImpl(mlir::Value v, bool getLastInstantiationPoint,
step.kind = Source::PathStep::Kind::Component;
step.component = *comp;
step.recordType =
- getAddressedElementType(designateOp.getMemref().getType());
+ fir::unwrapSequenceType(fir::dyn_cast_ptrOrBoxEleTy(
+ designateOp.getMemref().getType()));
pathSteps.push_back(step);
- if (crossedUncontainedView)
- markPathOpaque();
}
} else if (auto coordOp = mlir::dyn_cast<fir::CoordinateOp>(defOp)) {
// fir.coordinate_of encodes field accesses as integer indices
@@ -2300,8 +2065,8 @@ AliasAnalysis::getSourceImpl(mlir::Value v, bool getLastInstantiationPoint,
if (fieldIndices) {
mlir::Type currentTy =
fir::dyn_cast_ptrOrBoxEleTy(coordOp.getRef().getType());
- llvm::SmallVector<Source::PathStep, 4> componentSteps;
- bool decoded = true;
+ llvm::SmallVector<std::pair<mlir::StringAttr, mlir::Type>, 4>
+ componentNames;
unsigned dimension = 0;
for (int32_t idx : *fieldIndices) {
if (idx == fir::CoordinateOp::kDynamicIndex) {
@@ -2322,39 +2087,32 @@ AliasAnalysis::getSourceImpl(mlir::Value v, bool getLastInstantiationPoint,
// Unexpected type structure; discard any partially
// collected names so the access path stays conservative
// rather than recording a misleading partial path.
- decoded = false;
+ 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.
- decoded = false;
+ componentNames.clear();
break;
}
- Source::PathStep step;
- step.kind = Source::PathStep::Kind::Component;
- step.component = mlir::StringAttr::get(defOp->getContext(),
- typeList[idx].first);
- step.recordType = recTy;
- componentSteps.push_back(step);
+ componentNames.push_back(
+ {mlir::StringAttr::get(defOp->getContext(),
+ typeList[idx].first),
+ recTy});
currentTy = typeList[idx].second;
}
- if (decoded) {
- // pathSteps is in leaf-to-root order (reversed at the end),
- // so push innermost component first.
- pathSteps.append(componentSteps.rbegin(),
- componentSteps.rend());
- if (crossedUncontainedView && !componentSteps.empty())
- markPathOpaque();
- } else if (!pathSteps.empty()) {
- // Dropping the names would misalign the steps collected
- // so far with the steps collected from now on.
- markPathOpaque();
+ // 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->first;
+ step.recordType = it->second;
+ pathSteps.push_back(step);
}
}
- } else if (!adjustPathStepsForView(op, opResult, pathSteps,
- crossedUncontainedView)) {
- markPathOpaque();
}
// Collect attributes from FortranVariableOpInterface operations.
@@ -2390,13 +2148,6 @@ AliasAnalysis::getSourceImpl(mlir::Value v, bool getLastInstantiationPoint,
collectScopedOrigins);
},
followingData, attributes);
- if (crossedUncontainedView &&
- hasComponentStep(accSourceReturn->accessPath))
- markPathOpaque();
- accSourceReturn->accessPath.isOpaque |= pathOpaque;
- for (Source::ScopedOrigin &scopedOrigin :
- accSourceReturn->scopedOrigins)
- scopedOrigin.accessPath.isOpaque |= pathOpaque;
breakFromLoop = true;
})
.Case([&](mlir::RegionBranchOpInterface branch) {
@@ -2428,14 +2179,7 @@ AliasAnalysis::getSourceImpl(mlir::Value v, bool getLastInstantiationPoint,
regionBranchReturn->approximateSource |= approximateSource;
regionBranchReturn->isCapturedInInternalProcedure |=
isCapturedInInternalProcedure;
- if (regionBranchReturn->accessPath.isOpaque ||
- (crossedUncontainedView &&
- hasComponentStep(regionBranchReturn->accessPath)))
- markPathOpaque();
- regionBranchReturn->accessPath.isOpaque |= pathOpaque;
- for (Source::ScopedOrigin &scopedOrigin :
- regionBranchReturn->scopedOrigins)
- scopedOrigin.accessPath.isOpaque |= pathOpaque;
+ setStorageDecl(regionBranchReturn->accessPath.storageDecl);
// Prepend the outer (leaf-closer) scopedOrigins -- declares
// already crossed between leaf and this region-branch op --
// to the merged predecessors' snapshots. The inner snapshots'
@@ -2494,7 +2238,7 @@ AliasAnalysis::getSourceImpl(mlir::Value v, bool getLastInstantiationPoint,
std::reverse(pathSteps.begin(), pathSteps.end());
accessPath.steps = std::move(pathSteps);
accessPath.isApproximate = approximateSource;
- accessPath.isOpaque = pathOpaque;
+ accessPath.storageDecl = storageDecl;
}
if (type == SourceKind::Global) {
diff --git a/flang/test/Analysis/AliasAnalysis/alias-analysis-access-path-type-change.fir b/flang/test/Analysis/AliasAnalysis/alias-analysis-access-path-record-type.fir
similarity index 70%
rename from flang/test/Analysis/AliasAnalysis/alias-analysis-access-path-type-change.fir
rename to flang/test/Analysis/AliasAnalysis/alias-analysis-access-path-record-type.fir
index c58079cce1608..5a527766720cd 100644
--- a/flang/test/Analysis/AliasAnalysis/alias-analysis-access-path-type-change.fir
+++ b/flang/test/Analysis/AliasAnalysis/alias-analysis-access-path-record-type.fir
@@ -1,7 +1,7 @@
// Tests for access paths in fir::AliasAnalysis through views that change the
// type of the addressed object: CLASS views of objects of extended types,
-// SELECT TYPE, storage association, component paths of array slices, and
-// descriptor casts.
+// SELECT TYPE, storage association, sequence association, and component
+// sections of arrays.
// RUN: fir-opt %s -split-input-file -o /dev/null --mlir-disable-threading \
// RUN: -pass-pipeline='builtin.module(func.func(test-fir-alias-analysis))' \
@@ -133,13 +133,11 @@ func.func @_QPup_down() {
// select type (sel => this)
// type is (t2)
// ... sel%a ... this%a ... sel%b
-// sel%a and this%a are the same component. this%a and sel%b are disjoint,
-// but the analysis does not relate the components of the two views when
-// the dynamic type of the dummy is unknown.
+// sel%a and this%a are the same component. this%a and sel%b are disjoint.
// CHECK-LABEL: Testing : "_QPsel"
// CHECK-DAG: this_a#0 <-> sel_a#0: MayAlias
-// CHECK-DAG: this_a#0 <-> sel_b#0: MayAlias
+// CHECK-DAG: this_a#0 <-> sel_b#0: NoAlias
// CHECK-DAG: sel_a#0 <-> sel_b#0: NoAlias
!t1 = !fir.type<_QMmTt1{a:!fir.array<4xf32>}>
@@ -160,6 +158,62 @@ func.func @_QPsel(%arg0: !fir.class<!t1> {fir.bindc_name = "this"}) {
// -----
+// SELECT TYPE with TYPE IS (child) on a CLASS(base) dummy, where the
+// selector is accessed through the parent component:
+// select type (a => this)
+// type is (child)
+// this%v(1:3) = a%base%v(2:4)
+// The two sections overlap.
+
+// CHECK-LABEL: Testing : "_QMmPshift"
+// CHECK-DAG: this_v#0 <-> a_v#0: MayAlias
+
+!base = !fir.type<_QMmTbase{v:!fir.array<4xf32>}>
+!child = !fir.type<_QMmTchild{base:!base,extra:!fir.array<4xf32>}>
+func.func @_QMmPshift(%arg0: !fir.class<!base> {fir.bindc_name = "this"}) {
+ %c1 = arith.constant 1 : index
+ %c2 = arith.constant 2 : index
+ %c3 = arith.constant 3 : index
+ %c4 = arith.constant 4 : index
+ %0 = fir.dummy_scope : !fir.dscope
+ %1:2 = hlfir.declare %arg0 dummy_scope %0 arg 1 uniq_name("_QMmFshiftEthis") : (!fir.class<!base>, !fir.dscope) -> (!fir.class<!base>, !fir.class<!base>)
+ %2 = fir.box_addr %1#1 : (!fir.class<!base>) -> !fir.ref<!child>
+ %3:2 = hlfir.declare %2 uniq_name("_QMmFshiftEa") : (!fir.ref<!child>) -> (!fir.ref<!child>, !fir.ref<!child>)
+ %4 = fir.shape %c4 : (index) -> !fir.shape<1>
+ %5 = fir.shape %c3 : (index) -> !fir.shape<1>
+ %6 = hlfir.designate %1#0{"v"} <%4> (%c1:%c3:%c1) shape %5 {test.ptr = "this_v"} : (!fir.class<!base>, !fir.shape<1>, index, index, index, !fir.shape<1>) -> !fir.ref<!fir.array<3xf32>>
+ %7 = hlfir.designate %3#0{"base"} : (!fir.ref<!child>) -> !fir.ref<!base>
+ %8 = hlfir.designate %7{"v"} <%4> (%c2:%c4:%c1) shape %5 {test.ptr = "a_v"} : (!fir.ref<!base>, !fir.shape<1>, index, index, index, !fir.shape<1>) -> !fir.ref<!fir.array<3xf32>>
+ return
+}
+
+// -----
+
+// The same SELECT TYPE after conversion to FIR and inlining into a caller
+// (two dummy scopes).
+
+// CHECK-LABEL: Testing : "_QMmPshift_inlined"
+// CHECK-DAG: this_v#0 <-> a_v#0: MayAlias
+
+!base = !fir.type<_QMmTbase{v:!fir.array<4xf32>}>
+!child = !fir.type<_QMmTchild{base:!base,extra:!fir.array<4xf32>}>
+func.func @_QMmPshift_inlined(%arg0: !fir.class<!base> {fir.bindc_name = "this"}, %i: index) {
+ %c4 = arith.constant 4 : index
+ %sh = fir.shape %c4 : (index) -> !fir.shape<1>
+ %caller_scope = fir.dummy_scope : !fir.dscope
+ %ds = fir.dummy_scope : !fir.dscope
+ %this = fir.declare %arg0 dummy_scope %ds arg 1 uniq_name("_QMmFshiftEthis") : (!fir.class<!base>, !fir.dscope) -> !fir.class<!base>
+ %2 = fir.box_addr %this : (!fir.class<!base>) -> !fir.ref<!child>
+ %a = fir.declare %2 uniq_name("_QMmFshiftEa") : (!fir.ref<!child>) -> !fir.ref<!child>
+ %tv = fir.coordinate_of %this, v : (!fir.class<!base>) -> !fir.ref<!fir.array<4xf32>>
+ %this_v = fir.array_coor %tv(%sh) %i {test.ptr = "this_v"} : (!fir.ref<!fir.array<4xf32>>, !fir.shape<1>, index) -> !fir.ref<f32>
+ %av = fir.coordinate_of %a, base, v : (!fir.ref<!child>) -> !fir.ref<!fir.array<4xf32>>
+ %a_v = fir.array_coor %av(%sh) %i {test.ptr = "a_v"} : (!fir.ref<!fir.array<4xf32>>, !fir.shape<1>, index) -> !fir.ref<f32>
+ return
+}
+
+// -----
+
// CLASS(*) views of an object of type t2, and SELECT TYPE on them with
// TYPE IS (t2) (view_b is obj%b) and CLASS IS (t1) (view_a is obj%t1%a).
@@ -192,12 +246,11 @@ func.func @_QPunlimited() {
// type(s1) :: x ! components a, b
// type(s2) :: y ! components c, d
// equivalence (x, y)
-// x%b and y%d are the same memory. The component paths of the two variables
-// cannot be related, so even x%a and x%b are not disambiguated.
+// x%b and y%d are the same memory. The components of x are still disjoint.
// CHECK-LABEL: Testing : "_QPequiv"
// CHECK-DAG: x_a#0 <-> y_d#0: MayAlias
-// CHECK-DAG: x_a#0 <-> x_b#0: MayAlias
+// CHECK-DAG: x_a#0 <-> x_b#0: NoAlias
// CHECK-DAG: y_d#0 <-> x_b#0: MayAlias
!s1 = !fir.type<_QFequivTs1,sequence{a:f32,b:f32}>
@@ -251,92 +304,8 @@ func.func @_QPequiv_shift() {
// -----
-// A component reinterpreted as an object of a larger type. The
-// reinterpreted object overlaps the sibling component that follows: pun_r
-// and obj%d are the same memory.
-
-// CHECK-LABEL: Testing : "_QPpun"
-// CHECK-DAG: pun_r#0 <-> direct_d#0: MayAlias
-
-!r = !fir.type<_QMmTr{c:!fir.array<2xf32>,d:f32}>
-!s = !fir.type<_QMmTs{p:f32,q:f32,r:f32}>
-func.func @_QPpun() {
- %0 = fir.alloca !r {bindc_name = "obj", uniq_name = "_QFpunEobj"}
- %obj = fir.declare %0 uniq_name("_QFpunEobj") : (!fir.ref<!r>) -> !fir.ref<!r>
- %c = fir.coordinate_of %obj, c : (!fir.ref<!r>) -> !fir.ref<!fir.array<2xf32>>
- %pun = fir.convert %c : (!fir.ref<!fir.array<2xf32>>) -> !fir.ref<!s>
- %pun_r = fir.coordinate_of %pun, r {test.ptr = "pun_r"} : (!fir.ref<!s>) -> !fir.ref<f32>
- %direct_d = fir.coordinate_of %obj, d {test.ptr = "direct_d"} : (!fir.ref<!r>) -> !fir.ref<f32>
- return
-}
-
-// -----
-
-// Views of the component section x%in of an array x of type outer:
-// slice_q and coor_q are x(k)%in%q.
-
-// CHECK-LABEL: Testing : "_QPslice_path"
-// CHECK-DAG: direct_q#0 <-> direct_j#0: NoAlias
-// CHECK-DAG: direct_q#0 <-> slice_q#0: MayAlias
-// CHECK-DAG: direct_j#0 <-> slice_q#0: NoAlias
-// CHECK-DAG: direct_p#0 <-> slice_q#0: NoAlias
-// CHECK-DAG: direct_q#0 <-> coor_q#0: MayAlias
-// CHECK-DAG: direct_j#0 <-> coor_q#0: NoAlias
-// CHECK-DAG: direct_p#0 <-> coor_q#0: NoAlias
-
-!inner = !fir.type<_QMmTinner{p:f32,q:f32}>
-!outer = !fir.type<_QMmTouter{in:!inner,j:f32}>
-func.func @_QPslice_path(%arg0: !fir.ref<!fir.array<10x!outer>> {fir.bindc_name = "x"}, %k: index) {
- %c1 = arith.constant 1 : index
- %c10 = arith.constant 10 : index
- %sh = fir.shape %c10 : (index) -> !fir.shape<1>
- %ds = fir.dummy_scope : !fir.dscope
- %x = fir.declare %arg0(%sh) dummy_scope %ds arg 1 uniq_name("_QFslice_pathEx") : (!fir.ref<!fir.array<10x!outer>>, !fir.shape<1>, !fir.dscope) -> !fir.ref<!fir.array<10x!outer>>
- %elt = fir.array_coor %x(%sh) %k : (!fir.ref<!fir.array<10x!outer>>, !fir.shape<1>, index) -> !fir.ref<!outer>
- %direct_q = fir.coordinate_of %elt, in, q {test.ptr = "direct_q"} : (!fir.ref<!outer>) -> !fir.ref<f32>
- %direct_j = fir.coordinate_of %elt, j {test.ptr = "direct_j"} : (!fir.ref<!outer>) -> !fir.ref<f32>
- %direct_p = fir.coordinate_of %elt, in, p {test.ptr = "direct_p"} : (!fir.ref<!outer>) -> !fir.ref<f32>
- %field = fir.field_index in, !outer
- %slice = fir.slice %c1, %c10, %c1 path %field : (index, index, index, !fir.field) -> !fir.slice<1>
- %box = fir.embox %x(%sh) [%slice] : (!fir.ref<!fir.array<10x!outer>>, !fir.shape<1>, !fir.slice<1>) -> !fir.box<!fir.array<10x!inner>>
- %selt = fir.array_coor %box %k : (!fir.box<!fir.array<10x!inner>>, index) -> !fir.ref<!inner>
- %slice_q = fir.coordinate_of %selt, q {test.ptr = "slice_q"} : (!fir.ref<!inner>) -> !fir.ref<f32>
- %celt = fir.array_coor %x(%sh) [%slice] %k : (!fir.ref<!fir.array<10x!outer>>, !fir.shape<1>, !fir.slice<1>, index) -> !fir.ref<!inner>
- %coor_q = fir.coordinate_of %celt, q {test.ptr = "coor_q"} : (!fir.ref<!inner>) -> !fir.ref<f32>
- return
-}
-
-// -----
-
-// A pointer component descriptor reinterpreted as a descriptor of data of
-// an unrelated type.
-
-// CHECK-LABEL: Testing : "_QPdescriptor_cast"
-// CHECK-DAG: direct_a#0 <-> cast_y#0: MayAlias
-
-!t1 = !fir.type<_QMmTt1{a:!fir.array<4xf32>}>
-!u = !fir.type<_QMmTu{x:f32,y:f32}>
-!h = !fir.type<_QMmTh{p:!fir.box<!fir.ptr<!t1>>,k:f32}>
-func.func @_QPdescriptor_cast() {
- %0 = fir.alloca !h {bindc_name = "obj", uniq_name = "_QFdescriptor_castEobj"}
- %obj = fir.declare %0 uniq_name("_QFdescriptor_castEobj") : (!fir.ref<!h>) -> !fir.ref<!h>
- %p = fir.coordinate_of %obj, p : (!fir.ref<!h>) -> !fir.ref<!fir.box<!fir.ptr<!t1>>>
- %pbox = fir.load %p : !fir.ref<!fir.box<!fir.ptr<!t1>>>
- %paddr = fir.box_addr %pbox : (!fir.box<!fir.ptr<!t1>>) -> !fir.ptr<!t1>
- %direct_a = fir.coordinate_of %paddr, a {test.ptr = "direct_a"} : (!fir.ptr<!t1>) -> !fir.ref<!fir.array<4xf32>>
- %cast = fir.convert %p : (!fir.ref<!fir.box<!fir.ptr<!t1>>>) -> !fir.ref<!fir.box<!fir.ptr<!u>>>
- %cbox = fir.load %cast : !fir.ref<!fir.box<!fir.ptr<!u>>>
- %caddr = fir.box_addr %cbox : (!fir.box<!fir.ptr<!u>>) -> !fir.ptr<!u>
- %cast_y = fir.coordinate_of %caddr, y {test.ptr = "cast_y"} : (!fir.ptr<!u>) -> !fir.ref<f32>
- return
-}
-
-// -----
-
// The same EQUIVALENCE as in _QPequiv_shift after inlining, so that the
-// declarations of x and y are compared by the scope-aware refinement. The
-// views crossed after the declarations of x and y reinterpret the storage
-// for them too.
+// declarations of x and y are compared by the scope-aware refinement.
// CHECK-LABEL: Testing : "_QPequiv_shift_scopes"
// CHECK-DAG: x_b#0 <-> y_a#0: MayAlias
@@ -366,142 +335,124 @@ func.func @_QPequiv_shift_scopes() {
// -----
-// The two-element array component c viewed as a three-element array: the
-// third element of the view, view_3, is obj%d.
-
-// CHECK-LABEL: Testing : "_QPview_element"
-// CHECK-DAG: view_3#0 <-> direct_d#0: MayAlias
-// CHECK-DAG: direct_c2#0 <-> direct_d#0: NoAlias
-
-!r = !fir.type<_QMmTr{c:!fir.array<2xf32>,d:f32}>
-func.func @_QPview_element() {
- %c2 = arith.constant 2 : index
- %c3 = arith.constant 3 : index
- %sh2 = fir.shape %c2 : (index) -> !fir.shape<1>
- %sh3 = fir.shape %c3 : (index) -> !fir.shape<1>
- %0 = fir.alloca !r {bindc_name = "obj", uniq_name = "_QFview_elementEobj"}
- %obj = fir.declare %0 uniq_name("_QFview_elementEobj") : (!fir.ref<!r>) -> !fir.ref<!r>
- %c = fir.coordinate_of %obj, c : (!fir.ref<!r>) -> !fir.ref<!fir.array<2xf32>>
- %view = fir.convert %c : (!fir.ref<!fir.array<2xf32>>) -> !fir.ref<!fir.array<3xf32>>
- %view_3 = fir.array_coor %view(%sh3) %c3 {test.ptr = "view_3"} : (!fir.ref<!fir.array<3xf32>>, !fir.shape<1>, index) -> !fir.ref<f32>
- %direct_c2 = fir.array_coor %c(%sh2) %c2 {test.ptr = "direct_c2"} : (!fir.ref<!fir.array<2xf32>>, !fir.shape<1>, index) -> !fir.ref<f32>
- %direct_d = fir.coordinate_of %obj, d {test.ptr = "direct_d"} : (!fir.ref<!r>) -> !fir.ref<f32>
- return
-}
-
-// -----
-
-// The component c viewed as an object of a larger type: the whole view,
-// view, overlaps obj%d.
-
-// CHECK-LABEL: Testing : "_QPview_scalar"
-// CHECK-DAG: view#0 <-> direct_d#0: MayAlias
-
-!r = !fir.type<_QMmTr{c:f32,d:f32}>
-!bigger = !fir.type<_QMmTbigger{x:f32,y:f32}>
-func.func @_QPview_scalar() {
- %0 = fir.alloca !r {bindc_name = "obj", uniq_name = "_QFview_scalarEobj"}
- %obj = fir.declare %0 uniq_name("_QFview_scalarEobj") : (!fir.ref<!r>) -> !fir.ref<!r>
- %c = fir.coordinate_of %obj, c : (!fir.ref<!r>) -> !fir.ref<f32>
- %view = fir.convert %c {test.ptr = "view"} : (!fir.ref<f32>) -> !fir.ref<!bigger>
- %direct_d = fir.coordinate_of %obj, d {test.ptr = "direct_d"} : (!fir.ref<!r>) -> !fir.ref<f32>
+// The same SEQUENCE type declared in two scopes is the same Fortran type,
+// but two different FIR record types. After inlining, the actual argument
+// obj%s (of the caller's type) is accessed through the dummy y (of the
+// callee's type): obj%s%a and y%a are the same memory.
+
+// CHECK-LABEL: Testing : "_QPsame_sequence_type"
+// CHECK-DAG: x_a#0 <-> y_a#0: MayAlias
+// CHECK-DAG: x_a#0 <-> y_b#0: MayAlias
+// CHECK-DAG: y_a#0 <-> y_b#0: NoAlias
+
+!sa = !fir.type<_QFcallerTs,sequence{a:f32,b:f32}>
+!sb = !fir.type<_QFcalleeTs,sequence{a:f32,b:f32}>
+!w = !fir.type<_QFcallerTw{s:!sa,z:f32}>
+func.func @_QPsame_sequence_type() {
+ %caller_scope = fir.dummy_scope : !fir.dscope
+ %0 = fir.alloca !w {bindc_name = "obj", uniq_name = "_QFEobj"}
+ %obj = fir.declare %0 uniq_name("_QFEobj") : (!fir.ref<!w>) -> !fir.ref<!w>
+ %x = fir.coordinate_of %obj, s : (!fir.ref<!w>) -> !fir.ref<!sa>
+ %x_a = fir.coordinate_of %obj, s, a {test.ptr = "x_a"} : (!fir.ref<!w>) -> !fir.ref<f32>
+ %actual = fir.convert %x : (!fir.ref<!sa>) -> !fir.ref<!sb>
+ %ds = fir.dummy_scope : !fir.dscope
+ %y = fir.declare %actual dummy_scope %ds arg 1 uniq_name("_QFcalleeEy") : (!fir.ref<!sb>, !fir.dscope) -> !fir.ref<!sb>
+ %y_a = fir.coordinate_of %y, a {test.ptr = "y_a"} : (!fir.ref<!sb>) -> !fir.ref<f32>
+ %y_b = fir.coordinate_of %y, b {test.ptr = "y_b"} : (!fir.ref<!sb>) -> !fir.ref<f32>
return
}
// -----
-// Sequence association of an element of an array component with an
-// explicit-shape dummy array, after inlining:
+// Sequence association of an element of a component after inlining:
// type r
-// real :: c(2), d
+// real :: c(10), d(10)
// end type
-// call foo(obj%c(2))
-// subroutine foo(x)
-// real :: x(2)
-// x(2) is obj%d.
+// call foo(obj%c(1), n) ! subroutine foo(x, n); real :: x(n)
+// ... obj%d(j) ...
+// x(i) is obj%c(i), which cannot overlap obj%d(j).
// CHECK-LABEL: Testing : "_QPsequence_association"
-// CHECK-DAG: x_2#0 <-> obj_d#0: MayAlias
+// CHECK-DAG: x_i#0 <-> obj_d_j#0: NoAlias
-!r = !fir.type<_QMmTr{c:!fir.array<2xf32>,d:f32}>
-func.func @_QPsequence_association() {
- %c2 = arith.constant 2 : index
+!r = !fir.type<_QMmTr{c:!fir.array<10xf32>,d:!fir.array<10xf32>}>
+func.func @_QPsequence_association(%n: index, %i: index, %j: index) {
+ %c1 = arith.constant 1 : index
+ %c10 = arith.constant 10 : index
+ %sh10 = fir.shape %c10 : (index) -> !fir.shape<1>
+ %ds_caller = fir.dummy_scope : !fir.dscope
%0 = fir.alloca !r {bindc_name = "obj", uniq_name = "_QFsequence_associationEobj"}
- %obj:2 = hlfir.declare %0 uniq_name("_QFsequence_associationEobj") : (!fir.ref<!r>) -> (!fir.ref<!r>, !fir.ref<!r>)
- %sh = fir.shape %c2 : (index) -> !fir.shape<1>
- %c_2 = hlfir.designate %obj#0{"c"} <%sh> (%c2) : (!fir.ref<!r>, !fir.shape<1>, index) -> !fir.ref<f32>
- %actual = fir.convert %c_2 : (!fir.ref<f32>) -> !fir.ref<!fir.array<2xf32>>
+ %obj = fir.declare %0 uniq_name("_QFsequence_associationEobj") : (!fir.ref<!r>) -> !fir.ref<!r>
+ %c = fir.coordinate_of %obj, c : (!fir.ref<!r>) -> !fir.ref<!fir.array<10xf32>>
+ %c_1 = fir.array_coor %c(%sh10) %c1 : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>, index) -> !fir.ref<f32>
+ %actual = fir.convert %c_1 : (!fir.ref<f32>) -> !fir.ref<!fir.array<?xf32>>
%ds = fir.dummy_scope : !fir.dscope
- %x:2 = hlfir.declare %actual(%sh) dummy_scope %ds arg 1 uniq_name("_QFfooEx") : (!fir.ref<!fir.array<2xf32>>, !fir.shape<1>, !fir.dscope) -> (!fir.ref<!fir.array<2xf32>>, !fir.ref<!fir.array<2xf32>>)
- %x_2 = hlfir.designate %x#0 (%c2) {test.ptr = "x_2"} : (!fir.ref<!fir.array<2xf32>>, index) -> !fir.ref<f32>
- %obj_d = hlfir.designate %obj#0{"d"} {test.ptr = "obj_d"} : (!fir.ref<!r>) -> !fir.ref<f32>
+ %shn = fir.shape %n : (index) -> !fir.shape<1>
+ %x = fir.declare %actual(%shn) dummy_scope %ds arg 1 uniq_name("_QFfooEx") : (!fir.ref<!fir.array<?xf32>>, !fir.shape<1>, !fir.dscope) -> !fir.ref<!fir.array<?xf32>>
+ %x_i = fir.array_coor %x(%shn) %i {test.ptr = "x_i"} : (!fir.ref<!fir.array<?xf32>>, !fir.shape<1>, index) -> !fir.ref<f32>
+ %d = fir.coordinate_of %obj, d : (!fir.ref<!r>) -> !fir.ref<!fir.array<10xf32>>
+ %d_j = fir.array_coor %d(%sh10) %j {test.ptr = "obj_d_j"} : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>, index) -> !fir.ref<f32>
return
}
// -----
-// A pointer component descriptor reinterpreted as a derived type object:
-// view_word is the first word of the descriptor of obj%p.
+// Views of the component section x%in of an array x of type outer:
+// slice_q and coor_q are x(k)%in%q. The component path of the slice is not
+// recorded, so the views are not disambiguated from x(k)%j.
-// CHECK-LABEL: Testing : "_QPdescriptor_view"
-// CHECK-DAG: desc#0 <-> view_word#0: MayAlias
+// CHECK-LABEL: Testing : "_QPslice_path"
+// CHECK-DAG: direct_q#0 <-> direct_j#0: NoAlias
+// CHECK-DAG: direct_q#0 <-> slice_q#0: MayAlias
+// CHECK-DAG: direct_j#0 <-> slice_q#0: MayAlias
+// CHECK-DAG: direct_p#0 <-> slice_q#0: NoAlias
+// CHECK-DAG: direct_q#0 <-> coor_q#0: MayAlias
+// CHECK-DAG: direct_j#0 <-> coor_q#0: MayAlias
+// CHECK-DAG: direct_p#0 <-> coor_q#0: NoAlias
-!t1 = !fir.type<_QMmTt1{a:!fir.array<4xf32>}>
-!w = !fir.type<_QMmTw{word:i64}>
-!h = !fir.type<_QMmTh{p:!fir.box<!fir.ptr<!t1>>,k:f32}>
-func.func @_QPdescriptor_view() {
- %0 = fir.alloca !h {bindc_name = "obj", uniq_name = "_QFdescriptor_viewEobj"}
- %obj = fir.declare %0 uniq_name("_QFdescriptor_viewEobj") : (!fir.ref<!h>) -> !fir.ref<!h>
- %desc = fir.coordinate_of %obj, p {test.ptr = "desc"} : (!fir.ref<!h>) -> !fir.ref<!fir.box<!fir.ptr<!t1>>>
- %view = fir.convert %desc : (!fir.ref<!fir.box<!fir.ptr<!t1>>>) -> !fir.ref<!w>
- %view_word = fir.coordinate_of %view, word {test.ptr = "view_word"} : (!fir.ref<!w>) -> !fir.ref<i64>
+!inner = !fir.type<_QMmTinner{p:f32,q:f32}>
+!outer = !fir.type<_QMmTouter{in:!inner,j:f32}>
+func.func @_QPslice_path(%arg0: !fir.ref<!fir.array<10x!outer>> {fir.bindc_name = "x"}, %k: index) {
+ %c1 = arith.constant 1 : index
+ %c10 = arith.constant 10 : index
+ %sh = fir.shape %c10 : (index) -> !fir.shape<1>
+ %ds = fir.dummy_scope : !fir.dscope
+ %x = fir.declare %arg0(%sh) dummy_scope %ds arg 1 uniq_name("_QFslice_pathEx") : (!fir.ref<!fir.array<10x!outer>>, !fir.shape<1>, !fir.dscope) -> !fir.ref<!fir.array<10x!outer>>
+ %elt = fir.array_coor %x(%sh) %k : (!fir.ref<!fir.array<10x!outer>>, !fir.shape<1>, index) -> !fir.ref<!outer>
+ %direct_q = fir.coordinate_of %elt, in, q {test.ptr = "direct_q"} : (!fir.ref<!outer>) -> !fir.ref<f32>
+ %direct_j = fir.coordinate_of %elt, j {test.ptr = "direct_j"} : (!fir.ref<!outer>) -> !fir.ref<f32>
+ %direct_p = fir.coordinate_of %elt, in, p {test.ptr = "direct_p"} : (!fir.ref<!outer>) -> !fir.ref<f32>
+ %field = fir.field_index in, !outer
+ %slice = fir.slice %c1, %c10, %c1 path %field : (index, index, index, !fir.field) -> !fir.slice<1>
+ %box = fir.embox %x(%sh) [%slice] : (!fir.ref<!fir.array<10x!outer>>, !fir.shape<1>, !fir.slice<1>) -> !fir.box<!fir.array<10x!inner>>
+ %selt = fir.array_coor %box %k : (!fir.box<!fir.array<10x!inner>>, index) -> !fir.ref<!inner>
+ %slice_q = fir.coordinate_of %selt, q {test.ptr = "slice_q"} : (!fir.ref<!inner>) -> !fir.ref<f32>
+ %celt = fir.array_coor %x(%sh) [%slice] %k : (!fir.ref<!fir.array<10x!outer>>, !fir.shape<1>, !fir.slice<1>, index) -> !fir.ref<!inner>
+ %coor_q = fir.coordinate_of %celt, q {test.ptr = "coor_q"} : (!fir.ref<!inner>) -> !fir.ref<f32>
return
}
// -----
-// A pointer component descriptor reinterpreted as an integer: word is the
-// first word of the descriptor of obj%p. It is reached through another
-// access to obj%p, so that it does not trivially address the same memory as
-// desc.
+// A pointer component descriptor reinterpreted as a descriptor of data of
+// an unrelated type.
-// CHECK-LABEL: Testing : "_QPdescriptor_word"
-// CHECK-DAG: desc#0 <-> word#0: MayAlias
+// CHECK-LABEL: Testing : "_QPdescriptor_cast"
+// CHECK-DAG: direct_a#0 <-> cast_y#0: MayAlias
!t1 = !fir.type<_QMmTt1{a:!fir.array<4xf32>}>
+!u = !fir.type<_QMmTu{x:f32,y:f32}>
!h = !fir.type<_QMmTh{p:!fir.box<!fir.ptr<!t1>>,k:f32}>
-func.func @_QPdescriptor_word() {
- %0 = fir.alloca !h {bindc_name = "obj", uniq_name = "_QFdescriptor_wordEobj"}
- %obj = fir.declare %0 uniq_name("_QFdescriptor_wordEobj") : (!fir.ref<!h>) -> !fir.ref<!h>
- %desc = fir.coordinate_of %obj, p {test.ptr = "desc"} : (!fir.ref<!h>) -> !fir.ref<!fir.box<!fir.ptr<!t1>>>
- %desc2 = fir.coordinate_of %obj, p : (!fir.ref<!h>) -> !fir.ref<!fir.box<!fir.ptr<!t1>>>
- %word = fir.convert %desc2 {test.ptr = "word"} : (!fir.ref<!fir.box<!fir.ptr<!t1>>>) -> !fir.ref<i64>
- return
-}
-
-// -----
-
-// A box with a byte stride smaller than the size of its elements: the
-// first component of its second element, box_a, is the second component of
-// the first element of the array, direct_b.
-
-// CHECK-LABEL: Testing : "_QPcreate_box_stride"
-// CHECK-DAG: box_a#0 <-> direct_b#0: MayAlias
-// CHECK-DAG: direct_a#0 <-> direct_b#0: NoAlias
-
-!s = !fir.type<_QMmTs{a:f32,b:f32}>
-func.func @_QPcreate_box_stride() {
- %c1 = arith.constant 1 : index
- %c2 = arith.constant 2 : index
- %c4 = arith.constant 4 : index
- %sh = fir.shape %c2 : (index) -> !fir.shape<1>
- %0 = fir.alloca !fir.array<2x!s> {bindc_name = "arr", uniq_name = "_QFcreate_box_strideEarr"}
- %arr = fir.declare %0(%sh) uniq_name("_QFcreate_box_strideEarr") : (!fir.ref<!fir.array<2x!s>>, !fir.shape<1>) -> !fir.ref<!fir.array<2x!s>>
- %box = fir.create_box %arr lbs(%c1) extents(%c2) strides(%c4) : (!fir.ref<!fir.array<2x!s>>, index, index, index) -> !fir.box<!fir.array<2x!s>>
- %belt = fir.array_coor %box %c2 : (!fir.box<!fir.array<2x!s>>, index) -> !fir.ref<!s>
- %box_a = fir.coordinate_of %belt, a {test.ptr = "box_a"} : (!fir.ref<!s>) -> !fir.ref<f32>
- %elt = fir.array_coor %arr(%sh) %c1 : (!fir.ref<!fir.array<2x!s>>, !fir.shape<1>, index) -> !fir.ref<!s>
- %direct_a = fir.coordinate_of %elt, a {test.ptr = "direct_a"} : (!fir.ref<!s>) -> !fir.ref<f32>
- %direct_b = fir.coordinate_of %elt, b {test.ptr = "direct_b"} : (!fir.ref<!s>) -> !fir.ref<f32>
+func.func @_QPdescriptor_cast() {
+ %0 = fir.alloca !h {bindc_name = "obj", uniq_name = "_QFdescriptor_castEobj"}
+ %obj = fir.declare %0 uniq_name("_QFdescriptor_castEobj") : (!fir.ref<!h>) -> !fir.ref<!h>
+ %p = fir.coordinate_of %obj, p : (!fir.ref<!h>) -> !fir.ref<!fir.box<!fir.ptr<!t1>>>
+ %pbox = fir.load %p : !fir.ref<!fir.box<!fir.ptr<!t1>>>
+ %paddr = fir.box_addr %pbox : (!fir.box<!fir.ptr<!t1>>) -> !fir.ptr<!t1>
+ %direct_a = fir.coordinate_of %paddr, a {test.ptr = "direct_a"} : (!fir.ptr<!t1>) -> !fir.ref<!fir.array<4xf32>>
+ %cast = fir.convert %p : (!fir.ref<!fir.box<!fir.ptr<!t1>>>) -> !fir.ref<!fir.box<!fir.ptr<!u>>>
+ %cbox = fir.load %cast : !fir.ref<!fir.box<!fir.ptr<!u>>>
+ %caddr = fir.box_addr %cbox : (!fir.box<!fir.ptr<!u>>) -> !fir.ptr<!u>
+ %cast_y = fir.coordinate_of %caddr, y {test.ptr = "cast_y"} : (!fir.ptr<!u>) -> !fir.ref<f32>
return
}
>From c3634d018ba2eb79834568e454ee4ec1e22c0b94 Mon Sep 17 00:00:00 2001
From: Slava Zakharin <szakharin at nvidia.com>
Date: Thu, 15 Oct 2026 13:10:58 -0700
Subject: [PATCH 3/3] [flang] Fix component comparison through dereferences and
parent types
Two fixes to the comparison of access path components:
- A PointerDeref or AllocDeref step reaches another object, but the
component steps after it were compared with the component steps of the
root object. For example, in x%a%v(2:4) = x%a%v(1:3), where x is of an
extended type with an allocatable component a of the parent type, the
component a of x was compared with the component v of x%a, and the two
sections were found disjoint. Two component steps are now only
compared if they select components of the same object: if neither of
them follows a dereference, or if the steps up to and including the
last dereference before each of them are the same.
- The parent type of an extended type was recognized as the type of its
first component when the component was named after its type. This also
matched a component t of a SEQUENCE or BIND(C) type t, which is not a
parent component, and such a type may be the same type as a type of
another scope. The parent type is now taken from the fir.type_info of
the extended type.
Assisted-by: Cursor
---
.../lib/Optimizer/Analysis/AliasAnalysis.cpp | 100 +++++---
...alias-analysis-access-path-record-type.fir | 240 +++++++++++++++++-
.../HLFIR/inline-hlfir-assign-select-type.fir | 2 +
3 files changed, 300 insertions(+), 42 deletions(-)
diff --git a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
index 5664e414b2bf0..9abf823226163 100644
--- a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
+++ b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
@@ -591,23 +591,14 @@ static bool typeContains(mlir::Type ty, mlir::Type subTy) {
return false;
}
-/// Return the type of the parent component of \p recTy, if any. The parent
-/// component is the first component, named after the parent type.
-static fir::RecordType getParentType(fir::RecordType recTy) {
- if (recTy.getNumFields() == 0)
- return {};
- const auto &[name, fieldTy] = recTy.getTypeList().front();
- auto parentTy = mlir::dyn_cast<fir::RecordType>(fieldTy);
- if (!parentTy ||
- fir::NameUniquer::deconstruct(parentTy.getName()).second.name != name)
- return {};
- return parentTy;
-}
-
-/// Return true if \p ty is an extension of \p ancestor. Extensible types
-/// are never SEQUENCE or BIND(C) types, so they are only the same Fortran
-/// type when they are the same FIR type.
-static bool isProperExtensionOf(fir::RecordType ty, fir::RecordType ancestor) {
+using GetParentTypeFn = llvm::function_ref<fir::RecordType(fir::RecordType)>;
+
+/// Return true if \p ty is an extension of \p ancestor, given the parent
+/// type of each derived type by \p getParentType. Extensible types are never
+/// SEQUENCE or BIND(C) types, so they are only the same Fortran type when
+/// they are the same FIR type.
+static bool isProperExtensionOf(fir::RecordType ty, fir::RecordType ancestor,
+ GetParentTypeFn getParentType) {
for (fir::RecordType parent = getParentType(ty); parent;
parent = getParentType(parent))
if (parent == ancestor)
@@ -625,7 +616,8 @@ static bool isProperExtensionOf(fir::RecordType ty, fir::RecordType ancestor) {
/// address of an object extending beyond the component.
static bool
componentsAreDisjoint(const fir::AliasAnalysis::Source::PathStep &lhs,
- const fir::AliasAnalysis::Source::PathStep &rhs) {
+ const fir::AliasAnalysis::Source::PathStep &rhs,
+ GetParentTypeFn getParentType) {
if (!lhs.recordType || !rhs.recordType)
return false;
if (lhs.recordType == rhs.recordType)
@@ -634,8 +626,8 @@ componentsAreDisjoint(const fir::AliasAnalysis::Source::PathStep &lhs,
auto rhsRecTy = mlir::dyn_cast<fir::RecordType>(rhs.recordType);
// Different FIR types may be the same SEQUENCE or BIND(C) Fortran type.
if (!lhsRecTy || !rhsRecTy ||
- (!isProperExtensionOf(lhsRecTy, rhsRecTy) &&
- !isProperExtensionOf(rhsRecTy, lhsRecTy)))
+ (!isProperExtensionOf(lhsRecTy, rhsRecTy, getParentType) &&
+ !isProperExtensionOf(rhsRecTy, lhsRecTy, getParentType)))
return false;
mlir::Type lhsFieldTy = lhsRecTy.getType(lhs.component.getValue());
mlir::Type rhsFieldTy = rhsRecTy.getType(rhs.component.getValue());
@@ -647,17 +639,24 @@ componentsAreDisjoint(const fir::AliasAnalysis::Source::PathStep &lhs,
/// Return true if two access paths from the same origin variable address
/// disjoint subobjects of the root variable. The paths are not compared step
-/// by step: views of the root (e.g. CLASS views of an object of extended
+/// by step: views of an object (e.g. CLASS views of an object of extended
/// type, or SELECT TYPE selectors) change the type of the addressed object,
/// so steps at the same index may be components of different types. Instead,
/// the paths are disjoint if a component step of one path is disjoint from a
-/// component step of the other path (see componentsAreDisjoint). For
-/// example, paths [Component("a")] and [Component("b")] of the same record
-/// type are disjoint, while [Component("a"), Component("x")] and
+/// component step of the other path (see componentsAreDisjoint), and both
+/// steps select components of the same object. For example, paths
+/// [Component("a")] and [Component("b")] of the same record type are
+/// disjoint, while [Component("a"), Component("x")] and
/// [Component("a"), Component("y")] are disjoint at their second steps.
/// Paths crossing different storage-associated declarations are not
/// compared.
///
+/// A PointerDeref or AllocDeref step reaches another object, whose type may
+/// be related to the type of the root variable (e.g. an allocatable
+/// component of the parent type). So two component steps are compared only
+/// if neither of them follows a dereference, or if the steps up to and
+/// including the last dereference before each of them are the same.
+///
/// When either path continues through a PointerDeref or AllocDeref after
/// the disjoint steps, the runtime address could potentially reach a
/// sibling subobject only if that sibling is a valid pointer target.
@@ -673,37 +672,42 @@ componentsAreDisjoint(const fir::AliasAnalysis::Source::PathStep &lhs,
/// pointers could independently be associated with the same third-party
/// TARGET variable, so such steps do not prove disjointness.
static bool pathsDivergeAtComponent(const fir::AliasAnalysis::Source &lhsSrc,
- const fir::AliasAnalysis::Source &rhsSrc) {
+ const fir::AliasAnalysis::Source &rhsSrc,
+ GetParentTypeFn getParentType) {
using PathStep = fir::AliasAnalysis::Source::PathStep;
if (lhsSrc.accessPath.storageDecl != rhsSrc.accessPath.storageDecl)
return false;
- auto &lhsSteps = lhsSrc.accessPath.steps;
- auto &rhsSteps = rhsSrc.accessPath.steps;
+ llvm::ArrayRef<PathStep> lhsSteps = lhsSrc.accessPath.steps;
+ llvm::ArrayRef<PathStep> rhsSteps = rhsSrc.accessPath.steps;
auto hasPtrDeref = [](llvm::ArrayRef<PathStep> steps) {
return llvm::any_of(steps, [](const PathStep &s) {
return s.kind == PathStep::Kind::PointerDeref;
});
};
+ // Return the steps that reach the object whose component steps[idx]
+ // selects.
+ auto getObjectSteps = [](llvm::ArrayRef<PathStep> steps, size_t idx) {
+ llvm::ArrayRef<PathStep> prefix = steps.take_front(idx);
+ while (!prefix.empty() && prefix.back().kind == PathStep::Kind::Component)
+ prefix = prefix.drop_back();
+ return prefix;
+ };
for (size_t i = 0; i < lhsSteps.size(); ++i) {
if (lhsSteps[i].kind != PathStep::Kind::Component)
continue;
+ llvm::ArrayRef<PathStep> objectSteps = getObjectSteps(lhsSteps, i);
for (size_t j = 0; j < rhsSteps.size(); ++j) {
if (rhsSteps[j].kind != PathStep::Kind::Component ||
- !componentsAreDisjoint(lhsSteps[i], rhsSteps[j]))
+ getObjectSteps(rhsSteps, j) != objectSteps ||
+ !componentsAreDisjoint(lhsSteps[i], rhsSteps[j], getParentType))
continue;
- bool lhsHasPtrDeref =
- hasPtrDeref(llvm::ArrayRef<PathStep>(lhsSteps).drop_front(i + 1));
- bool rhsHasPtrDeref =
- hasPtrDeref(llvm::ArrayRef<PathStep>(rhsSteps).drop_front(j + 1));
+ bool lhsHasPtrDeref = hasPtrDeref(lhsSteps.drop_front(i + 1));
+ bool rhsHasPtrDeref = hasPtrDeref(rhsSteps.drop_front(j + 1));
if (lhsHasPtrDeref && rhsHasPtrDeref)
continue;
- if (lhsHasPtrDeref || rhsHasPtrDeref) {
- if (hasPtrDeref(llvm::ArrayRef<PathStep>(lhsSteps).take_front(i)) ||
- hasPtrDeref(llvm::ArrayRef<PathStep>(rhsSteps).take_front(j)))
- continue;
- if (lhsSrc.isTarget() || rhsSrc.isTarget())
- continue;
- }
+ if ((lhsHasPtrDeref || rhsHasPtrDeref) &&
+ (hasPtrDeref(objectSteps) || lhsSrc.isTarget() || rhsSrc.isTarget()))
+ continue;
return true;
}
}
@@ -909,6 +913,20 @@ AliasResult AliasAnalysis::alias(Source lhsSrc, Source rhsSrc, mlir::Value lhs,
}
if (lhsSrc.kind == rhsSrc.kind) {
+ // A derived type without fir.type_info is not known to be an extension.
+ auto getParentType = [&](fir::RecordType recTy) -> fir::RecordType {
+ mlir::Region *region = lhs.getParentRegion();
+ if (!region)
+ return {};
+ const mlir::SymbolTable *symTab =
+ getNearestSymbolTable(region->getParentOp());
+ auto module =
+ symTab ? mlir::dyn_cast<mlir::ModuleOp>(symTab->getOp()) : nullptr;
+ if (!module)
+ return {};
+ fir::TypeInfoOp typeInfo = fir::lookupTypeInfoOp(recTy, module, symTab);
+ return typeInfo ? typeInfo.getIfParentType() : fir::RecordType{};
+ };
// 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
@@ -917,7 +935,7 @@ AliasResult AliasAnalysis::alias(Source lhsSrc, Source rhsSrc, mlir::Value lhs,
LLVM_DEBUG(llvm::dbgs()
<< " aliasing because same source kind and origin\n");
if (approximateSource) {
- if (pathsDivergeAtComponent(lhsSrc, rhsSrc)) {
+ if (pathsDivergeAtComponent(lhsSrc, rhsSrc, getParentType)) {
LLVM_DEBUG(llvm::dbgs()
<< " no alias: different components of same origin\n");
return AliasResult::NoAlias;
@@ -957,7 +975,7 @@ AliasResult AliasAnalysis::alias(Source lhsSrc, Source rhsSrc, mlir::Value lhs,
if (lhsSrc.origin.u == rhsSrc.origin.u &&
((isRecordWithPointerComponent(lhs.getType()) && !rhsSrc.isData()) ||
(isRecordWithPointerComponent(rhs.getType()) && !lhsSrc.isData()))) {
- if (pathsDivergeAtComponent(lhsSrc, rhsSrc)) {
+ if (pathsDivergeAtComponent(lhsSrc, rhsSrc, getParentType)) {
LLVM_DEBUG(llvm::dbgs()
<< " no alias: different components of same origin\n");
return AliasResult::NoAlias;
diff --git a/flang/test/Analysis/AliasAnalysis/alias-analysis-access-path-record-type.fir b/flang/test/Analysis/AliasAnalysis/alias-analysis-access-path-record-type.fir
index 5a527766720cd..dd348a52261eb 100644
--- a/flang/test/Analysis/AliasAnalysis/alias-analysis-access-path-record-type.fir
+++ b/flang/test/Analysis/AliasAnalysis/alias-analysis-access-path-record-type.fir
@@ -1,7 +1,9 @@
// Tests for access paths in fir::AliasAnalysis through views that change the
// type of the addressed object: CLASS views of objects of extended types,
// SELECT TYPE, storage association, sequence association, and component
-// sections of arrays.
+// sections of arrays, and through dereferences of allocatable and pointer
+// components. The parent type of an extended type is given by its
+// fir.type_info.
// RUN: fir-opt %s -split-input-file -o /dev/null --mlir-disable-threading \
// RUN: -pass-pipeline='builtin.module(func.func(test-fir-alias-analysis))' \
@@ -20,6 +22,8 @@
!t1 = !fir.type<_QMmTt1{a:!fir.array<4xf32>}>
!t2 = !fir.type<_QMmTt2{t1:!t1,b:f32}>
+fir.type_info @_QMmTt1 : !t1
+fir.type_info @_QMmTt2 extends !t1 : !t2
func.func @_QPparent_view() {
%c1 = arith.constant 1 : index
%c4 = arith.constant 4 : index
@@ -52,6 +56,8 @@ func.func @_QPparent_view() {
!t1 = !fir.type<_QMmTt1{a:!fir.array<4xf32>}>
!t2 = !fir.type<_QMmTt2{t1:!t1,b:f32}>
+fir.type_info @_QMmTt1 : !t1
+fir.type_info @_QMmTt2 extends !t1 : !t2
func.func @_QParray_view(%i: index) {
%c10 = arith.constant 10 : index
%sh = fir.shape %c10 : (index) -> !fir.shape<1>
@@ -83,6 +89,9 @@ func.func @_QParray_view(%i: index) {
!t1 = !fir.type<_QMmTt1{a:!fir.array<4xf32>}>
!t2 = !fir.type<_QMmTt2{t1:!t1,b:f32}>
!t3 = !fir.type<_QMmTt3{t2:!t2,c:f32}>
+fir.type_info @_QMmTt1 : !t1
+fir.type_info @_QMmTt2 extends !t1 : !t2
+fir.type_info @_QMmTt3 extends !t2 : !t3
func.func @_QPtwo_level() {
%0 = fir.alloca !t3 {bindc_name = "obj", uniq_name = "_QFtwo_levelEobj"}
%obj = fir.declare %0 uniq_name("_QFtwo_levelEobj") : (!fir.ref<!t3>) -> !fir.ref<!t3>
@@ -111,6 +120,8 @@ func.func @_QPtwo_level() {
!t1 = !fir.type<_QMmTt1{a:!fir.array<4xf32>}>
!t2 = !fir.type<_QMmTt2{t1:!t1,b:f32}>
+fir.type_info @_QMmTt1 : !t1
+fir.type_info @_QMmTt2 extends !t1 : !t2
func.func @_QPup_down() {
%0 = fir.alloca !t2 {bindc_name = "obj", uniq_name = "_QFup_downEobj"}
%obj = fir.declare %0 uniq_name("_QFup_downEobj") : (!fir.ref<!t2>) -> !fir.ref<!t2>
@@ -142,6 +153,8 @@ func.func @_QPup_down() {
!t1 = !fir.type<_QMmTt1{a:!fir.array<4xf32>}>
!t2 = !fir.type<_QMmTt2{t1:!t1,b:f32}>
+fir.type_info @_QMmTt1 : !t1
+fir.type_info @_QMmTt2 extends !t1 : !t2
func.func @_QPsel(%arg0: !fir.class<!t1> {fir.bindc_name = "this"}) {
%c4 = arith.constant 4 : index
%sh = fir.shape %c4 : (index) -> !fir.shape<1>
@@ -170,6 +183,8 @@ func.func @_QPsel(%arg0: !fir.class<!t1> {fir.bindc_name = "this"}) {
!base = !fir.type<_QMmTbase{v:!fir.array<4xf32>}>
!child = !fir.type<_QMmTchild{base:!base,extra:!fir.array<4xf32>}>
+fir.type_info @_QMmTbase : !base
+fir.type_info @_QMmTchild extends !base : !child
func.func @_QMmPshift(%arg0: !fir.class<!base> {fir.bindc_name = "this"}) {
%c1 = arith.constant 1 : index
%c2 = arith.constant 2 : index
@@ -197,6 +212,8 @@ func.func @_QMmPshift(%arg0: !fir.class<!base> {fir.bindc_name = "this"}) {
!base = !fir.type<_QMmTbase{v:!fir.array<4xf32>}>
!child = !fir.type<_QMmTchild{base:!base,extra:!fir.array<4xf32>}>
+fir.type_info @_QMmTbase : !base
+fir.type_info @_QMmTchild extends !base : !child
func.func @_QMmPshift_inlined(%arg0: !fir.class<!base> {fir.bindc_name = "this"}, %i: index) {
%c4 = arith.constant 4 : index
%sh = fir.shape %c4 : (index) -> !fir.shape<1>
@@ -226,6 +243,8 @@ func.func @_QMmPshift_inlined(%arg0: !fir.class<!base> {fir.bindc_name = "this"}
!t1 = !fir.type<_QMmTt1{a:!fir.array<4xf32>}>
!t2 = !fir.type<_QMmTt2{t1:!t1,b:f32}>
+fir.type_info @_QMmTt1 : !t1
+fir.type_info @_QMmTt2 extends !t1 : !t2
func.func @_QPunlimited() {
%0 = fir.alloca !t2 {bindc_name = "obj", uniq_name = "_QFunlimitedEobj"}
%obj = fir.declare %0 uniq_name("_QFunlimitedEobj") : (!fir.ref<!t2>) -> !fir.ref<!t2>
@@ -364,6 +383,102 @@ func.func @_QPsame_sequence_type() {
// -----
+// A first component named after its type is not a parent component:
+// use ma, only: ta => t, shift
+// use mb, only: tb => t
+// type outer
+// type(ta) :: t
+// type(tb) :: other
+// end type
+// type(outer) :: obj
+// call shift(obj%other)
+// where modules ma and mb both define
+// type t
+// sequence
+// real :: a(4)
+// end type
+// and ma also contains
+// subroutine shift(x)
+// type(t) :: x
+// x%a(2:4) = x%a(1:3)
+// end subroutine
+// The two definitions of t describe the same type (F2023 7.5.2.4 p2). After
+// inlining, the actual argument obj%other (of type tb) is accessed through the
+// dummy x (of type ta): obj%other%a and x%a are the same memory.
+
+// CHECK-LABEL: Testing : "_QPsequence_component"
+// CHECK-DAG: obj_t_a#0 <-> x_a_i#0: NoAlias
+// CHECK-DAG: obj_other_a#0 <-> x_a_i#0: MayAlias
+// CHECK-DAG: x_a_i#0 <-> x_a_j#0: MayAlias
+
+!ta = !fir.type<_QMmaTt,sequence{a:!fir.array<4xf32>}>
+!tb = !fir.type<_QMmbTt,sequence{a:!fir.array<4xf32>}>
+!outer = !fir.type<_QFsequence_componentTouter{t:!ta,other:!tb}>
+fir.type_info @_QMmaTt : !ta
+fir.type_info @_QMmbTt : !tb
+fir.type_info @_QFsequence_componentTouter : !outer
+func.func @_QPsequence_component(%i: index, %j: index) {
+ %c4 = arith.constant 4 : index
+ %sh = fir.shape %c4 : (index) -> !fir.shape<1>
+ %caller_scope = fir.dummy_scope : !fir.dscope
+ %0 = fir.alloca !outer {bindc_name = "obj", uniq_name = "_QFsequence_componentEobj"}
+ %obj = fir.declare %0 uniq_name("_QFsequence_componentEobj") : (!fir.ref<!outer>) -> !fir.ref<!outer>
+ %ta = fir.coordinate_of %obj, t, a : (!fir.ref<!outer>) -> !fir.ref<!fir.array<4xf32>>
+ %obj_t_a = fir.array_coor %ta(%sh) %i {test.ptr = "obj_t_a"} : (!fir.ref<!fir.array<4xf32>>, !fir.shape<1>, index) -> !fir.ref<f32>
+ %other = fir.coordinate_of %obj, other : (!fir.ref<!outer>) -> !fir.ref<!tb>
+ %oa = fir.coordinate_of %other, a : (!fir.ref<!tb>) -> !fir.ref<!fir.array<4xf32>>
+ %obj_other_a = fir.array_coor %oa(%sh) %i {test.ptr = "obj_other_a"} : (!fir.ref<!fir.array<4xf32>>, !fir.shape<1>, index) -> !fir.ref<f32>
+ %actual = fir.convert %other : (!fir.ref<!tb>) -> !fir.ref<!ta>
+ %ds = fir.dummy_scope : !fir.dscope
+ %x = fir.declare %actual dummy_scope %ds arg 1 uniq_name("_QMmaFshiftEx") : (!fir.ref<!ta>, !fir.dscope) -> !fir.ref<!ta>
+ %xa = fir.coordinate_of %x, a : (!fir.ref<!ta>) -> !fir.ref<!fir.array<4xf32>>
+ %x_a_i = fir.array_coor %xa(%sh) %i {test.ptr = "x_a_i"} : (!fir.ref<!fir.array<4xf32>>, !fir.shape<1>, index) -> !fir.ref<f32>
+ %x_a_j = fir.array_coor %xa(%sh) %j {test.ptr = "x_a_j"} : (!fir.ref<!fir.array<4xf32>>, !fir.shape<1>, index) -> !fir.ref<f32>
+ return
+}
+
+// -----
+
+// The same with BIND(C) types t, which are the same type too, but are not
+// SEQUENCE types in FIR. Modules ma and mb both use c_float from iso_c_binding
+// and define
+// type, bind(c) :: t
+// real(c_float) :: a(4)
+// end type
+
+// CHECK-LABEL: Testing : "_QPbind_c_component"
+// CHECK-DAG: obj_t_a#0 <-> x_a_i#0: NoAlias
+// CHECK-DAG: obj_other_a#0 <-> x_a_i#0: MayAlias
+// CHECK-DAG: x_a_i#0 <-> x_a_j#0: MayAlias
+
+!ta = !fir.type<_QMmaTt{a:!fir.array<4xf32>}>
+!tb = !fir.type<_QMmbTt{a:!fir.array<4xf32>}>
+!outer = !fir.type<_QFbind_c_componentTouter{t:!ta,other:!tb}>
+fir.type_info @_QMmaTt : !ta
+fir.type_info @_QMmbTt : !tb
+fir.type_info @_QFbind_c_componentTouter : !outer
+func.func @_QPbind_c_component(%i: index, %j: index) {
+ %c4 = arith.constant 4 : index
+ %sh = fir.shape %c4 : (index) -> !fir.shape<1>
+ %caller_scope = fir.dummy_scope : !fir.dscope
+ %0 = fir.alloca !outer {bindc_name = "obj", uniq_name = "_QFbind_c_componentEobj"}
+ %obj = fir.declare %0 uniq_name("_QFbind_c_componentEobj") : (!fir.ref<!outer>) -> !fir.ref<!outer>
+ %ta = fir.coordinate_of %obj, t, a : (!fir.ref<!outer>) -> !fir.ref<!fir.array<4xf32>>
+ %obj_t_a = fir.array_coor %ta(%sh) %i {test.ptr = "obj_t_a"} : (!fir.ref<!fir.array<4xf32>>, !fir.shape<1>, index) -> !fir.ref<f32>
+ %other = fir.coordinate_of %obj, other : (!fir.ref<!outer>) -> !fir.ref<!tb>
+ %oa = fir.coordinate_of %other, a : (!fir.ref<!tb>) -> !fir.ref<!fir.array<4xf32>>
+ %obj_other_a = fir.array_coor %oa(%sh) %i {test.ptr = "obj_other_a"} : (!fir.ref<!fir.array<4xf32>>, !fir.shape<1>, index) -> !fir.ref<f32>
+ %actual = fir.convert %other : (!fir.ref<!tb>) -> !fir.ref<!ta>
+ %ds = fir.dummy_scope : !fir.dscope
+ %x = fir.declare %actual dummy_scope %ds arg 1 uniq_name("_QMmaFshiftEx") : (!fir.ref<!ta>, !fir.dscope) -> !fir.ref<!ta>
+ %xa = fir.coordinate_of %x, a : (!fir.ref<!ta>) -> !fir.ref<!fir.array<4xf32>>
+ %x_a_i = fir.array_coor %xa(%sh) %i {test.ptr = "x_a_i"} : (!fir.ref<!fir.array<4xf32>>, !fir.shape<1>, index) -> !fir.ref<f32>
+ %x_a_j = fir.array_coor %xa(%sh) %j {test.ptr = "x_a_j"} : (!fir.ref<!fir.array<4xf32>>, !fir.shape<1>, index) -> !fir.ref<f32>
+ return
+}
+
+// -----
+
// Sequence association of an element of a component after inlining:
// type r
// real :: c(10), d(10)
@@ -456,3 +571,126 @@ func.func @_QPdescriptor_cast() {
%cast_y = fir.coordinate_of %caddr, y {test.ptr = "cast_y"} : (!fir.ptr<!u>) -> !fir.ref<f32>
return
}
+
+// -----
+
+// Allocatable components of an extended type:
+// type base
+// real :: v(4)
+// end type
+// type, extends(base) :: child
+// type(base), allocatable :: a, b
+// end type
+// type(child) :: x
+// x%a%v(2:4) = x%a%v(1:3)
+// The access path of both sections is [a, AllocDeref, v]: component a of x,
+// the allocated object x%a, and component v of x%a. Component a of x is
+// disjoint from the parent component of x, which contains x%v, but v here is
+// a component of x%a, not of x, so this does not prove the sections disjoint.
+
+// CHECK-LABEL: Testing : "_QPalloc_component"
+// CHECK-DAG: a_v_lo#0 <-> a_v_hi#0: MayAlias
+// CHECK-DAG: a_v_lo#0 <-> b_v#0: NoAlias
+// CHECK-DAG: a_v_lo#0 <-> x_v#0: NoAlias
+
+!base = !fir.type<_QMmTbase{v:!fir.array<4xf32>}>
+!child = !fir.type<_QMmTchild{base:!base,a:!fir.box<!fir.heap<!base>>,b:!fir.box<!fir.heap<!base>>}>
+fir.type_info @_QMmTbase : !base
+fir.type_info @_QMmTchild extends !base : !child
+func.func @_QPalloc_component(%arg0: !fir.ref<!child> {fir.bindc_name = "x"}) {
+ %c1 = arith.constant 1 : index
+ %c2 = arith.constant 2 : index
+ %c3 = arith.constant 3 : index
+ %c4 = arith.constant 4 : index
+ %sh4 = fir.shape %c4 : (index) -> !fir.shape<1>
+ %sh3 = fir.shape %c3 : (index) -> !fir.shape<1>
+ %ds = fir.dummy_scope : !fir.dscope
+ %x:2 = hlfir.declare %arg0 dummy_scope %ds arg 1 uniq_name("_QFalloc_componentEx") : (!fir.ref<!child>, !fir.dscope) -> (!fir.ref<!child>, !fir.ref<!child>)
+ %a = hlfir.designate %x#0{"a"} fortran_attrs<allocatable> : (!fir.ref<!child>) -> !fir.ref<!fir.box<!fir.heap<!base>>>
+ %abox = fir.load %a : !fir.ref<!fir.box<!fir.heap<!base>>>
+ %aaddr = fir.box_addr %abox : (!fir.box<!fir.heap<!base>>) -> !fir.heap<!base>
+ %a_v_lo = hlfir.designate %aaddr{"v"} <%sh4> (%c1:%c3:%c1) shape %sh3 {test.ptr = "a_v_lo"} : (!fir.heap<!base>, !fir.shape<1>, index, index, index, !fir.shape<1>) -> !fir.ref<!fir.array<3xf32>>
+ %a_v_hi = hlfir.designate %aaddr{"v"} <%sh4> (%c2:%c4:%c1) shape %sh3 {test.ptr = "a_v_hi"} : (!fir.heap<!base>, !fir.shape<1>, index, index, index, !fir.shape<1>) -> !fir.ref<!fir.array<3xf32>>
+ %b = hlfir.designate %x#0{"b"} fortran_attrs<allocatable> : (!fir.ref<!child>) -> !fir.ref<!fir.box<!fir.heap<!base>>>
+ %bbox = fir.load %b : !fir.ref<!fir.box<!fir.heap<!base>>>
+ %baddr = fir.box_addr %bbox : (!fir.box<!fir.heap<!base>>) -> !fir.heap<!base>
+ %b_v = hlfir.designate %baddr{"v"} shape %sh4 {test.ptr = "b_v"} : (!fir.heap<!base>, !fir.shape<1>) -> !fir.ref<!fir.array<4xf32>>
+ %parent = hlfir.designate %x#0{"base"} : (!fir.ref<!child>) -> !fir.ref<!base>
+ %x_v = hlfir.designate %parent{"v"} shape %sh4 {test.ptr = "x_v"} : (!fir.ref<!base>, !fir.shape<1>) -> !fir.ref<!fir.array<4xf32>>
+ return
+}
+
+// -----
+
+// A pointer component that may be associated with an allocatable component
+// of the same object:
+// type, extends(base) :: child
+// type(base), allocatable :: a
+// type(base), pointer :: p
+// end type
+// type(child), target :: x
+// x%a%v(2:4) = x%p%v(1:3)
+
+// CHECK-LABEL: Testing : "_QPtarget_component"
+// CHECK-DAG: p_v#0 <-> a_v#0: MayAlias
+
+!base = !fir.type<_QMmTbase{v:!fir.array<4xf32>}>
+!child = !fir.type<_QMmTchild{base:!base,a:!fir.box<!fir.heap<!base>>,p:!fir.box<!fir.ptr<!base>>}>
+fir.type_info @_QMmTbase : !base
+fir.type_info @_QMmTchild extends !base : !child
+func.func @_QPtarget_component(%arg0: !fir.ref<!child> {fir.bindc_name = "x", fir.target}) {
+ %c1 = arith.constant 1 : index
+ %c2 = arith.constant 2 : index
+ %c3 = arith.constant 3 : index
+ %c4 = arith.constant 4 : index
+ %sh4 = fir.shape %c4 : (index) -> !fir.shape<1>
+ %sh3 = fir.shape %c3 : (index) -> !fir.shape<1>
+ %ds = fir.dummy_scope : !fir.dscope
+ %x:2 = hlfir.declare %arg0 dummy_scope %ds arg 1 uniq_name("_QFtarget_componentEx") fortran_attrs<target> : (!fir.ref<!child>, !fir.dscope) -> (!fir.ref<!child>, !fir.ref<!child>)
+ %p = hlfir.designate %x#0{"p"} fortran_attrs<pointer> : (!fir.ref<!child>) -> !fir.ref<!fir.box<!fir.ptr<!base>>>
+ %pbox = fir.load %p : !fir.ref<!fir.box<!fir.ptr<!base>>>
+ %paddr = fir.box_addr %pbox : (!fir.box<!fir.ptr<!base>>) -> !fir.ptr<!base>
+ %p_v = hlfir.designate %paddr{"v"} <%sh4> (%c1:%c3:%c1) shape %sh3 {test.ptr = "p_v"} : (!fir.ptr<!base>, !fir.shape<1>, index, index, index, !fir.shape<1>) -> !fir.ref<!fir.array<3xf32>>
+ %a = hlfir.designate %x#0{"a"} fortran_attrs<allocatable> : (!fir.ref<!child>) -> !fir.ref<!fir.box<!fir.heap<!base>>>
+ %abox = fir.load %a : !fir.ref<!fir.box<!fir.heap<!base>>>
+ %aaddr = fir.box_addr %abox : (!fir.box<!fir.heap<!base>>) -> !fir.heap<!base>
+ %a_v = hlfir.designate %aaddr{"v"} <%sh4> (%c2:%c4:%c1) shape %sh3 {test.ptr = "a_v"} : (!fir.heap<!base>, !fir.shape<1>, index, index, index, !fir.shape<1>) -> !fir.ref<!fir.array<3xf32>>
+ return
+}
+
+// -----
+
+// A recursive allocatable component:
+// type t
+// real :: v(4)
+// type(t), allocatable :: next
+// end type
+// type(t) :: x
+// x%next%v(2:4) = x%next%v(1:3)
+// The access path of both sections is [next, AllocDeref, v]: component next
+// of x, the allocated object x%next, and component v of x%next. The steps
+// next and v select different components of type t, but of different objects
+// (x and x%next), so they do not prove the sections disjoint.
+
+// CHECK-LABEL: Testing : "_QPrecursive_component"
+// CHECK-DAG: next_v_lo#0 <-> next_v_hi#0: MayAlias
+// CHECK-DAG: next_v_lo#0 <-> x_v#0: NoAlias
+
+!t = !fir.type<_QMmTt{v:!fir.array<4xf32>,next:!fir.box<!fir.heap<!fir.type<_QMmTt>>>}>
+func.func @_QPrecursive_component(%arg0: !fir.ref<!t> {fir.bindc_name = "x"}) {
+ %c1 = arith.constant 1 : index
+ %c2 = arith.constant 2 : index
+ %c3 = arith.constant 3 : index
+ %c4 = arith.constant 4 : index
+ %sh4 = fir.shape %c4 : (index) -> !fir.shape<1>
+ %sh3 = fir.shape %c3 : (index) -> !fir.shape<1>
+ %ds = fir.dummy_scope : !fir.dscope
+ %x:2 = hlfir.declare %arg0 dummy_scope %ds arg 1 uniq_name("_QFrecursive_componentEx") : (!fir.ref<!t>, !fir.dscope) -> (!fir.ref<!t>, !fir.ref<!t>)
+ %next = hlfir.designate %x#0{"next"} fortran_attrs<allocatable> : (!fir.ref<!t>) -> !fir.ref<!fir.box<!fir.heap<!t>>>
+ %nbox = fir.load %next : !fir.ref<!fir.box<!fir.heap<!t>>>
+ %naddr = fir.box_addr %nbox : (!fir.box<!fir.heap<!t>>) -> !fir.heap<!t>
+ %next_v_lo = hlfir.designate %naddr{"v"} <%sh4> (%c1:%c3:%c1) shape %sh3 {test.ptr = "next_v_lo"} : (!fir.heap<!t>, !fir.shape<1>, index, index, index, !fir.shape<1>) -> !fir.ref<!fir.array<3xf32>>
+ %next_v_hi = hlfir.designate %naddr{"v"} <%sh4> (%c2:%c4:%c1) shape %sh3 {test.ptr = "next_v_hi"} : (!fir.heap<!t>, !fir.shape<1>, index, index, index, !fir.shape<1>) -> !fir.ref<!fir.array<3xf32>>
+ %x_v = hlfir.designate %x#0{"v"} shape %sh4 {test.ptr = "x_v"} : (!fir.ref<!t>, !fir.shape<1>) -> !fir.ref<!fir.array<4xf32>>
+ return
+}
diff --git a/flang/test/HLFIR/inline-hlfir-assign-select-type.fir b/flang/test/HLFIR/inline-hlfir-assign-select-type.fir
index 1c064cc379951..3ed42004855c0 100644
--- a/flang/test/HLFIR/inline-hlfir-assign-select-type.fir
+++ b/flang/test/HLFIR/inline-hlfir-assign-select-type.fir
@@ -23,6 +23,8 @@
!base = !fir.type<_QMmTbase{v:!fir.array<4xf32>}>
!child = !fir.type<_QMmTchild{base:!base,extra:!fir.array<4xf32>}>
+fir.type_info @_QMmTbase : !base
+fir.type_info @_QMmTchild extends !base : !child
func.func @_QMmPshift(%arg0: !fir.class<!base> {fir.bindc_name = "this"}) {
%c1 = arith.constant 1 : index
%c2 = arith.constant 2 : index
More information about the flang-commits
mailing list