[flang-commits] [flang] [flang][aa] Use Fortran dummy intent in FIR call modref (PR #227432)

Susan Tan ス-ザン タン via flang-commits flang-commits at lists.llvm.org
Tue Sep 29 11:55:10 PDT 2026


https://github.com/SusanTan updated https://github.com/llvm/llvm-project/pull/227432

>From 337f4756f3bd12d063d522fc8cfb0a76ea41ddc1 Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Tue, 29 Sep 2026 11:41:57 -0700
Subject: [PATCH 1/6] impl

---
 .../flang/Optimizer/Dialect/FIROpsSupport.h   | 36 +++++++++++++++
 .../lib/Optimizer/Analysis/AliasAnalysis.cpp  | 45 +++++++++++++++----
 2 files changed, 73 insertions(+), 8 deletions(-)

diff --git a/flang/include/flang/Optimizer/Dialect/FIROpsSupport.h b/flang/include/flang/Optimizer/Dialect/FIROpsSupport.h
index 5d320c8634b5ca..f068d91b1bfcdc 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROpsSupport.h
+++ b/flang/include/flang/Optimizer/Dialect/FIROpsSupport.h
@@ -240,6 +240,42 @@ inline mlir::NamedAttribute getAdaptToByRefAttr(Builder &builder) {
 
 bool isDummyArgument(mlir::Value v);
 
+/// Intent of dummy argument `argIdx` when `callee`'s body declares it.
+/// Empty when the body is missing or the dummy has no intent attribute.
+enum class FortranDummyIntent { In, Out, InOut };
+
+inline std::optional<FortranDummyIntent>
+getFortranDummyIntent(mlir::func::FuncOp callee, unsigned argIdx) {
+  if (!callee || argIdx >= callee.getNumArguments())
+    return std::nullopt;
+  mlir::BlockArgument arg = callee.getArgument(argIdx);
+  std::optional<FortranVariableFlagsEnum> flags;
+  callee.walk([&](fir::DeclareOp decl) {
+    if (decl.getMemref() != arg)
+      return mlir::WalkResult::advance();
+    if (auto attrs = decl.getFortranAttrs()) {
+      flags = *attrs;
+      return mlir::WalkResult::interrupt();
+    }
+    return mlir::WalkResult::advance();
+  });
+  if (!flags)
+    return std::nullopt;
+  const bool isIn =
+      bitEnumContainsAny(*flags, FortranVariableFlagsEnum::intent_in);
+  const bool isInOut =
+      bitEnumContainsAny(*flags, FortranVariableFlagsEnum::intent_inout);
+  const bool isOut =
+      bitEnumContainsAny(*flags, FortranVariableFlagsEnum::intent_out);
+  if (isInOut || (isIn && isOut))
+    return FortranDummyIntent::InOut;
+  if (isOut)
+    return FortranDummyIntent::Out;
+  if (isIn)
+    return FortranDummyIntent::In;
+  return std::nullopt;
+}
+
 template <fir::FortranProcedureFlagsEnum Flag>
 inline bool hasProcedureAttr(fir::FortranProcedureFlagsEnumAttr flags) {
   return flags && bitEnumContainsAny(flags.getValue(), Flag);
diff --git a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
index 5d60b373b0819c..3d4decaa3cbae6 100644
--- a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
+++ b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
@@ -24,6 +24,7 @@
 #include "mlir/IR/Value.h"
 #include "mlir/Interfaces/ControlFlowInterfaces.h"
 #include "mlir/Interfaces/SideEffectInterfaces.h"
+#include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/TypeSwitch.h"
 #include "llvm/Support/Casting.h"
 #include "llvm/Support/CommandLine.h"
@@ -1081,16 +1082,44 @@ ModRefResult AliasAnalysis::getCallModRef(Operation *op, Value var) {
         !isSavedLocal(varSrc))
       return ModRefResult::getModAndRef();
   }
-  // 2. Check if the variable is passed via the arguments.
-  for (auto arg : call.getArgs()) {
-    if (fir::conformsWithPassByRef(arg.getType()) && !alias(arg, var).isNo()) {
-      // TODO: intent(in) would allow returning Ref here. This can be obtained
-      // in the func.func attributes for direct calls, but the module lookup is
-      // linear with the number of MLIR symbols, which would introduce a pseudo
-      // quadratic behavior num_calls * num_func.
-      return ModRefResult::getModAndRef();
+  // 2. Check if the variable is passed via the arguments. A dummy with a
+  // declared intent is a read, a write, or both. An argument with no visible
+  // intent stays ModAndRef. The callee is resolved through the cached symbol
+  // table.
+  mlir::func::FuncOp callee;
+  if (std::optional<mlir::SymbolRefAttr> calleeAttr = call.getCallee()) {
+    if (const mlir::SymbolTable *symTab = getNearestSymbolTable(call))
+      callee =
+          symTab->lookup<mlir::func::FuncOp>(calleeAttr->getLeafReference());
+  }
+  auto args = call.getArgs();
+  const bool intentsAvailable = callee && !callee.isDeclaration() &&
+                                args.size() == callee.getNumArguments();
+  std::optional<ModRefResult> passed;
+  for (auto [idx, arg] : llvm::enumerate(args)) {
+    if (!fir::conformsWithPassByRef(arg.getType()) || alias(arg, var).isNo())
+      continue;
+    ModRefResult one = ModRefResult::getModAndRef();
+    if (intentsAvailable) {
+      if (std::optional<fir::FortranDummyIntent> intent =
+              fir::getFortranDummyIntent(callee, idx)) {
+        switch (*intent) {
+        case fir::FortranDummyIntent::In:
+          one = ModRefResult::getRef();
+          break;
+        case fir::FortranDummyIntent::Out:
+          one = ModRefResult::getMod();
+          break;
+        case fir::FortranDummyIntent::InOut:
+          one = ModRefResult::getModAndRef();
+          break;
+        }
+      }
     }
+    passed = passed ? passed->merge(one) : one;
   }
+  if (passed)
+    return *passed;
   // The call cannot access the variable.
   return ModRefResult::getNoModRef();
 }

>From af41cc9d9763e6ec311afc6afd597c2fe90fb172 Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Tue, 29 Sep 2026 11:42:18 -0700
Subject: [PATCH 2/6] test

---
 .../AliasAnalysis/modref-call-intent.fir      | 70 +++++++++++++++++++
 1 file changed, 70 insertions(+)
 create mode 100644 flang/test/Analysis/AliasAnalysis/modref-call-intent.fir

diff --git a/flang/test/Analysis/AliasAnalysis/modref-call-intent.fir b/flang/test/Analysis/AliasAnalysis/modref-call-intent.fir
new file mode 100644
index 00000000000000..673b2599c5d602
--- /dev/null
+++ b/flang/test/Analysis/AliasAnalysis/modref-call-intent.fir
@@ -0,0 +1,70 @@
+// RUN:  fir-opt -pass-pipeline='builtin.module(func.func(test-fir-alias-analysis-modref))' \
+// RUN:  --mlir-disable-threading %s -o /dev/null 2>&1 | FileCheck %s
+
+// A direct call uses the callee dummy's Fortran intent when the argument
+// aliases the queried variable:
+//   intent(in)    -> Ref
+//   intent(out)   -> Mod
+//   intent(inout) -> ModRef
+// A dummy with no intent stays ModRef. A local that is not passed is NoModRef.
+
+// CHECK-LABEL: Testing : "test_call_intent"
+// CHECK-DAG: in_call -> in#0: Ref{{$}}
+// CHECK-DAG: in_call -> out#0: NoModRef{{$}}
+// CHECK-DAG: in_call -> other#0: NoModRef{{$}}
+// CHECK-DAG: out_call -> out#0: Mod{{$}}
+// CHECK-DAG: out_call -> in#0: NoModRef{{$}}
+// CHECK-DAG: inout_call -> inout#0: ModRef{{$}}
+// CHECK-DAG: inout_call -> other#0: NoModRef{{$}}
+// CHECK-DAG: unknown_call -> unknown#0: ModRef{{$}}
+// CHECK-DAG: unknown_call -> other#0: NoModRef{{$}}
+// CHECK-DAG: mixed_call -> in#0: Ref{{$}}
+// CHECK-DAG: mixed_call -> inout#0: ModRef{{$}}
+// CHECK-DAG: mixed_call -> out#0: Mod{{$}}
+// CHECK-DAG: mixed_call -> other#0: NoModRef{{$}}
+func.func @test_call_intent() {
+  %in_alloca = fir.alloca f32 {bindc_name = "in", uniq_name = "_QFtest_call_intentEin"}
+  %in = fir.declare %in_alloca {test.ptr = "in", uniq_name = "_QFtest_call_intentEin"} : (!fir.ref<f32>) -> !fir.ref<f32>
+  %out_alloca = fir.alloca f32 {bindc_name = "out", uniq_name = "_QFtest_call_intentEout"}
+  %out = fir.declare %out_alloca {test.ptr = "out", uniq_name = "_QFtest_call_intentEout"} : (!fir.ref<f32>) -> !fir.ref<f32>
+  %inout_alloca = fir.alloca f32 {bindc_name = "inout", uniq_name = "_QFtest_call_intentEinout"}
+  %inout = fir.declare %inout_alloca {test.ptr = "inout", uniq_name = "_QFtest_call_intentEinout"} : (!fir.ref<f32>) -> !fir.ref<f32>
+  %unknown_alloca = fir.alloca f32 {bindc_name = "unknown", uniq_name = "_QFtest_call_intentEunknown"}
+  %unknown = fir.declare %unknown_alloca {test.ptr = "unknown", uniq_name = "_QFtest_call_intentEunknown"} : (!fir.ref<f32>) -> !fir.ref<f32>
+  %other_alloca = fir.alloca f32 {bindc_name = "other", uniq_name = "_QFtest_call_intentEother"}
+  %other = fir.declare %other_alloca {test.ptr = "other", uniq_name = "_QFtest_call_intentEother"} : (!fir.ref<f32>) -> !fir.ref<f32>
+
+  fir.call @_QPread_in(%in) {test.ptr = "in_call"} : (!fir.ref<f32>) -> ()
+  fir.call @_QPfill_out(%out) {test.ptr = "out_call"} : (!fir.ref<f32>) -> ()
+  fir.call @_QPupdate_inout(%inout) {test.ptr = "inout_call"} : (!fir.ref<f32>) -> ()
+  fir.call @_QPno_intent(%unknown) {test.ptr = "unknown_call"} : (!fir.ref<f32>) -> ()
+  fir.call @_QPscratch(%in, %inout, %out) {test.ptr = "mixed_call"} : (!fir.ref<f32>, !fir.ref<f32>, !fir.ref<f32>) -> ()
+  return
+}
+
+func.func private @_QPread_in(%arg0: !fir.ref<f32>) {
+  %x = fir.declare %arg0 {fortran_attrs = #fir.var_attrs<intent_in>, uniq_name = "_QFread_inEx"} : (!fir.ref<f32>) -> !fir.ref<f32>
+  return
+}
+
+func.func private @_QPfill_out(%arg0: !fir.ref<f32>) {
+  %x = fir.declare %arg0 {fortran_attrs = #fir.var_attrs<intent_out>, uniq_name = "_QFfill_outEx"} : (!fir.ref<f32>) -> !fir.ref<f32>
+  return
+}
+
+func.func private @_QPupdate_inout(%arg0: !fir.ref<f32>) {
+  %x = fir.declare %arg0 {fortran_attrs = #fir.var_attrs<intent_inout>, uniq_name = "_QFupdate_inoutEx"} : (!fir.ref<f32>) -> !fir.ref<f32>
+  return
+}
+
+func.func private @_QPno_intent(%arg0: !fir.ref<f32>) {
+  %x = fir.declare %arg0 {uniq_name = "_QFno_intentEx"} : (!fir.ref<f32>) -> !fir.ref<f32>
+  return
+}
+
+func.func private @_QPscratch(%arg0: !fir.ref<f32>, %arg1: !fir.ref<f32>, %arg2: !fir.ref<f32>) {
+  %x = fir.declare %arg0 {fortran_attrs = #fir.var_attrs<intent_in>, uniq_name = "_QFscratchEx"} : (!fir.ref<f32>) -> !fir.ref<f32>
+  %a = fir.declare %arg1 {fortran_attrs = #fir.var_attrs<intent_inout>, uniq_name = "_QFscratchEa"} : (!fir.ref<f32>) -> !fir.ref<f32>
+  %res = fir.declare %arg2 {fortran_attrs = #fir.var_attrs<intent_out>, uniq_name = "_QFscratchEres"} : (!fir.ref<f32>) -> !fir.ref<f32>
+  return
+}

>From bc8519f340ba669e585f17e1eb46175224dc3928 Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Tue, 29 Sep 2026 11:45:34 -0700
Subject: [PATCH 3/6] consolidate

---
 .../flang/Optimizer/Dialect/FIROpsSupport.h   | 43 ++++++++-----------
 1 file changed, 19 insertions(+), 24 deletions(-)

diff --git a/flang/include/flang/Optimizer/Dialect/FIROpsSupport.h b/flang/include/flang/Optimizer/Dialect/FIROpsSupport.h
index f068d91b1bfcdc..61864b24f8883a 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROpsSupport.h
+++ b/flang/include/flang/Optimizer/Dialect/FIROpsSupport.h
@@ -248,31 +248,26 @@ inline std::optional<FortranDummyIntent>
 getFortranDummyIntent(mlir::func::FuncOp callee, unsigned argIdx) {
   if (!callee || argIdx >= callee.getNumArguments())
     return std::nullopt;
-  mlir::BlockArgument arg = callee.getArgument(argIdx);
-  std::optional<FortranVariableFlagsEnum> flags;
-  callee.walk([&](fir::DeclareOp decl) {
-    if (decl.getMemref() != arg)
-      return mlir::WalkResult::advance();
-    if (auto attrs = decl.getFortranAttrs()) {
-      flags = *attrs;
-      return mlir::WalkResult::interrupt();
-    }
-    return mlir::WalkResult::advance();
-  });
-  if (!flags)
+  // The dummy's fir.declare uses the block argument as its memref.
+  mlir::Value arg = callee.getArgument(argIdx);
+  for (mlir::Operation *user : arg.getUsers()) {
+    auto decl = mlir::dyn_cast<fir::DeclareOp>(user);
+    if (!decl || decl.getMemref() != arg)
+      continue;
+    auto attrs = decl.getFortranAttrs();
+    if (!attrs)
+      continue;
+    using F = FortranVariableFlagsEnum;
+    if (bitEnumContainsAny(*attrs, F::intent_inout) ||
+        (bitEnumContainsAny(*attrs, F::intent_in) &&
+         bitEnumContainsAny(*attrs, F::intent_out)))
+      return FortranDummyIntent::InOut;
+    if (bitEnumContainsAny(*attrs, F::intent_out))
+      return FortranDummyIntent::Out;
+    if (bitEnumContainsAny(*attrs, F::intent_in))
+      return FortranDummyIntent::In;
     return std::nullopt;
-  const bool isIn =
-      bitEnumContainsAny(*flags, FortranVariableFlagsEnum::intent_in);
-  const bool isInOut =
-      bitEnumContainsAny(*flags, FortranVariableFlagsEnum::intent_inout);
-  const bool isOut =
-      bitEnumContainsAny(*flags, FortranVariableFlagsEnum::intent_out);
-  if (isInOut || (isIn && isOut))
-    return FortranDummyIntent::InOut;
-  if (isOut)
-    return FortranDummyIntent::Out;
-  if (isIn)
-    return FortranDummyIntent::In;
+  }
   return std::nullopt;
 }
 

>From 643ee214b0b0be4a3ae67fa046bbc54dc6fb5a48 Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Tue, 29 Sep 2026 11:49:07 -0700
Subject: [PATCH 4/6] tweak

---
 .../lib/Optimizer/Analysis/AliasAnalysis.cpp  | 28 ++++++++-----------
 1 file changed, 11 insertions(+), 17 deletions(-)

diff --git a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
index 3d4decaa3cbae6..9e51e9d04e35de 100644
--- a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
+++ b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
@@ -1095,27 +1095,21 @@ ModRefResult AliasAnalysis::getCallModRef(Operation *op, Value var) {
   auto args = call.getArgs();
   const bool intentsAvailable = callee && !callee.isDeclaration() &&
                                 args.size() == callee.getNumArguments();
+  // Several aliasing arguments are combined. A missing intent or intent(inout)
+  // is already both, so only a pure read is merged with a pure write.
   std::optional<ModRefResult> passed;
   for (auto [idx, arg] : llvm::enumerate(args)) {
     if (!fir::conformsWithPassByRef(arg.getType()) || alias(arg, var).isNo())
       continue;
-    ModRefResult one = ModRefResult::getModAndRef();
-    if (intentsAvailable) {
-      if (std::optional<fir::FortranDummyIntent> intent =
-              fir::getFortranDummyIntent(callee, idx)) {
-        switch (*intent) {
-        case fir::FortranDummyIntent::In:
-          one = ModRefResult::getRef();
-          break;
-        case fir::FortranDummyIntent::Out:
-          one = ModRefResult::getMod();
-          break;
-        case fir::FortranDummyIntent::InOut:
-          one = ModRefResult::getModAndRef();
-          break;
-        }
-      }
-    }
+    if (!intentsAvailable)
+      return ModRefResult::getModAndRef();
+    std::optional<fir::FortranDummyIntent> intent =
+        fir::getFortranDummyIntent(callee, idx);
+    if (!intent || *intent == fir::FortranDummyIntent::InOut)
+      return ModRefResult::getModAndRef();
+    ModRefResult one = *intent == fir::FortranDummyIntent::In
+                           ? ModRefResult::getRef()
+                           : ModRefResult::getMod();
     passed = passed ? passed->merge(one) : one;
   }
   if (passed)

>From b2672f5dd8d2cd7032502ec4ab3e2fe4225a07b5 Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Tue, 29 Sep 2026 11:53:13 -0700
Subject: [PATCH 5/6] tweak

---
 flang/lib/Optimizer/Analysis/AliasAnalysis.cpp | 18 +++++++-----------
 1 file changed, 7 insertions(+), 11 deletions(-)

diff --git a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
index 9e51e9d04e35de..1caa5e2670bb91 100644
--- a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
+++ b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
@@ -1095,9 +1095,9 @@ ModRefResult AliasAnalysis::getCallModRef(Operation *op, Value var) {
   auto args = call.getArgs();
   const bool intentsAvailable = callee && !callee.isDeclaration() &&
                                 args.size() == callee.getNumArguments();
-  // Several aliasing arguments are combined. A missing intent or intent(inout)
-  // is already both, so only a pure read is merged with a pure write.
-  std::optional<ModRefResult> passed;
+  // NoModRef is the merge identity, so an argument that does not alias leaves
+  // this unchanged. A missing intent or intent(inout) is already both.
+  ModRefResult passed = ModRefResult::getNoModRef();
   for (auto [idx, arg] : llvm::enumerate(args)) {
     if (!fir::conformsWithPassByRef(arg.getType()) || alias(arg, var).isNo())
       continue;
@@ -1107,15 +1107,11 @@ ModRefResult AliasAnalysis::getCallModRef(Operation *op, Value var) {
         fir::getFortranDummyIntent(callee, idx);
     if (!intent || *intent == fir::FortranDummyIntent::InOut)
       return ModRefResult::getModAndRef();
-    ModRefResult one = *intent == fir::FortranDummyIntent::In
-                           ? ModRefResult::getRef()
-                           : ModRefResult::getMod();
-    passed = passed ? passed->merge(one) : one;
+    passed = passed.merge(*intent == fir::FortranDummyIntent::In
+                               ? ModRefResult::getRef()
+                               : ModRefResult::getMod());
   }
-  if (passed)
-    return *passed;
-  // The call cannot access the variable.
-  return ModRefResult::getNoModRef();
+  return passed;
 }
 
 AliasAnalysis::AliasAnalysis(AliasAnalysisRecursiveEffectsCache &cacheRef)

>From 0d903973bbdaa6adc960719939e0f4fbc6408cc5 Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Tue, 29 Sep 2026 11:54:50 -0700
Subject: [PATCH 6/6] tweak

---
 flang/lib/Optimizer/Analysis/AliasAnalysis.cpp | 12 +++++-------
 1 file changed, 5 insertions(+), 7 deletions(-)

diff --git a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
index 1caa5e2670bb91..903dc3e397d43a 100644
--- a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
+++ b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
@@ -1095,9 +1095,7 @@ ModRefResult AliasAnalysis::getCallModRef(Operation *op, Value var) {
   auto args = call.getArgs();
   const bool intentsAvailable = callee && !callee.isDeclaration() &&
                                 args.size() == callee.getNumArguments();
-  // NoModRef is the merge identity, so an argument that does not alias leaves
-  // this unchanged. A missing intent or intent(inout) is already both.
-  ModRefResult passed = ModRefResult::getNoModRef();
+  ModRefResult modRef = ModRefResult::getNoModRef();
   for (auto [idx, arg] : llvm::enumerate(args)) {
     if (!fir::conformsWithPassByRef(arg.getType()) || alias(arg, var).isNo())
       continue;
@@ -1107,11 +1105,11 @@ ModRefResult AliasAnalysis::getCallModRef(Operation *op, Value var) {
         fir::getFortranDummyIntent(callee, idx);
     if (!intent || *intent == fir::FortranDummyIntent::InOut)
       return ModRefResult::getModAndRef();
-    passed = passed.merge(*intent == fir::FortranDummyIntent::In
-                               ? ModRefResult::getRef()
-                               : ModRefResult::getMod());
+    modRef = modRef.merge(*intent == fir::FortranDummyIntent::In
+                              ? ModRefResult::getRef()
+                              : ModRefResult::getMod());
   }
-  return passed;
+  return modRef;
 }
 
 AliasAnalysis::AliasAnalysis(AliasAnalysisRecursiveEffectsCache &cacheRef)



More information about the flang-commits mailing list