[flang-commits] [flang] 201cf31 - [flang] Reduce FIR AA overhead for functions with one scope. (#204009)

via flang-commits flang-commits at lists.llvm.org
Mon Jun 22 14:53:29 PDT 2026


Author: Slava Zakharin
Date: 2026-06-22T14:53:24-07:00
New Revision: 201cf31b84acbc80bf4a6d09197bc7e9df9bb4a5

URL: https://github.com/llvm/llvm-project/commit/201cf31b84acbc80bf4a6d09197bc7e9df9bb4a5
DIFF: https://github.com/llvm/llvm-project/commit/201cf31b84acbc80bf4a6d09197bc7e9df9bb4a5.diff

LOG: [flang] Reduce FIR AA overhead for functions with one scope. (#204009)

Avoid overheads of collectScopedOrigins and
getDeclarationScope/DominanceInfo
when the values passed to FIR AA belong to a function with a single
dummy scope.

Added: 
    

Modified: 
    flang/include/flang/Optimizer/Analysis/AliasAnalysis.h
    flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
    flang/test/Analysis/AliasAnalysis/alias-analysis-host-assoc.fir
    flang/test/Analysis/AliasAnalysis/alias-analysis-scoped-origins.fir

Removed: 
    


################################################################################
diff  --git a/flang/include/flang/Optimizer/Analysis/AliasAnalysis.h b/flang/include/flang/Optimizer/Analysis/AliasAnalysis.h
index fa4a673683df4..832634a708dba 100644
--- a/flang/include/flang/Optimizer/Analysis/AliasAnalysis.h
+++ b/flang/include/flang/Optimizer/Analysis/AliasAnalysis.h
@@ -359,6 +359,16 @@ struct AliasAnalysis {
   /// POINTER object or a raw fir::PointerType.
   static bool isPointerReference(mlir::Type ty);
 
+  /// Return true if the function containing \p v has more than one
+  /// fir.dummy_scope op (e.g. the function body has been inlined into).
+  /// Scope-aware disambiguation in alias(lhs, rhs) is only meaningful in
+  /// that case; skipping it for functions with just one scope avoids the
+  /// getDeclarationScope/DominanceInfo overhead in getSource.
+  /// Both true and false results are cached in multiScopeCache so the
+  /// function walk is paid at most once per funcOp per AliasAnalysis
+  /// instance.
+  bool functionHasMultipleScopes(mlir::Value v);
+
 private:
   /// Build an intermediate Source rooted at the declare captured by the
   /// snapshot. Reuses getSource(declValue) for the SourceKind / origin
@@ -431,6 +441,11 @@ struct AliasAnalysis {
       domInfoCache;
   llvm::DenseMap<mlir::Operation *, llvm::SmallVector<mlir::Operation *, 16>>
       sortedScopeCache;
+  /// Per-function cache: true iff the function contains more than one
+  /// fir.dummy_scope op (i.e. has been inlined into). Populated by
+  /// functionHasMultipleScopes(); both true and false are cached so that
+  /// repeated queries are O(1) without re-walking the function body.
+  llvm::DenseMap<mlir::Operation *, bool> multiScopeCache;
 };
 
 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 838fcffc5fa66..a30d54841dd2e 100644
--- a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
+++ b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
@@ -599,9 +599,17 @@ static mlir::Value getZeroOffsetViewRoot(mlir::Value val) {
 AliasResult AliasAnalysis::alias(mlir::Value lhs, mlir::Value rhs) {
   // A wrapper around alias(Source lhsSrc, Source rhsSrc, mlir::Value lhs,
   // mlir::Value rhs) This allows a user to provide Source that may be obtained
-  // through other dialects
-  auto lhsSrc = getSource(lhs);
-  auto rhsSrc = getSource(rhs);
+  // through other dialects.
+  //
+  // Scope-aware refinement is only meaningful after inlining, when the
+  // function contains more than one fir.dummy_scope op. Skip
+  // collectScopedOrigins and the scope-pair loop for non-inlined functions
+  // to avoid the per-query getDeclarationScope/DominanceInfo overhead.
+  bool multiScopes = functionHasMultipleScopes(lhs);
+  auto lhsSrc =
+      getSource(lhs, /*getLastInstantiationPoint=*/false, multiScopes);
+  auto rhsSrc =
+      getSource(rhs, /*getLastInstantiationPoint=*/false, multiScopes);
   AliasResult result = alias(lhsSrc, rhsSrc, lhs, rhs);
 
   // Scope-aware refinement after inlining: if both walks crossed declares
@@ -615,7 +623,8 @@ AliasResult AliasAnalysis::alias(mlir::Value lhs, mlir::Value rhs) {
   // 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)
+  if (!multiScopes || result == AliasResult::NoAlias ||
+      result == AliasResult::MustAlias)
     return result;
   for (const auto &lhsScopedOrigin : lhsSrc.scopedOrigins) {
     if (!lhsScopedOrigin.scope)
@@ -1295,6 +1304,18 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
                   type = SourceKind::Allocate;
                   v = def;
                   defOp = nullptr;
+                } else if (boxSrc.kind == SourceKind::HostAssoc) {
+                  // Box loaded from a host-associated descriptor: classify
+                  // the dereferenced target as HostAssoc (not Indirect) so
+                  // alias() can apply the host-assoc/pointer rules instead
+                  // of coarsening to MayAlias. The access path (PointerDeref/
+                  // AllocDeref step) and Pointer attribute were already set
+                  // above, so the resulting Source matches the one that
+                  // buildSourceAtDeclare() rebuilds during scope-aware
+                  // refinement.
+                  type = SourceKind::HostAssoc;
+                  v = def;
+                  defOp = nullptr;
                 } else if (isDummyArgument(def)) {
                   defOp = nullptr;
                   v = def;
@@ -1758,4 +1779,27 @@ fir::AliasAnalysis::Source fir::AliasAnalysis::buildSourceAtDeclare(
   return source;
 }
 
+bool fir::AliasAnalysis::functionHasMultipleScopes(mlir::Value v) {
+  mlir::func::FuncOp funcOp;
+  if (mlir::Operation *defOp = v.getDefiningOp())
+    funcOp = defOp->getParentOfType<mlir::func::FuncOp>();
+  else if (auto bArg = mlir::dyn_cast<mlir::BlockArgument>(v))
+    if (mlir::Region *region = bArg.getOwner()->getParent())
+      funcOp = region->getParentOfType<mlir::func::FuncOp>();
+  if (!funcOp)
+    return true; // conservative
+  mlir::Operation *funcOpPtr = funcOp.getOperation();
+  auto it = multiScopeCache.find(funcOpPtr);
+  if (it != multiScopeCache.end())
+    return it->second;
+  // Walk counting DummyScopeOps, stop early at 2.
+  unsigned count = 0;
+  funcOp.walk([&](fir::DummyScopeOp) -> mlir::WalkResult {
+    return ++count >= 2 ? mlir::WalkResult::interrupt()
+                        : mlir::WalkResult::advance();
+  });
+  // Cache both true and false so subsequent queries are O(1).
+  return multiScopeCache.try_emplace(funcOpPtr, count >= 2).first->second;
+}
+
 } // namespace fir

diff  --git a/flang/test/Analysis/AliasAnalysis/alias-analysis-host-assoc.fir b/flang/test/Analysis/AliasAnalysis/alias-analysis-host-assoc.fir
index 7f90384ac99c5..d570d039432bd 100644
--- a/flang/test/Analysis/AliasAnalysis/alias-analysis-host-assoc.fir
+++ b/flang/test/Analysis/AliasAnalysis/alias-analysis-host-assoc.fir
@@ -184,9 +184,12 @@ func.func @_QFtest5Pinner(%arg0: !fir.ref<!fir.array<10xi32>> {fir.bindc_name =
 //   end subroutine inner
 // end subroutine test6
 
-// F18 15.5.2.13 (4):
-// FIXME: 'x' is classified as Indirect access leading to a conservative reply:
-// CHECK: test6_y(1)#0 <-> test6_x(1)#0: MayAlias
+// F18 15.5.2.13 (4): 'x' is a host-associated POINTER and 'y' is a non-TARGET
+// dummy, so 'x' cannot be associated with 'y' and they do not alias. The
+// host-associated pointer descriptor load is now classified as HostAssoc
+// rather than Indirect, so this no longer falls back to a conservative
+// MayAlias.
+// CHECK: test6_y(1)#0 <-> test6_x(1)#0: NoAlias
 func.func @_QFtest6Pinner(%arg0: !fir.ref<!fir.array<10xi32>> {fir.bindc_name = "y"}, %arg1: !fir.ref<tuple<!fir.ref<!fir.box<!fir.ptr<!fir.array<?xi32>>>>>> {fir.host_assoc}) attributes {fir.internal_proc} {
   %c0_i32 = arith.constant 0 : i32
   %0 = fir.coordinate_of %arg1, %c0_i32 : (!fir.ref<tuple<!fir.ref<!fir.box<!fir.ptr<!fir.array<?xi32>>>>>>, i32) -> !fir.llvm_ptr<!fir.ref<!fir.box<!fir.ptr<!fir.array<?xi32>>>>>

diff  --git a/flang/test/Analysis/AliasAnalysis/alias-analysis-scoped-origins.fir b/flang/test/Analysis/AliasAnalysis/alias-analysis-scoped-origins.fir
index 6291ba86caccc..228c720d0450a 100644
--- a/flang/test/Analysis/AliasAnalysis/alias-analysis-scoped-origins.fir
+++ b/flang/test/Analysis/AliasAnalysis/alias-analysis-scoped-origins.fir
@@ -131,11 +131,17 @@ func.func @_QPtest_two_dummies_fir(
 // 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.
+// one fir.dummy_scope).
+//
+// This models REAL two-level inlining: the caller is itself a procedure
+// with its own dummy arguments, so it has its own fir.dummy_scope
+// (%outer) in addition to the inlined callee's (%inner). With two
+// dummy_scope ops, functionHasMultipleScopes() is true and the
+// scope-aware refinement is enabled. buildSourceAtDeclare 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
@@ -143,17 +149,28 @@ 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.
+  // Outer (caller) frame: the caller has its own dummy arguments, hence
+  // its own fir.dummy_scope.
+  %outer = fir.dummy_scope : !fir.dscope
+  %f_outer = fir.declare %arg0 dummy_scope %outer
+      {fortran_attrs = #fir.var_attrs<intent_inout>,
+       uniq_name = "_QFcallerEfield"}
+      : (!fir.ref<f32>, !fir.dscope) -> !fir.ref<f32>
+  %v_outer = fir.declare %arg1 dummy_scope %outer
+      {fortran_attrs = #fir.var_attrs<intent_in, optional>,
+       uniq_name = "_QFcallerEvalue"}
+      : (!fir.ref<f32>, !fir.dscope) -> !fir.ref<f32>
+  // field: contiguity copy-in select -> caller actual or a local temp.
   %ftmp = fir.alloca f32
   %f_sel = fir.if %cond -> (!fir.ref<f32>) {
-    fir.result %arg0 : !fir.ref<f32>
+    fir.result %f_outer : !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
+  // value: OPTIONAL presence select -> caller actual or absent.
+  %present = fir.is_present %v_outer : (!fir.ref<f32>) -> i1
   %v_sel = fir.if %present -> (!fir.ref<f32>) {
-    fir.result %arg1 : !fir.ref<f32>
+    fir.result %v_outer : !fir.ref<f32>
   } else {
     %absent = fir.absent !fir.ref<f32>
     fir.result %absent : !fir.ref<f32>


        


More information about the flang-commits mailing list