[flang-commits] [flang] [flang] Add initial support for RegionBranchOpInterface to AA (PR #196132)
Razvan Lupusoru via flang-commits
flang-commits at lists.llvm.org
Thu May 7 07:14:38 PDT 2026
https://github.com/razvanlupusoru updated https://github.com/llvm/llvm-project/pull/196132
>From 09c4f0e0c281f1771c572c1c10f92e95da01c634 Mon Sep 17 00:00:00 2001
From: Razvan Lupusoru <rlupusoru at nvidia.com>
Date: Wed, 6 May 2026 10:36:54 -0700
Subject: [PATCH 1/4] [flang] Add initial support for RegionBranchOpInterface
to AA
This PR introduces initial support for being able to disambiguate
values through operations that implement RegionBranchOpInterface (eg
fir.if). The initial approach implements a localized approach which
instead of an invasive update to the current algorithm so that a
single underlying origin to be replaced with multiple, it still
leaves the single origin concept. It does so through a conservative
merge of all input sources.
The main initial goal is to allow disambiguating OPTIONAL pattern where
we have an allocate on one branch and an absent on the other.
Additionally, it is able to properly merge sources if the sources have
same properties.
---
.../lib/Optimizer/Analysis/AliasAnalysis.cpp | 183 ++++++++++++
.../alias-analysis-regionbranch.mlir | 262 ++++++++++++++++++
2 files changed, 445 insertions(+)
create mode 100644 flang/test/Analysis/AliasAnalysis/alias-analysis-regionbranch.mlir
diff --git a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
index 2d19e07d9d7ce..98478a49477a9 100644
--- a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
+++ b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
@@ -22,6 +22,7 @@
#include "mlir/Dialect/OpenMP/OpenMPInterfaces.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/Value.h"
+#include "mlir/Interfaces/ControlFlowInterfaces.h"
#include "mlir/Interfaces/SideEffectInterfaces.h"
#include "llvm/ADT/TypeSwitch.h"
#include "llvm/Support/Casting.h"
@@ -141,6 +142,156 @@ static fir::AliasAnalysis::Source getSourceForACCMappedValue(
return source;
}
+/// Predecessor SSA values that may define a result of \p branch when control
+/// continues in the parent region (same mapping as
+/// `LocalAliasAnalysis::collectUnderlyingAddressValues2` for
+/// `RegionSuccessor::parent()`).
+static void getRegionBranchPredecessorValuesForParentResult(
+ mlir::RegionBranchOpInterface branch, mlir::OpResult result,
+ llvm::SmallVectorImpl<mlir::Value> &out) {
+ mlir::RegionSuccessor parentSucc = mlir::RegionSuccessor::parent();
+ mlir::Value inputValue = result;
+ unsigned inputIndex = result.getResultNumber();
+ mlir::ValueRange inputs = branch.getSuccessorInputs(parentSucc);
+ if (inputs.empty()) {
+ out.push_back(inputValue);
+ return;
+ }
+ unsigned firstInputIndex, lastInputIndex;
+ if (mlir::isa<mlir::BlockArgument>(inputs[0])) {
+ firstInputIndex = mlir::cast<mlir::BlockArgument>(inputs[0]).getArgNumber();
+ lastInputIndex =
+ mlir::cast<mlir::BlockArgument>(inputs.back()).getArgNumber();
+ } else {
+ firstInputIndex = mlir::cast<mlir::OpResult>(inputs[0]).getResultNumber();
+ lastInputIndex =
+ mlir::cast<mlir::OpResult>(inputs.back()).getResultNumber();
+ }
+ if (firstInputIndex > inputIndex || lastInputIndex < inputIndex) {
+ out.push_back(inputValue);
+ return;
+ }
+ branch.getPredecessorValues(parentSucc, inputIndex - firstInputIndex, out);
+}
+
+/// True when \p src's tracked origin value is an SSA result of an operation
+/// nested under \p branch's regions.
+static bool originIsInsideRegionBranch(mlir::RegionBranchOpInterface branch,
+ const fir::AliasAnalysis::Source &src) {
+ const fir::AliasAnalysis::Source::SourceOrigin &origin = src.origin;
+ if (llvm::isa<mlir::SymbolRefAttr>(origin.u))
+ return false;
+ mlir::Value originVal = llvm::cast<mlir::Value>(origin.u);
+ if (mlir::isa<mlir::BlockArgument>(originVal))
+ return false;
+ mlir::Operation *defOp = originVal.getDefiningOp();
+ if (!defOp)
+ return false;
+ return branch.getOperation()->isProperAncestor(defOp);
+}
+
+/// Conservative join of memory sources from region-branch predecessors.
+static fir::AliasAnalysis::Source mergeRegionBranchPredecessorSources(
+ llvm::ArrayRef<fir::AliasAnalysis::Source> sources,
+ mlir::Value fallbackValue, mlir::Type fallbackType, bool followingData) {
+ assert(!sources.empty() && "expected at least one predecessor source");
+
+ // For kind, origin, attributes, isApproximate/accessPath, valueType, we
+ // capture if all of the sources have exactly the same value.
+ bool allKindsSame =
+ llvm::all_of(sources, [&](const fir::AliasAnalysis::Source &s) {
+ return s.kind == sources[0].kind;
+ });
+ bool allOriginsSame =
+ llvm::all_of(sources, [&](const fir::AliasAnalysis::Source &s) {
+ return s.origin == sources[0].origin;
+ });
+ bool allAttrsSame =
+ llvm::all_of(sources, [&](const fir::AliasAnalysis::Source &s) {
+ return s.attributes == sources[0].attributes;
+ });
+ bool allPathsSame =
+ llvm::all_of(sources, [&](const fir::AliasAnalysis::Source &s) {
+ return s.accessPath.isApproximate ==
+ sources[0].accessPath.isApproximate &&
+ s.accessPath.steps == sources[0].accessPath.steps;
+ });
+ bool allTypesSame =
+ llvm::all_of(sources, [&](const fir::AliasAnalysis::Source &s) {
+ return s.valueType == sources[0].valueType;
+ });
+
+ // For approximateSource and isCapturedInInternalProcedure, we mark them
+ // as true if any of the sources are true.
+ bool mergedApprox =
+ llvm::any_of(sources, [](const fir::AliasAnalysis::Source &s) {
+ return s.approximateSource;
+ });
+ bool mergedCaptured =
+ llvm::any_of(sources, [](const fir::AliasAnalysis::Source &s) {
+ return s.isCapturedInInternalProcedure;
+ });
+
+ fir::AliasAnalysis::SourceKind mergedKind;
+ fir::AliasAnalysis::Source::Attributes mergedAttrs;
+ if (!allKindsSame) {
+ mergedKind = fir::AliasAnalysis::SourceKind::Unknown;
+ mergedAttrs = {};
+ } else if (!allAttrsSame) {
+ mergedKind = fir::AliasAnalysis::SourceKind::Unknown;
+ mergedAttrs = {};
+ } else if (!allOriginsSame) {
+ // Same kind and attributes on every path, but different concrete origins.
+ // Since origins are different, for most cases fall back to Indirect here.
+ // However, for Allocate, we want to keep the information about this being
+ // an Allocate as long as all are defined inside the region's branches
+ // because then they are all unique and thus cannot alias anything outside
+ // the region (this is key here - because this only holds when comparing
+ // region's result only with outside values not the origins themselves).
+ // TODO: An origin list would be better to preserve this information
+ // more accurately instead of a single origin.
+ auto branchOp = mlir::dyn_cast<mlir::RegionBranchOpInterface>(
+ mlir::cast<mlir::OpResult>(fallbackValue).getOwner());
+ assert(branchOp && "merge region-branch sources expects branch op result");
+ unsigned originsOutsideBranch =
+ llvm::count_if(sources, [&](const fir::AliasAnalysis::Source &s) {
+ return !originIsInsideRegionBranch(branchOp, s);
+ });
+ bool keepAllocate =
+ sources[0].kind == fir::AliasAnalysis::SourceKind::Allocate &&
+ originsOutsideBranch == 0u;
+ mergedKind = keepAllocate ? fir::AliasAnalysis::SourceKind::Allocate
+ : fir::AliasAnalysis::SourceKind::Indirect;
+ mergedAttrs = sources[0].attributes;
+ } else {
+ mergedKind = sources[0].kind;
+ mergedAttrs = sources[0].attributes;
+ }
+
+ fir::AliasAnalysis::Source::SourceOrigin mergedOrigin;
+ if (allOriginsSame) {
+ mergedOrigin = sources[0].origin;
+ } else {
+ // Set the origin as the fallbackValue provided - which should be the
+ // region-branch result.
+ mergedOrigin = {fallbackValue, nullptr, followingData};
+ }
+
+ fir::AliasAnalysis::Source::AccessPath mergedPath;
+ if (allPathsSame) {
+ mergedPath = sources[0].accessPath;
+ mergedPath.isApproximate |= mergedApprox;
+ } else {
+ mergedPath = {};
+ mergedPath.isApproximate = true;
+ }
+
+ mlir::Type mergedTy = allTypesSame ? sources[0].valueType : fallbackType;
+
+ return {mergedOrigin, mergedKind, mergedTy, mergedAttrs,
+ mergedApprox, mergedPath, mergedCaptured};
+}
+
namespace fir {
void AliasAnalysis::Source::AccessPath::print(llvm::raw_ostream &os) const {
@@ -861,6 +1012,7 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
}
ty = opResult.getType();
std::optional<AliasAnalysis::Source> accSourceReturn;
+ std::optional<AliasAnalysis::Source> regionBranchReturn;
llvm::TypeSwitch<Operation *>(defOp)
.Case([&](hlfir::AsExprOp op) {
// TODO: we should probably always report hlfir.as_expr
@@ -1225,10 +1377,41 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
followingData, attributes);
breakFromLoop = true;
})
+ .Case([&](mlir::RegionBranchOpInterface branch) {
+ llvm::SmallVector<mlir::Value, 4> predecessors;
+ getRegionBranchPredecessorValuesForParentResult(branch, opResult,
+ predecessors);
+ if (predecessors.empty() ||
+ llvm::all_of(predecessors,
+ [&](mlir::Value pred) { return pred == v; })) {
+ regionBranchReturn = {{{v, instantiationPoint, followingData},
+ SourceKind::Unknown,
+ ty,
+ attributes,
+ /*approximateSource=*/true,
+ /*accessPath=*/{},
+ isCapturedInInternalProcedure}};
+ breakFromLoop = true;
+ return;
+ }
+ llvm::SmallVector<AliasAnalysis::Source, 4> predSources;
+ predSources.reserve(predecessors.size());
+ for (mlir::Value pred : predecessors)
+ predSources.push_back(getSource(pred, getLastInstantiationPoint));
+ regionBranchReturn = mergeRegionBranchPredecessorSources(
+ predSources, v, ty, followingData);
+ regionBranchReturn->attributes |= attributes;
+ regionBranchReturn->approximateSource |= approximateSource;
+ regionBranchReturn->isCapturedInInternalProcedure |=
+ isCapturedInInternalProcedure;
+ breakFromLoop = true;
+ })
.Default([&](auto op) {
defOp = nullptr;
breakFromLoop = true;
});
+ if (regionBranchReturn)
+ return *regionBranchReturn;
if (accSourceReturn)
return *accSourceReturn;
}
diff --git a/flang/test/Analysis/AliasAnalysis/alias-analysis-regionbranch.mlir b/flang/test/Analysis/AliasAnalysis/alias-analysis-regionbranch.mlir
new file mode 100644
index 0000000000000..7b0ed3aee84f4
--- /dev/null
+++ b/flang/test/Analysis/AliasAnalysis/alias-analysis-regionbranch.mlir
@@ -0,0 +1,262 @@
+// RUN: fir-opt %s -pass-pipeline='builtin.module(func.func(test-fir-alias-analysis))' \
+// RUN: -split-input-file --mlir-disable-threading 2>&1 | FileCheck %s
+
+// -----
+
+// Distinct fir.alloca in each branch; unrelated fir.alloca outside the if.
+// CHECK-LABEL: Testing : "test_rb_both_alloc_distinct"
+// CHECK-DAG: outside_alloc#0 <-> join_alloc#0: NoAlias
+
+func.func @test_rb_both_alloc_distinct() {
+ %cond = arith.constant true
+ %a_out = fir.alloca f32 {uniq_name = "_QFEa_out"}
+ %d_out = fir.declare %a_out {uniq_name = "_QFEa_out", test.ptr = "outside_alloc"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ %jf = fir.if %cond -> !fir.ref<f32> {
+ %a1 = fir.alloca f32 {uniq_name = "_QFEa1"}
+ %d1 = fir.declare %a1 {uniq_name = "_QFEa1"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ fir.result %d1 : !fir.ref<f32>
+ } else {
+ %a2 = fir.alloca f32 {uniq_name = "_QFEa2"}
+ %d2 = fir.declare %a2 {uniq_name = "_QFEa2"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ fir.result %d2 : !fir.ref<f32>
+ }
+ %join = fir.convert %jf {test.ptr = "join_alloc"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ return
+}
+
+// -----
+
+// Each branch yields different fir.declare which means that the
+// origin is from either - this case is handled as Indirect.
+// CHECK-LABEL: Testing : "test_rb_both_alloc_distinct_outer_scope"
+// CHECK-DAG: d1#0 <-> join_outer#0: MayAlias
+// CHECK-DAG: d2#0 <-> join_outer#0: MayAlias
+// CHECK-DAG: outside_outer#0 <-> join_outer#0: MayAlias
+
+func.func @test_rb_both_alloc_distinct_outer_scope() {
+ %cond = arith.constant true
+ %a1 = fir.alloca f32 {uniq_name = "_QFEa1o"}
+ %a2 = fir.alloca f32 {uniq_name = "_QFEa2o"}
+ %d1 = fir.declare %a1 {uniq_name = "_QFEa1o", test.ptr = "d1"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ %d2 = fir.declare %a2 {uniq_name = "_QFEa2o", test.ptr = "d2"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ %a_ext = fir.alloca f32 {uniq_name = "_QFEa_ext_o"}
+ %d_ext = fir.declare %a_ext {uniq_name = "_QFEa_ext_o", test.ptr = "outside_outer"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ %jf = fir.if %cond -> !fir.ref<f32> {
+ fir.result %d1 : !fir.ref<f32>
+ } else {
+ fir.result %d2 : !fir.ref<f32>
+ }
+ %join = fir.convert %jf {test.ptr = "join_outer"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ return
+}
+
+// -----
+
+// Both branches yield the same fir.declare from outside.
+// CHECK-LABEL: Testing : "test_rb_both_alloc_same_decl"
+// CHECK-DAG: shared_decl#0 <-> join_same#0: MustAlias
+
+func.func @test_rb_both_alloc_same_decl() {
+ %cond = arith.constant true
+ %a = fir.alloca f32 {uniq_name = "_QFEa"}
+ %da = fir.declare %a {uniq_name = "_QFEa", test.ptr = "shared_decl"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ %jf = fir.if %cond -> !fir.ref<f32> {
+ fir.result %da : !fir.ref<f32>
+ } else {
+ fir.result %da : !fir.ref<f32>
+ }
+ %join = fir.convert %jf {test.ptr = "join_same"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ return
+}
+
+// -----
+
+// An example of conservative behavior where one branch return value comes from
+// outside. Because at the merge we do not find a common origin and since there's
+// currently no way to record multiple origins, we fallback to MayAlias.
+// TODO: Ideally this should be NoAlias
+// CHECK-LABEL: Testing : "test_rb_alloc_outer_vs_inner"
+// CHECK-DAG: outside_neither#0 <-> join_oi#0: MayAlias
+
+func.func @test_rb_alloc_outer_vs_inner() {
+ %cond = arith.constant true
+ %a = fir.alloca f32 {uniq_name = "_QFEa"}
+ %da = fir.declare %a {uniq_name = "_QFEa"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ %a_neither = fir.alloca f32 {uniq_name = "_QFEa_neither"}
+ %d_neither = fir.declare %a_neither {uniq_name = "_QFEa_neither", test.ptr = "outside_neither"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ %jf = fir.if %cond -> !fir.ref<f32> {
+ fir.result %da : !fir.ref<f32>
+ } else {
+ %a_else = fir.alloca f32 {uniq_name = "_QFEa_else"}
+ %d_else = fir.declare %a_else {uniq_name = "_QFEa_else"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ fir.result %d_else : !fir.ref<f32>
+ }
+ %join = fir.convert %jf {test.ptr = "join_oi"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ return
+}
+
+// -----
+
+fir.global @rb_merge_g : f32 {
+ %0 = arith.constant 0.0 : f32
+ fir.has_value %0 : f32
+}
+
+// Both branches yield the same global.
+// CHECK-LABEL: Testing : "test_rb_both_same_global"
+// CHECK-DAG: global_decl#0 <-> join_g#0: MustAlias
+
+func.func @test_rb_both_same_global() {
+ %cond = arith.constant true
+ %addr = fir.address_of(@rb_merge_g) : !fir.ref<f32>
+ %dg = fir.declare %addr {uniq_name = "_QErb_merge_g", test.ptr = "global_decl"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ %jf = fir.if %cond -> !fir.ref<f32> {
+ fir.result %dg : !fir.ref<f32>
+ } else {
+ fir.result %dg : !fir.ref<f32>
+ }
+ %join = fir.convert %jf {test.ptr = "join_g"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ return
+}
+
+// -----
+
+// Both branches yield the same dummy argument.
+// CHECK-LABEL: Testing : "test_rb_both_same_argument"
+// CHECK-DAG: decl_arg#0 <-> join_arg#0: MustAlias
+
+func.func @test_rb_both_same_argument(%arg0: !fir.ref<f32> {fir.bindc_name = "x"}) {
+ %cond = arith.constant true
+ %ds = fir.dummy_scope : !fir.dscope
+ %dx = fir.declare %arg0 dummy_scope %ds arg 1 {uniq_name = "_QFEex", test.ptr = "decl_arg"} : (!fir.ref<f32>, !fir.dscope) -> !fir.ref<f32>
+ %jf = fir.if %cond -> !fir.ref<f32> {
+ fir.result %dx : !fir.ref<f32>
+ } else {
+ fir.result %dx : !fir.ref<f32>
+ }
+ %join = fir.convert %jf {test.ptr = "join_arg"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ return
+}
+
+// -----
+
+fir.global @rb_side_g : f32 {
+ %0 = arith.constant 0.0 : f32
+ fir.has_value %0 : f32
+}
+
+// Merged kinds differ (Allocate vs Global) thus conservative MayAlias
+// (and this would be the case even if we tracked multiple origins).
+// CHECK-LABEL: Testing : "test_rb_merged_unknown_global_vs_alloc"
+// CHECK-DAG: outside_mixed#0 <-> join_mixed#0: MayAlias
+
+func.func @test_rb_merged_unknown_global_vs_alloc() {
+ %cond = arith.constant true
+ %addr = fir.address_of(@rb_side_g) : !fir.ref<f32>
+ %dg = fir.declare %addr {uniq_name = "_QErb_side_g"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ %a = fir.alloca f32 {uniq_name = "_QFEa"}
+ %da = fir.declare %a {uniq_name = "_QFEa"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ %a_by = fir.alloca f32 {uniq_name = "_QFEa_by"}
+ %d_by = fir.declare %a_by {uniq_name = "_QFEa_by", test.ptr = "outside_mixed"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ %jf = fir.if %cond -> !fir.ref<f32> {
+ fir.result %dg : !fir.ref<f32>
+ } else {
+ fir.result %da : !fir.ref<f32>
+ }
+ %join = fir.convert %jf {test.ptr = "join_mixed"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ return
+}
+
+// -----
+
+// Distinct fir.alloca in each branch with mismatched Fortran attrs on the declares;
+// despite being different allocations inside the branches, we give conservative
+// response. This is primarily to test that the attribute merge is working but
+// this pattern is unlikely to be generated from real Fortran code.
+// CHECK-LABEL: Testing : "test_rb_merged_unknown_attr_mismatch"
+// CHECK-DAG: outside_attr#0 <-> join_attr#0: MayAlias
+
+func.func @test_rb_merged_unknown_attr_mismatch() {
+ %cond = arith.constant true
+ %a_ext = fir.alloca f32 {uniq_name = "_QFEa_attr_ext"}
+ %d_ext = fir.declare %a_ext {uniq_name = "_QFEa_attr_ext", test.ptr = "outside_attr"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ %jf = fir.if %cond -> !fir.ref<f32> {
+ %a1 = fir.alloca f32 {uniq_name = "_QFEa1"}
+ %dt = fir.declare %a1 {fortran_attrs = #fir.var_attrs<target>, uniq_name = "_QFEa1"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ fir.result %dt : !fir.ref<f32>
+ } else {
+ %a2 = fir.alloca f32 {uniq_name = "_QFEa2"}
+ %dp = fir.declare %a2 {uniq_name = "_QFEa2"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ fir.result %dp : !fir.ref<f32>
+ }
+ %join = fir.convert %jf {test.ptr = "join_attr"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ return
+}
+
+// -----
+
+// One branch yields the dummy box, the other yields fir.pack_array of the same
+// box (approximateSource on that predecessor). Join is disambiguated from a
+// different dummy argument's data.
+// CHECK-LABEL: Testing : "test_rb_merge_one_branch_pack_array"
+// CHECK-DAG: join_pack#0 <-> other_arg_box#0: NoAlias
+
+func.func @test_rb_merge_one_branch_pack_array(
+ %arg0: !fir.box<!fir.array<?xf32>> {fir.bindc_name = "x"},
+ %arg1: !fir.box<!fir.array<?xf32>> {fir.bindc_name = "y"}) {
+ %c = arith.constant true
+ %packed = fir.pack_array %arg0 heap whole : (!fir.box<!fir.array<?xf32>>) -> !fir.box<!fir.array<?xf32>>
+ %jf = fir.if %c -> !fir.box<!fir.array<?xf32>> {
+ fir.result %arg0 : !fir.box<!fir.array<?xf32>>
+ } else {
+ fir.result %packed : !fir.box<!fir.array<?xf32>>
+ }
+ %join = fir.convert %jf {test.ptr = "join_pack"} : (!fir.box<!fir.array<?xf32>>) -> !fir.box<!fir.array<?xf32>>
+ %other = fir.convert %arg1 {test.ptr = "other_arg_box"} : (!fir.box<!fir.array<?xf32>>) -> !fir.box<!fir.array<?xf32>>
+ return
+}
+
+// -----
+
+// OPTIONAL pattern tested against an unrelated fir.alloca.
+// CHECK-LABEL: Testing : "test_rb_optional_ref_present_absent"
+// CHECK-DAG: outside_opt#0 <-> opt_join#0: NoAlias
+
+func.func @test_rb_optional_ref_present_absent(%arg0: !fir.ref<i32> {fir.bindc_name = "x", fir.optional}) {
+ %present = fir.is_present %arg0 : (!fir.ref<i32>) -> i1
+ %a_ext = fir.alloca i32 {uniq_name = "_QFEopt_ext"}
+ %d_ext = fir.declare %a_ext {uniq_name = "_QFEopt_ext", test.ptr = "outside_opt"} : (!fir.ref<i32>) -> !fir.ref<i32>
+ %slot = fir.if %present -> !fir.ref<i32> {
+ %a = fir.alloca i32 {uniq_name = "_QFEopt"}
+ %d = fir.declare %a {uniq_name = "_QFEopt"} : (!fir.ref<i32>) -> !fir.ref<i32>
+ fir.result %d : !fir.ref<i32>
+ } else {
+ %abs = fir.absent !fir.ref<i32>
+ fir.result %abs : !fir.ref<i32>
+ }
+ %join = fir.convert %slot {test.ptr = "opt_join"} : (!fir.ref<i32>) -> !fir.ref<i32>
+ return
+}
+
+// -----
+
+// Same OPTIONAL idea as above, but the unrelated fir.alloca outside the if is
+// declared with TARGET: join should still not alias that unrelated storage.
+// CHECK-LABEL: Testing : "test_rb_optional_ref_outside_target"
+// CHECK-DAG: outside_opt_tgt#0 <-> opt_join_tgt#0: NoAlias
+
+func.func @test_rb_optional_ref_outside_target(%arg0: !fir.ref<i32> {fir.bindc_name = "x", fir.optional}) {
+ %present = fir.is_present %arg0 : (!fir.ref<i32>) -> i1
+ %a_ext = fir.alloca i32 {uniq_name = "_QFEopt_ext_t"}
+ %d_ext = fir.declare %a_ext {fortran_attrs = #fir.var_attrs<target>, uniq_name = "_QFEopt_ext_t", test.ptr = "outside_opt_tgt"} : (!fir.ref<i32>) -> !fir.ref<i32>
+ %slot = fir.if %present -> !fir.ref<i32> {
+ %a = fir.alloca i32 {uniq_name = "_QFEopt_t"}
+ %d = fir.declare %a {uniq_name = "_QFEopt_t"} : (!fir.ref<i32>) -> !fir.ref<i32>
+ fir.result %d : !fir.ref<i32>
+ } else {
+ %abs = fir.absent !fir.ref<i32>
+ fir.result %abs : !fir.ref<i32>
+ }
+ %join = fir.convert %slot {test.ptr = "opt_join_tgt"} : (!fir.ref<i32>) -> !fir.ref<i32>
+ return
+}
>From b8b9b13109dcdd0aa64bae980c16d11e12c3eb34 Mon Sep 17 00:00:00 2001
From: Razvan Lupusoru <rlupusoru at nvidia.com>
Date: Wed, 6 May 2026 14:01:18 -0700
Subject: [PATCH 2/4] Add loop result test
---
.../alias-analysis-regionbranch.mlir | 24 +++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/flang/test/Analysis/AliasAnalysis/alias-analysis-regionbranch.mlir b/flang/test/Analysis/AliasAnalysis/alias-analysis-regionbranch.mlir
index 7b0ed3aee84f4..ea0dd5d99570f 100644
--- a/flang/test/Analysis/AliasAnalysis/alias-analysis-regionbranch.mlir
+++ b/flang/test/Analysis/AliasAnalysis/alias-analysis-regionbranch.mlir
@@ -260,3 +260,27 @@ func.func @test_rb_optional_ref_outside_target(%arg0: !fir.ref<i32> {fir.bindc_n
%join = fir.convert %slot {test.ptr = "opt_join_tgt"} : (!fir.ref<i32>) -> !fir.ref<i32>
return
}
+
+// -----
+
+// Add loop test whose result is based on BlockArguments to ensure
+// no cycle.
+// CHECK-LABEL: Testing : "test_rb_do_loop_iter_carry_ref"
+// CHECK-DAG: carry_init#0 <-> outside_loop#0: NoAlias
+// CHECK-DAG: carry_init#0 <-> loop_join_ref#0: MayAlias
+// CHECK-DAG: outside_loop#0 <-> loop_join_ref#0: MayAlias
+
+func.func @test_rb_do_loop_iter_carry_ref() {
+ %lb = arith.constant 1 : index
+ %ub = arith.constant 2 : index
+ %st = arith.constant 1 : index
+ %a_carry = fir.alloca f32 {uniq_name = "_QFEcarry"}
+ %d_carry = fir.declare %a_carry {uniq_name = "_QFEcarry", test.ptr = "carry_init"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ %a_out = fir.alloca f32 {uniq_name = "_QFEoutside"}
+ %d_out = fir.declare %a_out {uniq_name = "_QFEoutside", test.ptr = "outside_loop"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ %loop_res = fir.do_loop %iv = %lb to %ub step %st iter_args(%carry = %d_carry) -> (!fir.ref<f32>) {
+ fir.result %carry : !fir.ref<f32>
+ }
+ %join = fir.convert %loop_res {test.ptr = "loop_join_ref"} : (!fir.ref<f32>) -> !fir.ref<f32>
+ return
+}
>From 9d3d996348092c7331ca83406e33655197caf2d8 Mon Sep 17 00:00:00 2001
From: Razvan Lupusoru <rlupusoru at nvidia.com>
Date: Thu, 7 May 2026 07:13:24 -0700
Subject: [PATCH 3/4] Add == operator for AccessPath
---
flang/include/flang/Optimizer/Analysis/AliasAnalysis.h | 5 +++++
flang/lib/Optimizer/Analysis/AliasAnalysis.cpp | 4 +---
2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/flang/include/flang/Optimizer/Analysis/AliasAnalysis.h b/flang/include/flang/Optimizer/Analysis/AliasAnalysis.h
index a310d133646ba..115358613e383 100644
--- a/flang/include/flang/Optimizer/Analysis/AliasAnalysis.h
+++ b/flang/include/flang/Optimizer/Analysis/AliasAnalysis.h
@@ -190,6 +190,11 @@ struct AliasAnalysis {
});
}
+ bool operator==(const AccessPath &o) const {
+ return isApproximate == o.isApproximate && steps == o.steps;
+ }
+ bool operator!=(const AccessPath &o) const { return !(*this == o); }
+
void print(llvm::raw_ostream &os) const;
};
diff --git a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
index 98478a49477a9..6178e284bbf21 100644
--- a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
+++ b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
@@ -212,9 +212,7 @@ static fir::AliasAnalysis::Source mergeRegionBranchPredecessorSources(
});
bool allPathsSame =
llvm::all_of(sources, [&](const fir::AliasAnalysis::Source &s) {
- return s.accessPath.isApproximate ==
- sources[0].accessPath.isApproximate &&
- s.accessPath.steps == sources[0].accessPath.steps;
+ return s.accessPath == sources[0].accessPath;
});
bool allTypesSame =
llvm::all_of(sources, [&](const fir::AliasAnalysis::Source &s) {
>From f124545239832fe7185a7003e2c1354b4d899cf0 Mon Sep 17 00:00:00 2001
From: Razvan Lupusoru <rlupusoru at nvidia.com>
Date: Thu, 7 May 2026 07:14:24 -0700
Subject: [PATCH 4/4] Change outside origins count to bool
---
flang/lib/Optimizer/Analysis/AliasAnalysis.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
index 6178e284bbf21..b0a5ac5c9eb88 100644
--- a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
+++ b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
@@ -251,13 +251,13 @@ static fir::AliasAnalysis::Source mergeRegionBranchPredecessorSources(
auto branchOp = mlir::dyn_cast<mlir::RegionBranchOpInterface>(
mlir::cast<mlir::OpResult>(fallbackValue).getOwner());
assert(branchOp && "merge region-branch sources expects branch op result");
- unsigned originsOutsideBranch =
- llvm::count_if(sources, [&](const fir::AliasAnalysis::Source &s) {
+ bool hasOriginOutsideBranch =
+ llvm::any_of(sources, [&](const fir::AliasAnalysis::Source &s) {
return !originIsInsideRegionBranch(branchOp, s);
});
bool keepAllocate =
sources[0].kind == fir::AliasAnalysis::SourceKind::Allocate &&
- originsOutsideBranch == 0u;
+ !hasOriginOutsideBranch;
mergedKind = keepAllocate ? fir::AliasAnalysis::SourceKind::Allocate
: fir::AliasAnalysis::SourceKind::Indirect;
mergedAttrs = sources[0].attributes;
More information about the flang-commits
mailing list