[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:53:32 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/5] 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 5d320c8634b5c..f068d91b1bfcd 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 5d60b373b0819..3d4decaa3cbae 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/5] 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 0000000000000..673b2599c5d60
--- /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/5] 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 f068d91b1bfcd..61864b24f8883 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/5] 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 3d4decaa3cbae..9e51e9d04e35d 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/5] 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 9e51e9d04e35d..1caa5e2670bb9 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)
More information about the flang-commits
mailing list