[flang-commits] [flang] [flang] Fix FIR AliasAnalysis for zero-offset view chains (PR #192710)
via flang-commits
flang-commits at lists.llvm.org
Tue May 5 14:09:16 PDT 2026
https://github.com/khaki3 updated https://github.com/llvm/llvm-project/pull/192710
>From 0ed14f231e29ca3b68ba08023e41ff31f310df05 Mon Sep 17 00:00:00 2001
From: Kazuaki Matsumura <kmatsumura at nvidia.com>
Date: Mon, 4 May 2026 21:50:07 -0700
Subject: [PATCH 1/3] [flang][OpenACC] Fix implicit data mapping for deviceptr
inside host_data
When a subroutine containing acc data deviceptr(b) with acc serial
is inlined, the deviceptr variable (an embox result) and the serial
variable (the underlying ref) become different SSA values. The existing
alias analysis traces through the full def-chain via getSource(), which
can encounter upstream sliced operations that set approximateSource,
causing MayAlias instead of MustAlias. This leads ACCImplicitData to
incorrectly generate copyin/copyout instead of deviceptr.
Fix:
- Add isOnZeroOffsetViewChain() to fir::AliasAnalysis that walks
backward through FortranObjectViewOpInterface ops with zero offset
to determine if one value is directly derived from another.
- Place this check in the 4-argument alias(Source, Source, Value, Value)
so it is effective in both the bbc/fir-opt and f18/LRO pipelines.
---
.../lib/Optimizer/Analysis/AliasAnalysis.cpp | 28 +++++++++
.../alias-analysis-zero-offset-view-chain.fir | 60 +++++++++++++++++++
.../Transforms/OpenACC/acc-implicit-data.fir | 43 +++++++++++++
3 files changed, 131 insertions(+)
create mode 100644 flang/test/Analysis/AliasAnalysis/alias-analysis-zero-offset-view-chain.fir
diff --git a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
index 2509334b8ec4a..b84ea3aef6187 100644
--- a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
+++ b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
@@ -365,6 +365,24 @@ static bool pathsDivergeAtComponent(const fir::AliasAnalysis::Source &lhsSrc,
return false;
}
+/// Walk backward from \p val through FortranObjectViewOpInterface ops
+/// that have zero offset (i.e. they access the same base address).
+/// Return true if \p other is found on this chain.
+static bool isOnZeroOffsetViewChain(mlir::Value val, mlir::Value other) {
+ while (auto *defOp = val.getDefiningOp()) {
+ auto viewOp = mlir::dyn_cast<fir::FortranObjectViewOpInterface>(defOp);
+ if (!viewOp)
+ break;
+ auto offset = viewOp.getViewOffset(mlir::cast<mlir::OpResult>(val));
+ if (!offset || *offset != 0)
+ break;
+ val = viewOp.getViewSource(mlir::cast<mlir::OpResult>(val));
+ if (val == other)
+ return true;
+ }
+ return false;
+}
+
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
@@ -379,6 +397,16 @@ AliasResult AliasAnalysis::alias(Source lhsSrc, Source rhsSrc, mlir::Value lhs,
// TODO: alias() has to be aware of the function scopes.
// After MLIR inlining, the current implementation may
// not recognize non-aliasing entities.
+
+ // If one value is directly derived from the other through a chain of
+ // zero-offset view operations (e.g. embox, declare, convert), they
+ // access the same underlying memory. This check avoids the case where
+ // getSource() traces through upstream operations (e.g. a sliced embox)
+ // that set approximateSource, conservatively preventing MustAlias.
+ if (lhs == rhs || isOnZeroOffsetViewChain(lhs, rhs) ||
+ isOnZeroOffsetViewChain(rhs, lhs))
+ return AliasResult::MustAlias;
+
bool approximateSource = lhsSrc.approximateSource || rhsSrc.approximateSource;
LLVM_DEBUG(llvm::dbgs() << "\nAliasAnalysis::alias\n";
llvm::dbgs() << " lhs: " << lhs << "\n";
diff --git a/flang/test/Analysis/AliasAnalysis/alias-analysis-zero-offset-view-chain.fir b/flang/test/Analysis/AliasAnalysis/alias-analysis-zero-offset-view-chain.fir
new file mode 100644
index 0000000000000..dd6bf1a889014
--- /dev/null
+++ b/flang/test/Analysis/AliasAnalysis/alias-analysis-zero-offset-view-chain.fir
@@ -0,0 +1,60 @@
+// Test that values connected through zero-offset FortranObjectViewOpInterface
+// operations (embox, declare, convert) are recognized as MustAlias, even when
+// getSource() would set approximateSource due to upstream sliced operations.
+// RUN: fir-opt %s -pass-pipeline='builtin.module(func.func(test-fir-alias-analysis))' -split-input-file --mlir-disable-threading 2>&1 | FileCheck %s
+
+// Test: embox(ref) vs ref => MustAlias
+// This is the pattern that arises when a subroutine with acc.deviceptr
+// on embox(b) is inlined into a caller that uses b directly.
+// CHECK-LABEL: Testing : "_QPtest_embox_ref"
+// CHECK-DAG: ref#0 <-> box#0: MustAlias
+func.func @_QPtest_embox_ref(%arg0: !fir.ref<!fir.array<10xf32>> {fir.bindc_name = "b"}) {
+ %c10 = arith.constant 10 : index
+ %shape = fir.shape %c10 : (index) -> !fir.shape<1>
+ %ref = fir.declare %arg0(%shape) {test.ptr = "ref", uniq_name = "b"} : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>) -> !fir.ref<!fir.array<10xf32>>
+ %box = fir.embox %ref(%shape) {test.ptr = "box"} : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>) -> !fir.box<!fir.array<10xf32>>
+ return
+}
+
+// -----
+
+// Test: declare(ref) vs ref => MustAlias
+// CHECK-LABEL: Testing : "_QPtest_declare_ref"
+// CHECK-DAG: ref#0 <-> decl#0: MustAlias
+func.func @_QPtest_declare_ref() {
+ %ref = fir.alloca !fir.array<10xf32> {test.ptr = "ref"}
+ %c10 = arith.constant 10 : index
+ %shape = fir.shape %c10 : (index) -> !fir.shape<1>
+ %decl = fir.declare %ref(%shape) {test.ptr = "decl", uniq_name = "arr"} : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>) -> !fir.ref<!fir.array<10xf32>>
+ return
+}
+
+// -----
+
+// Test: embox(declare(ref)) vs ref => MustAlias (two-hop chain)
+// CHECK-LABEL: Testing : "_QPtest_embox_declare_ref"
+// CHECK-DAG: ref#0 <-> box#0: MustAlias
+func.func @_QPtest_embox_declare_ref() {
+ %ref = fir.alloca !fir.array<10xf32> {test.ptr = "ref"}
+ %c10 = arith.constant 10 : index
+ %shape = fir.shape %c10 : (index) -> !fir.shape<1>
+ %decl = fir.declare %ref(%shape) {uniq_name = "arr"} : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>) -> !fir.ref<!fir.array<10xf32>>
+ %box = fir.embox %decl(%shape) {test.ptr = "box"} : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>) -> !fir.box<!fir.array<10xf32>>
+ return
+}
+
+// -----
+
+// Test: embox with slice does NOT chain-walk to the ref (non-zero offset)
+// CHECK-LABEL: Testing : "_QPtest_embox_slice_ref"
+// CHECK-DAG: ref#0 <-> sliced_box#0: MayAlias
+func.func @_QPtest_embox_slice_ref() {
+ %ref = fir.alloca !fir.array<10xf32> {test.ptr = "ref"}
+ %c10 = arith.constant 10 : index
+ %c1 = arith.constant 1 : index
+ %c5 = arith.constant 5 : index
+ %shape = fir.shape %c10 : (index) -> !fir.shape<1>
+ %slice = fir.slice %c1, %c5, %c1 : (index, index, index) -> !fir.slice<1>
+ %sliced_box = fir.embox %ref(%shape) [%slice] {test.ptr = "sliced_box"} : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>, !fir.slice<1>) -> !fir.box<!fir.array<?xf32>>
+ return
+}
diff --git a/flang/test/Transforms/OpenACC/acc-implicit-data.fir b/flang/test/Transforms/OpenACC/acc-implicit-data.fir
index 050fe55747d23..e20e3231438d8 100644
--- a/flang/test/Transforms/OpenACC/acc-implicit-data.fir
+++ b/flang/test/Transforms/OpenACC/acc-implicit-data.fir
@@ -394,3 +394,46 @@ func.func private @_FortranAAllocatableSetBounds(!fir.ref<!fir.box<none>>, i32,
// CHECK-NOT: acc.copyin
// CHECK: acc.deviceptr
// CHECK-NOT: acc.copyout
+
+// -----
+
+// Test that acc.serial inside acc.data deviceptr generates implicit deviceptr
+// (not copyin) when the deviceptr clause variable is derived from the ref used
+// by the serial construct (here, via fir.embox wrapping the declared ref).
+// This pattern arises when a subroutine containing:
+// !$acc data deviceptr(b) ← deviceptr operates on embox(b) (a box type)
+// !$acc serial
+// ... uses b ... ← serial uses b directly (a ref type)
+// !$acc end serial
+// !$acc end data
+// is inlined into the caller. After inlining, the deviceptr's box and the
+// serial's ref are different SSA values with different types, so alias
+// analysis returns NoAlias. The pass must recognize that the deviceptr's
+// variable is derived from the ref and generate an implicit deviceptr clause
+// instead of falling back to copyin/copyout.
+func.func @test_serial_inside_data_deviceptr_embox() {
+ %c10 = arith.constant 10 : index
+ %c1 = arith.constant 1 : index
+ %arr = fir.alloca !fir.array<10xf32> {bindc_name = "b"}
+ %shape = fir.shape %c10 : (index) -> !fir.shape<1>
+ %arr_decl = fir.declare %arr(%shape) {uniq_name = "b"} : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>) -> !fir.ref<!fir.array<10xf32>>
+ %box = fir.embox %arr_decl(%shape) : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>) -> !fir.box<!fir.array<10xf32>>
+ %devptr = acc.deviceptr var(%box : !fir.box<!fir.array<10xf32>>) -> !fir.box<!fir.array<10xf32>> {name = "b(1:10)"}
+ acc.data dataOperands(%devptr : !fir.box<!fir.array<10xf32>>) {
+ acc.serial {
+ %elem = fir.array_coor %arr_decl(%shape) %c1 : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>, index) -> !fir.ref<f32>
+ %val = fir.load %elem : !fir.ref<f32>
+ acc.yield
+ }
+ acc.terminator
+ }
+ return
+}
+
+// CHECK-LABEL: func.func @test_serial_inside_data_deviceptr_embox
+// CHECK: acc.deviceptr var({{.*}} : !fir.box<!fir.array<10xf32>>) -> !fir.box<!fir.array<10xf32>> {name = "b(1:10)"}
+// CHECK: acc.data
+// CHECK: acc.deviceptr varPtr({{.*}} : !fir.ref<!fir.array<10xf32>>) -> !fir.ref<!fir.array<10xf32>> {implicit = true, name = "b"}
+// CHECK: acc.serial dataOperands({{.*}} : !fir.ref<!fir.array<10xf32>>)
+// CHECK-NOT: acc.copyin
+// CHECK-NOT: acc.copyout
>From b0e78914e9e8dfb28821ef1420f04f3740ac443a Mon Sep 17 00:00:00 2001
From: Kazuaki Matsumura <kmatsumura at nvidia.com>
Date: Mon, 4 May 2026 22:04:17 -0700
Subject: [PATCH 2/3] [flang][OpenACC] Fix implicit data mapping for deviceptr
inside host_data
When a subroutine containing acc data deviceptr(b) with acc serial
is inlined, the deviceptr variable (an embox result) and the serial
variable (the underlying ref) become different SSA values. The existing
alias analysis traces through the full def-chain via getSource(), which
can encounter upstream sliced operations that set approximateSource,
causing MayAlias instead of MustAlias. The pass falls back to implicit
copyin/copyout instead of deviceptr, causing a segfault.
Fix: add getZeroOffsetViewRoot() to fir::AliasAnalysis that walks
backward through FortranObjectViewOpInterface ops with zero offset
to find the root value. If both values share the same root, MustAlias
is returned immediately, avoiding the conservative MayAlias caused by
distant approximateSource flags.
---
flang/lib/Optimizer/Analysis/AliasAnalysis.cpp | 17 +++++++----------
.../AliasAnalysis/alias-analysis-pack-array.fir | 6 ++----
.../alias-analysis-zero-offset-view-chain.fir | 13 +++++++++++++
3 files changed, 22 insertions(+), 14 deletions(-)
diff --git a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
index b84ea3aef6187..9335ab688128b 100644
--- a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
+++ b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
@@ -367,8 +367,8 @@ static bool pathsDivergeAtComponent(const fir::AliasAnalysis::Source &lhsSrc,
/// Walk backward from \p val through FortranObjectViewOpInterface ops
/// that have zero offset (i.e. they access the same base address).
-/// Return true if \p other is found on this chain.
-static bool isOnZeroOffsetViewChain(mlir::Value val, mlir::Value other) {
+/// Return the root value at the end of the chain.
+static mlir::Value getZeroOffsetViewRoot(mlir::Value val) {
while (auto *defOp = val.getDefiningOp()) {
auto viewOp = mlir::dyn_cast<fir::FortranObjectViewOpInterface>(defOp);
if (!viewOp)
@@ -377,10 +377,8 @@ static bool isOnZeroOffsetViewChain(mlir::Value val, mlir::Value other) {
if (!offset || *offset != 0)
break;
val = viewOp.getViewSource(mlir::cast<mlir::OpResult>(val));
- if (val == other)
- return true;
}
- return false;
+ return val;
}
AliasResult AliasAnalysis::alias(mlir::Value lhs, mlir::Value rhs) {
@@ -398,13 +396,12 @@ AliasResult AliasAnalysis::alias(Source lhsSrc, Source rhsSrc, mlir::Value lhs,
// After MLIR inlining, the current implementation may
// not recognize non-aliasing entities.
- // If one value is directly derived from the other through a chain of
- // zero-offset view operations (e.g. embox, declare, convert), they
- // access the same underlying memory. This check avoids the case where
+ // 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
// getSource() traces through upstream operations (e.g. a sliced embox)
// that set approximateSource, conservatively preventing MustAlias.
- if (lhs == rhs || isOnZeroOffsetViewChain(lhs, rhs) ||
- isOnZeroOffsetViewChain(rhs, lhs))
+ if (lhs == rhs || getZeroOffsetViewRoot(lhs) == getZeroOffsetViewRoot(rhs))
return AliasResult::MustAlias;
bool approximateSource = lhsSrc.approximateSource || rhsSrc.approximateSource;
diff --git a/flang/test/Analysis/AliasAnalysis/alias-analysis-pack-array.fir b/flang/test/Analysis/AliasAnalysis/alias-analysis-pack-array.fir
index 06d090407a26b..5c2520723f672 100644
--- a/flang/test/Analysis/AliasAnalysis/alias-analysis-pack-array.fir
+++ b/flang/test/Analysis/AliasAnalysis/alias-analysis-pack-array.fir
@@ -16,10 +16,8 @@
// CHECK-DAG: test1_x_repack(1)#0 <-> test1_x_orig(1)#0: MayAlias
// CHECK-DAG: test1_y_repack(1)#0 <-> test1_y_orig(1)#0: MayAlias
-// Ideally, these should report MustAlias, but MayAlias
-// may work as well:
-// CHECK-DAG: test1_y_repack(1)#0 <-> test1_y_repack2(1)#0: MayAlias
-// CHECK-DAG: test1_x_repack(1)#0 <-> test1_x_repack2(1)#0: MayAlias
+// CHECK-DAG: test1_y_repack(1)#0 <-> test1_y_repack2(1)#0: MustAlias
+// CHECK-DAG: test1_x_repack(1)#0 <-> test1_x_repack2(1)#0: MustAlias
func.func @_QFtest1(%arg0: !fir.box<!fir.array<?xf32>> {fir.bindc_name = "x"}, %arg1: !fir.box<!fir.array<?xf32>> {fir.bindc_name = "y"}) {
diff --git a/flang/test/Analysis/AliasAnalysis/alias-analysis-zero-offset-view-chain.fir b/flang/test/Analysis/AliasAnalysis/alias-analysis-zero-offset-view-chain.fir
index dd6bf1a889014..ab368a2e9cab1 100644
--- a/flang/test/Analysis/AliasAnalysis/alias-analysis-zero-offset-view-chain.fir
+++ b/flang/test/Analysis/AliasAnalysis/alias-analysis-zero-offset-view-chain.fir
@@ -45,6 +45,19 @@ func.func @_QPtest_embox_declare_ref() {
// -----
+// Test: two box_addr from the same source => MustAlias (sibling case)
+// CHECK-LABEL: Testing : "_QPtest_sibling_box_addr"
+// CHECK-DAG: addr1#0 <-> addr2#0: MustAlias
+func.func @_QPtest_sibling_box_addr(%arg0: !fir.box<!fir.array<?xf32>> {fir.bindc_name = "x"}) {
+ %0 = fir.dummy_scope : !fir.dscope
+ %1:2 = hlfir.declare %arg0 dummy_scope %0 {uniq_name = "_QPtestEx"} : (!fir.box<!fir.array<?xf32>>, !fir.dscope) -> (!fir.box<!fir.array<?xf32>>, !fir.box<!fir.array<?xf32>>)
+ %addr1 = fir.box_addr %1#0 {test.ptr = "addr1"} : (!fir.box<!fir.array<?xf32>>) -> !fir.ref<f32>
+ %addr2 = fir.box_addr %1#0 {test.ptr = "addr2"} : (!fir.box<!fir.array<?xf32>>) -> !fir.ref<f32>
+ return
+}
+
+// -----
+
// Test: embox with slice does NOT chain-walk to the ref (non-zero offset)
// CHECK-LABEL: Testing : "_QPtest_embox_slice_ref"
// CHECK-DAG: ref#0 <-> sliced_box#0: MayAlias
>From 878e2de4fbff0e66da879d64c3af74cdedd23de1 Mon Sep 17 00:00:00 2001
From: Kazuaki Matsumura <kmatsumura at nvidia.com>
Date: Tue, 5 May 2026 14:06:10 -0700
Subject: [PATCH 3/3] Remove OpenACC-specific comment from alias analysis test
The test is a pure FIR alias analysis test; the acc.deviceptr
motivation is irrelevant here.
---
.../AliasAnalysis/alias-analysis-zero-offset-view-chain.fir | 2 --
1 file changed, 2 deletions(-)
diff --git a/flang/test/Analysis/AliasAnalysis/alias-analysis-zero-offset-view-chain.fir b/flang/test/Analysis/AliasAnalysis/alias-analysis-zero-offset-view-chain.fir
index ab368a2e9cab1..50c3ca715ea88 100644
--- a/flang/test/Analysis/AliasAnalysis/alias-analysis-zero-offset-view-chain.fir
+++ b/flang/test/Analysis/AliasAnalysis/alias-analysis-zero-offset-view-chain.fir
@@ -4,8 +4,6 @@
// RUN: fir-opt %s -pass-pipeline='builtin.module(func.func(test-fir-alias-analysis))' -split-input-file --mlir-disable-threading 2>&1 | FileCheck %s
// Test: embox(ref) vs ref => MustAlias
-// This is the pattern that arises when a subroutine with acc.deviceptr
-// on embox(b) is inlined into a caller that uses b directly.
// CHECK-LABEL: Testing : "_QPtest_embox_ref"
// CHECK-DAG: ref#0 <-> box#0: MustAlias
func.func @_QPtest_embox_ref(%arg0: !fir.ref<!fir.array<10xf32>> {fir.bindc_name = "b"}) {
More information about the flang-commits
mailing list