[flang-commits] [flang] [flang] Support declarations scoping in FIR AA. (PR #201216)
Slava Zakharin via flang-commits
flang-commits at lists.llvm.org
Tue Jun 2 16:58:25 PDT 2026
https://github.com/vzakhari updated https://github.com/llvm/llvm-project/pull/201216
>From 9ee7a020b187b8a4cf56a9952c57274ec4b2c805 Mon Sep 17 00:00:00 2001
From: Slava Zakharin <szakharin at nvidia.com>
Date: Tue, 2 Jun 2026 14:48:28 -0700
Subject: [PATCH 1/2] [flang] Support declarations scoping in FIR AA.
Further experimentation with MLIR inlining showed that
FIR AA becomes more conservative once a subprogram is inlined.
For example:
```
subroutine caller(p1,p2)
real, pointer :: p1,p1
call callee(p1,p2)
end
subroutine callee(a1,a2)
real :: a1,a2
a1 = a2
end
```
After `callee` is inlined, FIR AA assumes that `a1` and `a2`
alias at the point of the assignment, because it classifies them
as pointer accesses.
This patch adds a machinery to collect declaration information
for multiple subprogram scopes (as currently defined by `fir.dummy_scope`),
so that FIR AA can use this information for better disambiguation.
---
.../flang/Optimizer/Analysis/AliasAnalysis.h | 99 +++++-
.../lib/Optimizer/Analysis/AliasAnalysis.cpp | 286 +++++++++++++++++-
.../alias-analysis-scoped-origins.fir | 172 +++++++++++
.../Analysis/AliasAnalysis/ptr-component.fir | 40 ++-
4 files changed, 560 insertions(+), 37 deletions(-)
create mode 100644 flang/test/Analysis/AliasAnalysis/alias-analysis-scoped-origins.fir
diff --git a/flang/include/flang/Optimizer/Analysis/AliasAnalysis.h b/flang/include/flang/Optimizer/Analysis/AliasAnalysis.h
index 115358613e383..12a9f33d46815 100644
--- a/flang/include/flang/Optimizer/Analysis/AliasAnalysis.h
+++ b/flang/include/flang/Optimizer/Analysis/AliasAnalysis.h
@@ -13,11 +13,13 @@
#include "flang/Common/enum-set.h"
#include "mlir/Analysis/AliasAnalysis.h"
#include "mlir/IR/BuiltinAttributes.h"
+#include "mlir/IR/Dominance.h"
#include "mlir/IR/SymbolTable.h"
#include "mlir/IR/Value.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/PointerUnion.h"
#include "llvm/ADT/SmallVector.h"
+#include <memory>
namespace fir {
@@ -198,6 +200,58 @@ struct AliasAnalysis {
void print(llvm::raw_ostream &os) const;
};
+ /// A snapshot taken when getSource() walks through an [hl]fir.declare.
+ /// Records the Fortran procedure scope of the declare (the dummy_scope
+ /// SSA value -- nullptr for non-dummy frames), the declare's result SSA
+ /// value, and the access path and attributes accumulated FROM THE LEAF
+ /// UP TO (and including) this declare. alias() uses these snapshots to
+ /// rebuild intermediate Sources rooted at shared-scope declares via
+ /// buildSourceAtDeclare().
+ ///
+ /// Relationship to the enclosing Source's top-level fields:
+ /// - Source::accessPath / Source::attributes / Source::approximateSource
+ /// describe the walk's TERMINAL stop ("root-of-walk to leaf"). They
+ /// are what every existing alias rule and external consumer reads.
+ /// - The root-closest ScopedOrigin is a snapshot at the last
+ /// declare crossed before that terminal stop. Because getSource()
+ /// walks backwards (leaf -> root) and push_back()s each snapshot,
+ /// this is the LAST element pushed, i.e. scopedOrigins.back() --
+ /// see the ordering note on the scopedOrigins member below (front
+ /// = leaf-closest, back = root-closest). Its accessPath is a
+ /// prefix-truncation of Source::accessPath, and its attributes are
+ /// a subset of Source::attributes; in the common case where the
+ /// terminal stop IS a declare (dummy with dummy_scope, host-assoc,
+ /// OpenMP private), they coincide.
+ /// We keep both because:
+ /// 1. alias(Source, Source, ...) and external consumers (AddAliasTags,
+ /// print, downstream analyses) read the top-level fields.
+ /// 2. When the terminal stop is not a declare (e.g. fir.alloca,
+ /// fir.address_of with TARGET-attributed global), the top-level
+ /// captures attributes the root-closest snapshot does not.
+ /// 3. When the walk terminates at Unknown without crossing any
+ /// declare, scopedOrigins is empty and the top-level is the only
+ /// answer.
+ struct ScopedOrigin {
+ /// The dummy_scope SSA value governing the declare. May be null Value
+ /// when the declare has no explicit dummy_scope and no fir.dummy_scope
+ /// op dominates it (e.g. globals at module scope).
+ mlir::Value scope;
+ /// Result SSA value of the [hl]fir.declare op.
+ mlir::Value declValue;
+ /// Path from the declare (treated as root) to the leaf Value the
+ /// original getSource() call was started from, in root-to-leaf order.
+ AccessPath accessPath;
+ /// Attributes accumulated from the leaf up to and including this
+ /// declare (includes getAttrsFromVariable(declare) and any
+ /// path-acquired bits such as Pointer from intermediate box loads).
+ Attributes attributes;
+ /// Whether the path is approximate at the moment of the snapshot.
+ bool approximateSource{false};
+ /// Whether the walk was following data (vs. a box reference) at the
+ /// moment of the snapshot.
+ bool isData{false};
+ };
+
SourceOrigin origin;
/// Kind of the memory source.
@@ -213,6 +267,11 @@ struct AliasAnalysis {
AccessPath accessPath;
/// Source object is used in an internal procedure via host association.
bool isCapturedInInternalProcedure{false};
+ /// Per-declare checkpoints collected as getSource() walked through
+ /// [hl]fir.declare operations, ordered from leaf-closest (front) to
+ /// root-closest (back). Empty when no declare was crossed (e.g. the
+ /// walk terminated at Unknown).
+ llvm::SmallVector<ScopedOrigin, 4> scopedOrigins;
/// Print information about the memory source to `os`.
void print(llvm::raw_ostream &os) const;
@@ -288,14 +347,34 @@ struct AliasAnalysis {
/// If getLastInstantiationPoint is true, the search for the source
/// will stop at [hl]fir.declare if it represents a dummy
/// argument declaration (i.e. it has the dummy_scope operand).
+ /// If collectScopedOrigins is false, the per-declare ScopedOrigin
+ /// snapshots are not collected (used internally by buildSourceAtDeclare
+ /// to reuse getSource purely for declare classification without the
+ /// bookkeeping side effect).
fir::AliasAnalysis::Source getSource(mlir::Value,
- bool getLastInstantiationPoint = false);
+ bool getLastInstantiationPoint = false,
+ bool collectScopedOrigins = true);
/// Return true, if `ty` is a reference type to a boxed
/// POINTER object or a raw fir::PointerType.
static bool isPointerReference(mlir::Type ty);
private:
+ /// Build an intermediate Source rooted at the declare captured by the
+ /// snapshot. Reuses getSource(declValue) for the SourceKind / origin
+ /// classification (with collectScopedOrigins=false), then overrides
+ /// accessPath/attributes/approximateSource/origin.isData from the
+ /// snapshot so the returned Source represents "declare-as-root,
+ /// original-query-as-leaf".
+ Source buildSourceAtDeclare(const Source::ScopedOrigin &so);
+
+ /// Return the dummy_scope SSA value governing \p declareOp.
+ /// Prefers the declare's explicit getDummyScope() operand; otherwise
+ /// falls back to the result of the dominating fir.dummy_scope op in
+ /// the parent func. Returns a null Value when no scope is found
+ /// (e.g. globals at module scope).
+ mlir::Value getDeclarationScope(mlir::Operation *declareOp);
+
/// Return true, if `ty` is a reference type to an object of derived type
/// that contains a component with POINTER attribute.
static bool isRecordWithPointerComponent(mlir::Type ty);
@@ -334,6 +413,24 @@ struct AliasAnalysis {
/// a few values that are likely not globals.
/// We can have both modes for different clients.
llvm::DenseMap<mlir::Operation *, mlir::SymbolTable> symTabMap;
+
+ /// Per-function caches used by getDeclarationScope() to map a
+ /// fir.declare without an explicit dummy_scope operand to its
+ /// dominating fir.dummy_scope op. Lazily populated. Mirrors the
+ /// logic in flang/lib/Optimizer/Transforms/AddAliasTags.cpp::
+ /// PassState::processFunctionScopes / getDeclarationScope. The cache
+ /// stores fir.dummy_scope ops as mlir::Operation * pointers to avoid
+ /// pulling FIROps.h into this header; the .cpp casts back as needed.
+ ///
+ /// TODO: this duplicates the scope-mapping logic in AddAliasTags.cpp.
+ /// AddAliasTags should reuse AliasAnalysis::getDeclarationScope (and
+ /// the ScopedOrigin snapshots collected by getSource) instead of
+ /// maintaining its own PassState::processFunctionScopes, so the two
+ /// places cannot diverge.
+ llvm::DenseMap<mlir::Operation *, std::unique_ptr<mlir::DominanceInfo>>
+ domInfoCache;
+ llvm::DenseMap<mlir::Operation *, llvm::SmallVector<mlir::Operation *, 16>>
+ sortedScopeCache;
};
inline bool operator==(const AliasAnalysis::Source::SourceOrigin &lhs,
diff --git a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
index 5e92ed5f42665..42f4d5ec26206 100644
--- a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
+++ b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
@@ -16,6 +16,7 @@
#include "flang/Optimizer/Support/InternalNames.h"
#include "flang/Optimizer/Support/Utils.h"
#include "mlir/Analysis/AliasAnalysis.h"
+#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/OpenACC/OpenACC.h"
#include "mlir/Dialect/OpenACC/OpenACCUtils.h"
#include "mlir/Dialect/OpenMP/OpenMPDialect.h"
@@ -127,7 +128,8 @@ static fir::AliasAnalysis::Source getSourceForACCMappedValue(
accumulatedAttrs,
/*approximateSource=*/false,
/*accessPath=*/{},
- /*isCapturedInInternalProcedure=*/false};
+ /*isCapturedInInternalProcedure=*/false,
+ /*scopedOrigins=*/{}};
// Not private-like: classify using the corresponding host variable's source.
//
@@ -286,8 +288,74 @@ static fir::AliasAnalysis::Source mergeRegionBranchPredecessorSources(
mlir::Type mergedTy = allTypesSame ? sources[0].valueType : fallbackType;
- return {mergedOrigin, mergedKind, mergedTy, mergedAttrs,
- mergedApprox, mergedPath, mergedCaptured};
+ // Intersect scopedOrigins across predecessors by (scope, declValue).
+ // For an entry that matches in every predecessor, take the bitwise
+ // union (|=) of its 'attributes' and 'approximateSource' bits and
+ // keep the first predecessor's path steps (the steps must be equal
+ // to match, so there is nothing to merge there). Drop the entry on a
+ // path-step or isData mismatch, because different control-flow shapes
+ // from the same declare to the merge point cannot be summarised
+ // safely.
+ llvm::SmallVector<fir::AliasAnalysis::Source::ScopedOrigin, 4>
+ mergedScopedOrigins;
+ if (!sources.empty()) {
+ // Seed with the first predecessor's snapshots, keyed by
+ // (scope, declValue) for intersection lookups.
+ llvm::DenseMap<std::pair<void *, void *>, unsigned> indexInMerged;
+ mergedScopedOrigins.assign(sources[0].scopedOrigins.begin(),
+ sources[0].scopedOrigins.end());
+ for (unsigned i = 0; i < mergedScopedOrigins.size(); ++i) {
+ const auto &scopedOrigin = mergedScopedOrigins[i];
+ indexInMerged[{scopedOrigin.scope.getAsOpaquePointer(),
+ scopedOrigin.declValue.getAsOpaquePointer()}] = i;
+ }
+ llvm::SmallVector<bool, 4> seenThisPred;
+ for (unsigned predIdx = 1; predIdx < sources.size(); ++predIdx) {
+ seenThisPred.assign(mergedScopedOrigins.size(), false);
+ for (const auto &scopedOrigin : sources[predIdx].scopedOrigins) {
+ auto it =
+ indexInMerged.find({scopedOrigin.scope.getAsOpaquePointer(),
+ scopedOrigin.declValue.getAsOpaquePointer()});
+ if (it == indexInMerged.end())
+ continue;
+ unsigned idx = it->second;
+ auto &mergedScopedOrigin = mergedScopedOrigins[idx];
+ if (mergedScopedOrigin.isData != scopedOrigin.isData ||
+ mergedScopedOrigin.accessPath.steps !=
+ scopedOrigin.accessPath.steps) {
+ // Mark as not-seen so the entry is dropped after this pred.
+ continue;
+ }
+ mergedScopedOrigin.attributes |= scopedOrigin.attributes;
+ mergedScopedOrigin.approximateSource |= scopedOrigin.approximateSource;
+ mergedScopedOrigin.accessPath.isApproximate |=
+ scopedOrigin.accessPath.isApproximate;
+ seenThisPred[idx] = true;
+ }
+ // Drop entries this predecessor did not match. Iterate in reverse
+ // to keep earlier indices valid while erasing.
+ for (int idx = static_cast<int>(mergedScopedOrigins.size()) - 1; idx >= 0;
+ --idx) {
+ if (!seenThisPred[idx]) {
+ const auto &scopedOrigin = mergedScopedOrigins[idx];
+ indexInMerged.erase({scopedOrigin.scope.getAsOpaquePointer(),
+ scopedOrigin.declValue.getAsOpaquePointer()});
+ mergedScopedOrigins.erase(mergedScopedOrigins.begin() + idx);
+ }
+ }
+ // Recompute indices after erase shifts.
+ indexInMerged.clear();
+ for (unsigned i = 0; i < mergedScopedOrigins.size(); ++i) {
+ const auto &scopedOrigin = mergedScopedOrigins[i];
+ indexInMerged[{scopedOrigin.scope.getAsOpaquePointer(),
+ scopedOrigin.declValue.getAsOpaquePointer()}] = i;
+ }
+ }
+ }
+
+ return {
+ mergedOrigin, mergedKind, mergedTy, mergedAttrs,
+ mergedApprox, mergedPath, mergedCaptured, std::move(mergedScopedOrigins)};
}
namespace fir {
@@ -536,15 +604,49 @@ AliasResult AliasAnalysis::alias(mlir::Value lhs, mlir::Value rhs) {
// through other dialects
auto lhsSrc = getSource(lhs);
auto rhsSrc = getSource(rhs);
- return alias(lhsSrc, rhsSrc, lhs, rhs);
+ AliasResult result = alias(lhsSrc, rhsSrc, lhs, rhs);
+
+ // Scope-aware refinement after inlining: if both walks crossed declares
+ // in the SAME Fortran procedure scope at DISTINCT declare values, the
+ // two queries may still be disambiguated by rebuilding intermediate
+ // Sources rooted at each shared-scope declare pair (Fortran 2018
+ // 15.5.2.13: distinct dummy arguments / locals of the same procedure
+ // frame do not alias unless TARGET/POINTER/Cray attributes permit it).
+ // The per-pair check delegates back to the 4-arg alias() with paths and
+ // attributes snapshotted at the declares, so TARGET-attributed declares
+ // and pointer-dereferenced paths remain correctly reported as MayAlias.
+ // Short-circuit on NoAlias since any pair that disambiguates is
+ // decisive.
+ if (result == AliasResult::NoAlias || result == AliasResult::MustAlias)
+ return result;
+ for (const auto &lhsScopedOrigin : lhsSrc.scopedOrigins) {
+ if (!lhsScopedOrigin.scope)
+ continue;
+ for (const auto &rhsScopedOrigin : rhsSrc.scopedOrigins) {
+ if (lhsScopedOrigin.scope != rhsScopedOrigin.scope ||
+ lhsScopedOrigin.declValue == rhsScopedOrigin.declValue)
+ continue;
+ Source lhsInner = buildSourceAtDeclare(lhsScopedOrigin);
+ Source rhsInner = buildSourceAtDeclare(rhsScopedOrigin);
+ // Use the OUTER lhs/rhs values, not the declare values: the
+ // rebuilt Sources describe accessing the outer query's leaf
+ // through the captured declare. Passing the declares here would
+ // make type-based checks (e.g. descriptor-vs-data via
+ // noAliasBasedOnType) compare the declare's box-descriptor type
+ // to the outer leaf type and yield bogus NoAlias for pointer
+ // dereferences.
+ AliasResult refined = alias(lhsInner, rhsInner, lhs, rhs);
+ if (refined == AliasResult::NoAlias) {
+ LLVM_DEBUG(llvm::dbgs() << " no alias via scoped-origin refinement\n");
+ return AliasResult::NoAlias;
+ }
+ }
+ }
+ return result;
}
AliasResult AliasAnalysis::alias(Source lhsSrc, Source rhsSrc, mlir::Value lhs,
mlir::Value rhs) {
- // TODO: alias() has to be aware of the function scopes.
- // After MLIR inlining, the current implementation may
- // not recognize non-aliasing entities.
-
// If both values trace back to the same root through zero-offset view
// operations (e.g. embox without slice, declare, convert), they access
// the same underlying memory. This check avoids the case where
@@ -996,7 +1098,8 @@ static mlir::Value walkBlockArgPassThroughs(mlir::Value v) {
}
AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
- bool getLastInstantiationPoint) {
+ bool getLastInstantiationPoint,
+ bool collectScopedOrigins) {
// If v is a pass-through block argument (see walkBlockArgPassThroughs),
// continue from the underlying operand so the tracking loop below has a
// defining op to chew on. Without this, a recursive query like the one in
@@ -1025,6 +1128,13 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
llvm::SmallVector<Source::PathStep, 4> pathSteps;
Source::AccessPath accessPath;
bool accessPathFinalized{false};
+
+ // Per-declare snapshots collected as the walk crosses [hl]fir.declare ops.
+ // Ordered from leaf-closest (front) to root-closest (back). Forwarded
+ // through region-branch merges and the box-load branch, then threaded into
+ // the final Source. Gated on collectScopedOrigins (suppressed when
+ // buildSourceAtDeclare reuses getSource purely for declare classification).
+ llvm::SmallVector<Source::ScopedOrigin, 4> scopedOrigins;
while (defOp && !breakFromLoop) {
// Operations may have multiple results, so we need to analyze
// the result for which the source is queried.
@@ -1106,7 +1216,17 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
if (isPointerBox)
attributes.set(Attribute::Pointer);
- auto boxSrc = getSource(op.getMemref());
+ // Keep the inner walk's getLastInstantiationPoint=false so it
+ // continues past dummy-scope declares to the underlying
+ // BlockArgument. The outer classification below relies on
+ // boxSrc.origin.u being the BlockArg (so isDummyArgument()
+ // succeeds and the outer SourceKind becomes Argument).
+ // Passing true here would stop the inner walk at the declare
+ // and force SourceKind::Indirect, which spuriously coarsens
+ // getCallModRef (e.g. for box_addr of allocatable dummies).
+ auto boxSrc = getSource(op.getMemref(),
+ /*getLastInstantiationPoint=*/false,
+ collectScopedOrigins);
attributes |= boxSrc.attributes;
approximateSource |= boxSrc.approximateSource;
isCapturedInInternalProcedure |=
@@ -1133,6 +1253,30 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
boxSrc.accessPath.isApproximate || approximateSource;
accessPathFinalized = true;
+ // Rebase each forwarded ScopedOrigin from the inner walk's
+ // coordinate system (rooted at the inner declare, leaf=memref)
+ // to the outer (leaf=original query) by splicing the deref
+ // step and the outer pathSteps onto the snapshot's path.
+ if (collectScopedOrigins) {
+ for (auto scopedOrigin : boxSrc.scopedOrigins) {
+ scopedOrigin.accessPath.steps.push_back(derefStep);
+ for (int i = pathSteps.size() - 1; i >= 0; --i)
+ scopedOrigin.accessPath.steps.push_back(pathSteps[i]);
+ scopedOrigin.accessPath.isApproximate |= approximateSource;
+ if (isPointerBox)
+ scopedOrigin.attributes.set(Attribute::Pointer);
+ scopedOrigin.approximateSource |= approximateSource;
+ // The inner walk computed isData against the box memref
+ // (typically false, since the walk started at
+ // !fir.ref<!fir.box<...>>). After splicing the deref step, the
+ // snapshot's path now describes reaching the outer query's leaf
+ // via the box's pointer, so it follows data iff the outer walk
+ // does.
+ scopedOrigin.isData = followingData;
+ scopedOrigins.push_back(std::move(scopedOrigin));
+ }
+ }
+
global = llvm::dyn_cast<mlir::SymbolRefAttr>(boxSrc.origin.u);
if (global) {
type = SourceKind::Global;
@@ -1240,6 +1384,23 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
attributes |= getAttrsFromVariable(varIf);
isCapturedInInternalProcedure |=
varIf.isCapturedInInternalProcedure();
+
+ // Snapshot a ScopedOrigin at this declare. The snapshot
+ // captures the path/attributes from the leaf to this declare
+ // and is used by alias() for scope-aware refinement.
+ if (collectScopedOrigins) {
+ Source::ScopedOrigin scopedOrigin;
+ scopedOrigin.scope = getDeclarationScope(op);
+ scopedOrigin.declValue = opResult;
+ scopedOrigin.accessPath.steps.assign(pathSteps.rbegin(),
+ pathSteps.rend());
+ scopedOrigin.accessPath.isApproximate = approximateSource;
+ scopedOrigin.attributes = attributes;
+ scopedOrigin.approximateSource = approximateSource;
+ scopedOrigin.isData = followingData;
+ scopedOrigins.push_back(std::move(scopedOrigin));
+ }
+
if (varIf.isHostAssoc()) {
// Do not track past such DeclareOp, because it does not
// currently provide any useful information. The host associated
@@ -1397,7 +1558,8 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
accSourceReturn = getSourceForACCMappedValue(
v, op.getOperation(),
[&](mlir::Value x) {
- return getSource(x, getLastInstantiationPoint);
+ return getSource(x, getLastInstantiationPoint,
+ collectScopedOrigins);
},
followingData, attributes);
breakFromLoop = true;
@@ -1415,20 +1577,37 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
attributes,
/*approximateSource=*/true,
/*accessPath=*/{},
- isCapturedInInternalProcedure}};
+ isCapturedInInternalProcedure,
+ /*scopedOrigins=*/{}}};
breakFromLoop = true;
return;
}
llvm::SmallVector<AliasAnalysis::Source, 4> predSources;
predSources.reserve(predecessors.size());
for (mlir::Value pred : predecessors)
- predSources.push_back(getSource(pred, getLastInstantiationPoint));
+ predSources.push_back(getSource(pred, getLastInstantiationPoint,
+ collectScopedOrigins));
regionBranchReturn = mergeRegionBranchPredecessorSources(
predSources, v, ty, followingData);
regionBranchReturn->attributes |= attributes;
regionBranchReturn->approximateSource |= approximateSource;
regionBranchReturn->isCapturedInInternalProcedure |=
isCapturedInInternalProcedure;
+ // Prepend the outer (leaf-closer) scopedOrigins -- declares
+ // already crossed between leaf and this region-branch op --
+ // to the merged predecessors' snapshots. The inner snapshots'
+ // paths are relative to the region-branch result (matching
+ // the existing approximation for the top-level accessPath
+ // composed across region-branch merges).
+ if (collectScopedOrigins && !scopedOrigins.empty()) {
+ llvm::SmallVector<Source::ScopedOrigin, 4> combined;
+ combined.reserve(scopedOrigins.size() +
+ regionBranchReturn->scopedOrigins.size());
+ combined.append(scopedOrigins.begin(), scopedOrigins.end());
+ combined.append(regionBranchReturn->scopedOrigins.begin(),
+ regionBranchReturn->scopedOrigins.end());
+ regionBranchReturn->scopedOrigins = std::move(combined);
+ }
breakFromLoop = true;
})
.Default([&](auto op) {
@@ -1481,7 +1660,8 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
attributes,
approximateSource,
accessPath,
- isCapturedInInternalProcedure};
+ isCapturedInInternalProcedure,
+ std::move(scopedOrigins)};
}
return {{v, instantiationPoint, followingData},
type,
@@ -1489,7 +1669,8 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
attributes,
approximateSource,
accessPath,
- isCapturedInInternalProcedure};
+ isCapturedInInternalProcedure,
+ std::move(scopedOrigins)};
}
const mlir::SymbolTable *
@@ -1504,4 +1685,79 @@ fir::AliasAnalysis::getNearestSymbolTable(mlir::Operation *from) {
return &symTabMap.try_emplace(symTabOp, symTabOp).first->second;
}
+mlir::Value
+fir::AliasAnalysis::getDeclarationScope(mlir::Operation *declareOp) {
+ assert(declareOp && "expected a non-null declare op");
+ // Prefer the declare's explicit dummy_scope operand when present.
+ if (auto hlfirDeclareOp = mlir::dyn_cast<hlfir::DeclareOp>(declareOp))
+ if (mlir::Value dummyScope = hlfirDeclareOp.getDummyScope())
+ return dummyScope;
+ if (auto firDeclareOp = mlir::dyn_cast<fir::DeclareOp>(declareOp))
+ if (mlir::Value dummyScope = firDeclareOp.getDummyScope())
+ return dummyScope;
+
+ // Otherwise look up the dominating fir.dummy_scope in the parent
+ // function. Mirrors PassState::getDeclarationScope in AddAliasTags.cpp.
+ auto func = declareOp->getParentOfType<mlir::func::FuncOp>();
+ if (!func)
+ return {};
+
+ mlir::Operation *funcOp = func.getOperation();
+ auto domIt = domInfoCache.find(funcOp);
+ if (domIt == domInfoCache.end()) {
+ auto inserted = domInfoCache.try_emplace(
+ funcOp, std::make_unique<mlir::DominanceInfo>(funcOp));
+ domIt = inserted.first;
+ }
+ mlir::DominanceInfo &domInfo = *domIt->second;
+
+ auto scopeIt = sortedScopeCache.find(funcOp);
+ if (scopeIt == sortedScopeCache.end()) {
+ llvm::SmallVector<mlir::Operation *, 16> scopeOps;
+ func.walk(
+ [&](fir::DummyScopeOp op) { scopeOps.push_back(op.getOperation()); });
+ llvm::stable_sort(scopeOps, [&](mlir::Operation *a, mlir::Operation *b) {
+ return domInfo.properlyDominates(a, b);
+ });
+ scopeIt = sortedScopeCache.insert({funcOp, std::move(scopeOps)}).first;
+ }
+
+ const auto &scopeOps = scopeIt->second;
+ for (auto it = scopeOps.rbegin(), ie = scopeOps.rend(); it != ie; ++it) {
+ if (domInfo.dominates(*it, declareOp))
+ return mlir::cast<fir::DummyScopeOp>(*it).getResult();
+ }
+ return {};
+}
+
+fir::AliasAnalysis::Source fir::AliasAnalysis::buildSourceAtDeclare(
+ const fir::AliasAnalysis::Source::ScopedOrigin &scopedOrigin) {
+ // Reuse getSource for classification (handles dummy_scope, alloca/
+ // allocmem, address_of, etc. exactly as the main walk does). Disable
+ // ScopedOrigin collection so we do not allocate snapshots that would
+ // be immediately discarded.
+ //
+ // Pass getLastInstantiationPoint=true so the walk STOPS at the captured
+ // declare and classifies the Source in that declare's own scope: a
+ // dummy-scope declare becomes SourceKind::Argument, while a local/global
+ // declare still continues to its alloca/address_of (Allocate/Global).
+ // This is essential after inlining: with getLastInstantiationPoint=false
+ // the walk would continue past the dummy declare through cross-scope
+ // chains (e.g. fir.embox/fir.box_addr/scf.if introduced by contiguity
+ // copy-in or OPTIONAL select in the caller frame), whose region-branch
+ // merge collapses the kind to SourceKind::Unknown and makes the 4-arg
+ // alias() report MayAlias -- defeating the whole point of the refinement.
+ Source source = getSource(scopedOrigin.declValue,
+ /*getLastInstantiationPoint=*/true,
+ /*collectScopedOrigins=*/false);
+ // Rebase path/attributes to the snapshot taken when the original
+ // walk crossed this declare, so the returned Source represents
+ // "declare-as-root, original-query-as-leaf".
+ source.accessPath = scopedOrigin.accessPath;
+ source.attributes = scopedOrigin.attributes;
+ source.approximateSource = scopedOrigin.approximateSource;
+ source.origin.isData = scopedOrigin.isData;
+ return source;
+}
+
} // namespace fir
diff --git a/flang/test/Analysis/AliasAnalysis/alias-analysis-scoped-origins.fir b/flang/test/Analysis/AliasAnalysis/alias-analysis-scoped-origins.fir
new file mode 100644
index 0000000000000..afbb8046c4639
--- /dev/null
+++ b/flang/test/Analysis/AliasAnalysis/alias-analysis-scoped-origins.fir
@@ -0,0 +1,172 @@
+// Check the ScopedOrigin-based scope-aware refinement in FIR
+// AliasAnalysis. After FIR inlining, the AliasAnalysis::getSource
+// walk no longer terminates at the original [hl]fir.declare of a
+// dummy argument: an inlined call appears as a sequence of fir.box
+// / fir.declare / fir.dummy_scope / fir.embox operations that walk
+// past the variable's declaration. The scope-aware refinement uses
+// per-declare snapshots (ScopedOrigin) collected during the walk so
+// that pairs of declares dominated by the same fir.dummy_scope can
+// still be disambiguated.
+//
+// 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
+
+// -----
+
+// Two distinct, plain (non-TARGET, non-POINTER) dummy arguments of
+// the SAME procedure frame: Fortran 2018 15.5.2.13 says they cannot
+// alias. Without scope-aware refinement, the walks would terminate
+// at the Argument SourceKind but both Sources would have unrelated
+// roots and the alias() conservative path would report MayAlias.
+//
+// CHECK-LABEL: Testing : "_QPtest_two_dummies"
+// CHECK-DAG: x#0 <-> y#0: NoAlias
+func.func @_QPtest_two_dummies(
+ %arg0: !fir.ref<f32> {fir.bindc_name = "x"},
+ %arg1: !fir.ref<f32> {fir.bindc_name = "y"}) {
+ %scope = fir.dummy_scope : !fir.dscope
+ %x:2 = hlfir.declare %arg0 dummy_scope %scope
+ {test.ptr = "x", uniq_name = "_QFtest_two_dummiesEx"}
+ : (!fir.ref<f32>, !fir.dscope) -> (!fir.ref<f32>, !fir.ref<f32>)
+ %y:2 = hlfir.declare %arg1 dummy_scope %scope
+ {test.ptr = "y", uniq_name = "_QFtest_two_dummiesEy"}
+ : (!fir.ref<f32>, !fir.dscope) -> (!fir.ref<f32>, !fir.ref<f32>)
+ return
+}
+
+// -----
+
+// Distinct dummy and a local of the SAME procedure frame. The local
+// is also covered by the function's fir.dummy_scope (it is dominated
+// by the unique dummy_scope) and the local is not TARGET, so the
+// refinement returns NoAlias.
+//
+// CHECK-LABEL: Testing : "_QPtest_dummy_vs_local"
+// CHECK-DAG: x#0 <-> l#0: NoAlias
+func.func @_QPtest_dummy_vs_local(
+ %arg0: !fir.ref<f32> {fir.bindc_name = "x"}) {
+ %scope = fir.dummy_scope : !fir.dscope
+ %x:2 = hlfir.declare %arg0 dummy_scope %scope
+ {test.ptr = "x", uniq_name = "_QFtest_dummy_vs_localEx"}
+ : (!fir.ref<f32>, !fir.dscope) -> (!fir.ref<f32>, !fir.ref<f32>)
+ %alloca = fir.alloca f32 {bindc_name = "l", uniq_name = "_QFtest_dummy_vs_localEl"}
+ %l:2 = hlfir.declare %alloca
+ {test.ptr = "l", uniq_name = "_QFtest_dummy_vs_localEl"}
+ : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+ return
+}
+
+// -----
+
+// One of the dummies has TARGET attribute. A pointer or another
+// TARGET could reach it, so the refinement must NOT downgrade the
+// result to NoAlias; it stays MayAlias.
+//
+// CHECK-LABEL: Testing : "_QPtest_target_dummy"
+// CHECK-DAG: xt#0 <-> y#0: MayAlias
+func.func @_QPtest_target_dummy(
+ %arg0: !fir.ref<f32> {fir.bindc_name = "xt"},
+ %arg1: !fir.ref<f32> {fir.bindc_name = "y"}) {
+ %scope = fir.dummy_scope : !fir.dscope
+ %xt:2 = hlfir.declare %arg0 dummy_scope %scope
+ {fortran_attrs = #fir.var_attrs<target>, test.ptr = "xt",
+ uniq_name = "_QFtest_target_dummyExt"}
+ : (!fir.ref<f32>, !fir.dscope) -> (!fir.ref<f32>, !fir.ref<f32>)
+ %y:2 = hlfir.declare %arg1 dummy_scope %scope
+ {fortran_attrs = #fir.var_attrs<target>, test.ptr = "y",
+ uniq_name = "_QFtest_target_dummyEy"}
+ : (!fir.ref<f32>, !fir.dscope) -> (!fir.ref<f32>, !fir.ref<f32>)
+ return
+}
+
+// -----
+
+// Same declare on both sides: the scope-aware refinement skips the
+// pair when the two snapshots share the same declValue, so the
+// original (correct) MustAlias / MayAlias result from the underlying
+// alias() is preserved.
+//
+// CHECK-LABEL: Testing : "_QPtest_same_declare"
+// CHECK-DAG: x#0 <-> x#1: MustAlias
+func.func @_QPtest_same_declare(
+ %arg0: !fir.ref<f32> {fir.bindc_name = "x"}) {
+ %scope = fir.dummy_scope : !fir.dscope
+ %x:2 = hlfir.declare %arg0 dummy_scope %scope
+ {test.ptr = "x", uniq_name = "_QFtest_same_declareEx"}
+ : (!fir.ref<f32>, !fir.dscope) -> (!fir.ref<f32>, !fir.ref<f32>)
+ return
+}
+
+// -----
+
+// Same as test_two_dummies, but expressed with fir.declare (single
+// result) instead of hlfir.declare. This mirrors production FIR after
+// convert-hlfir-to-fir, where the scope-aware refinement must still
+// disambiguate two plain dummies sharing one fir.dummy_scope.
+//
+// CHECK-LABEL: Testing : "_QPtest_two_dummies_fir"
+// CHECK-DAG: x.fir#0 <-> y.fir#0: NoAlias
+func.func @_QPtest_two_dummies_fir(
+ %arg0: !fir.ref<f32> {fir.bindc_name = "x"},
+ %arg1: !fir.ref<f32> {fir.bindc_name = "y"}) {
+ %scope = fir.dummy_scope : !fir.dscope
+ %x = fir.declare %arg0 dummy_scope %scope
+ {test.ptr = "x.fir", uniq_name = "_QFtest_two_dummies_firEx"}
+ : (!fir.ref<f32>, !fir.dscope) -> !fir.ref<f32>
+ %y = fir.declare %arg1 dummy_scope %scope
+ {test.ptr = "y.fir", uniq_name = "_QFtest_two_dummies_firEy"}
+ : (!fir.ref<f32>, !fir.dscope) -> !fir.ref<f32>
+ return
+}
+
+// -----
+
+// Two-level inlining: the queried values are distinct dummies of the
+// INNER (inlined) procedure frame, but each is only reachable through
+// a fir.if region-branch introduced in the caller frame (a contiguity
+// copy-in for `field`, an OPTIONAL presence select for `value`). The
+// else branches yield a different SourceKind (a local temp / fir.absent),
+// so the top-level getSource walk for each value merges to
+// SourceKind::Unknown and the underlying alias() reports MayAlias
+// ("indirect access"). The ONLY way to disambiguate is via the
+// ScopedOrigin snapshots taken at the inner-frame declares (which share
+// one fir.dummy_scope). This is a regression for buildSourceAtDeclare:
+// it must classify each rebuilt Source AT the captured declare's own
+// scope (getLastInstantiationPoint=true, yielding SourceKind::Argument)
+// rather than walking past the declare back into the fir.if and
+// collapsing to Unknown.
+//
+// CHECK-LABEL: Testing : "_QPtest_nested_inline_region_branch"
+// CHECK-DAG: field#0 <-> value#0: NoAlias
+func.func @_QPtest_nested_inline_region_branch(
+ %arg0: !fir.ref<f32> {fir.bindc_name = "field"},
+ %arg1: !fir.ref<f32> {fir.bindc_name = "value", fir.optional},
+ %cond: i1) {
+ // field: contiguity copy-in select -> original actual or a local temp.
+ %ftmp = fir.alloca f32
+ %f_sel = fir.if %cond -> (!fir.ref<f32>) {
+ fir.result %arg0 : !fir.ref<f32>
+ } else {
+ fir.result %ftmp : !fir.ref<f32>
+ }
+ // value: OPTIONAL presence select -> actual or absent.
+ %present = fir.is_present %arg1 : (!fir.ref<f32>) -> i1
+ %v_sel = fir.if %present -> (!fir.ref<f32>) {
+ fir.result %arg1 : !fir.ref<f32>
+ } else {
+ %absent = fir.absent !fir.ref<f32>
+ fir.result %absent : !fir.ref<f32>
+ }
+ // Inner (inlined) frame: distinct dummies sharing one fir.dummy_scope.
+ %inner = fir.dummy_scope : !fir.dscope
+ %f_inner = fir.declare %f_sel dummy_scope %inner
+ {fortran_attrs = #fir.var_attrs<intent_inout>, test.ptr = "field",
+ uniq_name = "_QFinnerEfield"}
+ : (!fir.ref<f32>, !fir.dscope) -> !fir.ref<f32>
+ %v_inner = fir.declare %v_sel dummy_scope %inner
+ {fortran_attrs = #fir.var_attrs<intent_in>, test.ptr = "value",
+ uniq_name = "_QFinnerEvalue"}
+ : (!fir.ref<f32>, !fir.dscope) -> !fir.ref<f32>
+ return
+}
diff --git a/flang/test/Analysis/AliasAnalysis/ptr-component.fir b/flang/test/Analysis/AliasAnalysis/ptr-component.fir
index 6fbe6796c4c25..1486e4b4aec88 100644
--- a/flang/test/Analysis/AliasAnalysis/ptr-component.fir
+++ b/flang/test/Analysis/AliasAnalysis/ptr-component.fir
@@ -469,15 +469,15 @@ func.func @_QMmPtest.fir(%arg0: !fir.ref<!fir.box<!fir.ptr<i32>>> {fir.bindc_nam
// CHECK-DAG: argp.fir#0 <-> arg%p.fir#0: MayAlias
// CHECK-DAG: argp.fir#0 <-> arg%i.fir#0: NoAlias
//
-// TODO: Shouldn't these all be NoAlias?
-// However, argp.tgt is currently handled as Indirect, and we only
-// can disambiguate descriptor and data addresses.
-// CHECK-DAG: argp.tgt#0 <-> arg#0: MayAlias
+// Scope-aware refinement (ScopedOrigin) now disambiguates argp.tgt
+// against unrelated scope-mate variables: arg is a non-TARGET dummy
+// so a pointer component dereference cannot reach it.
+// CHECK-DAG: argp.tgt#0 <-> arg#0: NoAlias
// CHECK-DAG: argp.tgt#0 <-> arg%p#0: NoAlias
-// CHECK-DAG: argp.tgt#0 <-> arg%i#0: MayAlias
-// CHECK-DAG: argp.tgt.fir#0 <-> arg.fir#0: MayAlias
+// CHECK-DAG: argp.tgt#0 <-> arg%i#0: NoAlias
+// CHECK-DAG: argp.tgt.fir#0 <-> arg.fir#0: NoAlias
// CHECK-DAG: argp.tgt.fir#0 <-> arg%p.fir#0: NoAlias
-// CHECK-DAG: argp.tgt.fir#0 <-> arg%i.fir#0: MayAlias
+// CHECK-DAG: argp.tgt.fir#0 <-> arg%i.fir#0: NoAlias
//
// CHECK-DAG: arga#0 <-> arg#0: NoAlias
// CHECK-DAG: arga#0 <-> arg%p#0: NoAlias
@@ -493,15 +493,14 @@ func.func @_QMmPtest.fir(%arg0: !fir.ref<!fir.box<!fir.ptr<i32>>> {fir.bindc_nam
// CHECK-DAG: argp.fir#0 <-> glob%p.fir#0: MayAlias
// CHECK-DAG: argp.fir#0 <-> glob%i.fir#0: NoAlias
//
-// TODO: Shouldn't these all be NoAlias?
-// However, argp.tgt is currently handled as Indirect, and we only
-// can disambiguate descriptor and data addresses.
-// CHECK-DAG: argp.tgt#0 <-> glob#0: MayAlias
+// Scope-aware refinement (ScopedOrigin) now disambiguates argp.tgt
+// against the non-TARGET global glob.
+// CHECK-DAG: argp.tgt#0 <-> glob#0: NoAlias
// CHECK-DAG: argp.tgt#0 <-> glob%p#0: NoAlias
-// CHECK-DAG: argp.tgt#0 <-> glob%i#0: MayAlias
-// CHECK-DAG: argp.tgt.fir#0 <-> glob.fir#0: MayAlias
+// CHECK-DAG: argp.tgt#0 <-> glob%i#0: NoAlias
+// CHECK-DAG: argp.tgt.fir#0 <-> glob.fir#0: NoAlias
// CHECK-DAG: argp.tgt.fir#0 <-> glob%p.fir#0: NoAlias
-// CHECK-DAG: argp.tgt.fir#0 <-> glob%i.fir#0: MayAlias
+// CHECK-DAG: argp.tgt.fir#0 <-> glob%i.fir#0: NoAlias
//
// CHECK-DAG: arga#0 <-> glob#0: NoAlias
// CHECK-DAG: arga#0 <-> glob%p#0: NoAlias
@@ -517,15 +516,14 @@ func.func @_QMmPtest.fir(%arg0: !fir.ref<!fir.box<!fir.ptr<i32>>> {fir.bindc_nam
// CHECK-DAG: argp.fir#0 <-> loc%p.fir#0: NoAlias
// CHECK-DAG: argp.fir#0 <-> loc%i.fir#0: NoAlias
//
-// TODO: Shouldn't these all be NoAlias?
-// However, argp.tgt is currently handled as Indirect, and we only
-// can disambiguate descriptor and data addresses.
-// CHECK-DAG: argp.tgt#0 <-> loc#0: MayAlias
+// Scope-aware refinement (ScopedOrigin) now disambiguates argp.tgt
+// against the non-TARGET local loc.
+// CHECK-DAG: argp.tgt#0 <-> loc#0: NoAlias
// CHECK-DAG: argp.tgt#0 <-> loc%p#0: NoAlias
-// CHECK-DAG: argp.tgt#0 <-> loc%i#0: MayAlias
-// CHECK-DAG: argp.tgt.fir#0 <-> loc.fir#0: MayAlias
+// CHECK-DAG: argp.tgt#0 <-> loc%i#0: NoAlias
+// CHECK-DAG: argp.tgt.fir#0 <-> loc.fir#0: NoAlias
// CHECK-DAG: argp.tgt.fir#0 <-> loc%p.fir#0: NoAlias
-// CHECK-DAG: argp.tgt.fir#0 <-> loc%i.fir#0: MayAlias
+// CHECK-DAG: argp.tgt.fir#0 <-> loc%i.fir#0: NoAlias
//
// CHECK-DAG: arga#0 <-> loc#0: NoAlias
// CHECK-DAG: arga#0 <-> loc%p#0: NoAlias
>From 6da8c744a3d88c889f2b79b5683b72f334155d1e Mon Sep 17 00:00:00 2001
From: Slava Zakharin <szakharin at nvidia.com>
Date: Tue, 2 Jun 2026 16:57:23 -0700
Subject: [PATCH 2/2] Added tests for chained declarations belonging to the
same scope.
---
.../alias-analysis-scoped-origins.fir | 99 +++++++++++++++++++
1 file changed, 99 insertions(+)
diff --git a/flang/test/Analysis/AliasAnalysis/alias-analysis-scoped-origins.fir b/flang/test/Analysis/AliasAnalysis/alias-analysis-scoped-origins.fir
index afbb8046c4639..6291ba86caccc 100644
--- a/flang/test/Analysis/AliasAnalysis/alias-analysis-scoped-origins.fir
+++ b/flang/test/Analysis/AliasAnalysis/alias-analysis-scoped-origins.fir
@@ -170,3 +170,102 @@ func.func @_QPtest_nested_inline_region_branch(
: (!fir.ref<f32>, !fir.dscope) -> !fir.ref<f32>
return
}
+
+// -----
+
+// Chained declares in the SAME scope: an ASSOCIATE construct renames a
+// dummy argument (`associate(y => x)`). Lowering emits a plain
+// hlfir.declare for `y` (NO dummy_scope -- associate names are not
+// registered dummies) whose memref is the selector declare's result
+// %x#0. getDeclarationScope() assigns `y`'s snapshot the same dominating
+// fir.dummy_scope as `x`, so the refinement sees them as a same-scope,
+// distinct-declValue pair. They must NOT be disambiguated to NoAlias:
+// `y` is just another name for `x`. buildSourceAtDeclare walks THROUGH
+// the (dummy_scope-less) `y` declare down to the `x` declare, yielding
+// the same origin, and the base alias() already reports MustAlias via
+// the zero-offset view root, so the result stays MustAlias.
+//
+// CHECK-LABEL: Testing : "_QPtest_associate_rename_dummy"
+// CHECK-DAG: x#0 <-> y#0: MustAlias
+func.func @_QPtest_associate_rename_dummy(
+ %arg0: !fir.ref<f32> {fir.bindc_name = "x"}) {
+ %scope = fir.dummy_scope : !fir.dscope
+ %x:2 = hlfir.declare %arg0 dummy_scope %scope
+ {test.ptr = "x", uniq_name = "_QFtest_associate_rename_dummyEx"}
+ : (!fir.ref<f32>, !fir.dscope) -> (!fir.ref<f32>, !fir.ref<f32>)
+ // associate(y => x): chained declare, no dummy_scope.
+ %y:2 = hlfir.declare %x#0
+ {test.ptr = "y", uniq_name = "_QFtest_associate_rename_dummyEy"}
+ : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+ return
+}
+
+// -----
+
+// Two associate-names each renaming a DISTINCT dummy argument
+// (`associate(a => x, b => y)`). The chained `a`/`b` declares carry no
+// dummy_scope; their memrefs trace through to the distinct dummy declares
+// %x / %y. Disambiguation must still succeed: a and b are distinct
+// entities and do not alias. (Here the base alias() already resolves the
+// chains to distinct Argument origins and returns NoAlias.)
+//
+// CHECK-LABEL: Testing : "_QPtest_associate_two_dummies"
+// CHECK-DAG: a#0 <-> b#0: NoAlias
+func.func @_QPtest_associate_two_dummies(
+ %arg0: !fir.ref<f32> {fir.bindc_name = "x"},
+ %arg1: !fir.ref<f32> {fir.bindc_name = "y"}) {
+ %scope = fir.dummy_scope : !fir.dscope
+ %x:2 = hlfir.declare %arg0 dummy_scope %scope
+ {uniq_name = "_QFtest_associate_two_dummiesEx"}
+ : (!fir.ref<f32>, !fir.dscope) -> (!fir.ref<f32>, !fir.ref<f32>)
+ %y:2 = hlfir.declare %arg1 dummy_scope %scope
+ {uniq_name = "_QFtest_associate_two_dummiesEy"}
+ : (!fir.ref<f32>, !fir.dscope) -> (!fir.ref<f32>, !fir.ref<f32>)
+ // associate(a => x, b => y): chained declares, no dummy_scope.
+ %a:2 = hlfir.declare %x#0
+ {test.ptr = "a", uniq_name = "_QFtest_associate_two_dummiesEa"}
+ : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+ %b:2 = hlfir.declare %y#0
+ {test.ptr = "b", uniq_name = "_QFtest_associate_two_dummiesEb"}
+ : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+ return
+}
+
+// -----
+
+// Two associate-names renaming the SAME array dummy
+// (`associate(a => v, b => v)`), with element accesses a(i) / b(j).
+// Both chained declares (no dummy_scope) trace through to the same dummy
+// declare %v, so the array-element accesses are approximate (statically
+// unknown indices) over the SAME origin. This exercises the scope-aware
+// refinement path (base result is MayAlias, not short-circuited): the
+// pair of chained `a`/`b` declares must rebuild to the same %v origin and
+// stay MayAlias, never falsely NoAlias.
+//
+// CHECK-LABEL: Testing : "_QPtest_associate_same_array"
+// CHECK-DAG: a_elem#0 <-> b_elem#0: MayAlias
+func.func @_QPtest_associate_same_array(
+ %arg0: !fir.ref<!fir.array<?xf32>> {fir.bindc_name = "v"},
+ %n: index, %i: index, %j: index) {
+ %scope = fir.dummy_scope : !fir.dscope
+ %shape = fir.shape %n : (index) -> !fir.shape<1>
+ %v = fir.declare %arg0(%shape) dummy_scope %scope
+ {fortran_attrs = #fir.var_attrs<intent_inout>,
+ uniq_name = "_QFtest_associate_same_arrayEv"}
+ : (!fir.ref<!fir.array<?xf32>>, !fir.shape<1>, !fir.dscope)
+ -> !fir.ref<!fir.array<?xf32>>
+ // associate(a => v, b => v): chained declares, no dummy_scope.
+ %a = fir.declare %v(%shape)
+ {uniq_name = "_QFtest_associate_same_arrayEa"}
+ : (!fir.ref<!fir.array<?xf32>>, !fir.shape<1>)
+ -> !fir.ref<!fir.array<?xf32>>
+ %b = fir.declare %v(%shape)
+ {uniq_name = "_QFtest_associate_same_arrayEb"}
+ : (!fir.ref<!fir.array<?xf32>>, !fir.shape<1>)
+ -> !fir.ref<!fir.array<?xf32>>
+ %a_elem = fir.array_coor %a(%shape) %i {test.ptr = "a_elem"}
+ : (!fir.ref<!fir.array<?xf32>>, !fir.shape<1>, index) -> !fir.ref<f32>
+ %b_elem = fir.array_coor %b(%shape) %j {test.ptr = "b_elem"}
+ : (!fir.ref<!fir.array<?xf32>>, !fir.shape<1>, index) -> !fir.ref<f32>
+ return
+}
More information about the flang-commits
mailing list