[flang-commits] [flang] [flang][OpenMP] Fix alias analysis for omp.private copy region block arguments to help inline hlfir.assign to improve time taken in LTO. (PR #209539)
Pranav Bhandarkar via flang-commits
flang-commits at lists.llvm.org
Thu Jul 16 22:00:05 PDT 2026
https://github.com/bhandarkar-pranav updated https://github.com/llvm/llvm-project/pull/209539
>From bddfff9597d72ad4394b1308080cf8058a1a7993 Mon Sep 17 00:00:00 2001
From: Pranav Bhandarkar <pranav.bhandarkar at amd.com>
Date: Tue, 2 Jun 2026 15:37:46 -0500
Subject: [PATCH 1/5] [flang][OpenMP] Fix alias analysis for omp.private copy
region block arguments
Fixes a compile-time regression for firstprivate on simple arrays.
Fixes #200922
Root cause: When InlineHLFIRAssign checks whether to inline hlfir.assign
operations in omp.private copy regions, alias analysis conservatively
returns MayAlias for the copy region block arguments (%arg0 mold vs
%arg1 private). This causes InlineHLFIRAssign to fail and fall back to
fir.call @_FortranAAssign, which pulls in 89 runtime functions via LTO,
creating 550K+ abstract attributes in OpenMPOpt.
Solution: Add special-case handling in FIR alias analysis to recognize
omp.private copy region block arguments. The OpenMP spec guarantees that
the mold and private arguments reference different memory (private copy
is freshly allocated in the init region), so they cannot alias.
The fix allows InlineHLFIRAssign to inline the assignment into an
element-wise loop, avoiding the runtime call entirely.
Test: Added alias-analysis-omp-private-copy-region.mlir
Assisted by: Claude Sonnet
---
.../lib/Optimizer/Analysis/AliasAnalysis.cpp | 42 ++++++++++++++++
...lias-analysis-omp-private-copy-region.mlir | 50 +++++++++++++++++++
2 files changed, 92 insertions(+)
create mode 100644 flang/test/Analysis/AliasAnalysis/alias-analysis-omp-private-copy-region.mlir
diff --git a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
index 599add48e4e95..64d63db0e9591 100644
--- a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
+++ b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
@@ -662,6 +662,48 @@ AliasResult AliasAnalysis::alias(Source lhsSrc, Source rhsSrc, mlir::Value lhs,
if (lhs == rhs || getZeroOffsetViewRoot(lhs) == getZeroOffsetViewRoot(rhs))
return AliasResult::MustAlias;
+ // OpenMP private clause copy region arguments are guaranteed to be disjoint.
+ // The copy region has two arguments: %arg0 (original/mold) and %arg1 (private).
+ // By omp.private semantics, %arg1 is initialized with freshly allocated memory
+ // (from the init region), while %arg0 references the original variable.
+ // These cannot alias. This check handles both direct block argument comparisons
+ // and cases where one value is loaded from a block argument (common pattern:
+ // hlfir.assign %loaded to %arg where %loaded = fir.load %arg0).
+ auto getBlockArgOrLoadSource = [](mlir::Value v) -> mlir::BlockArgument {
+ if (auto arg = mlir::dyn_cast<BlockArgument>(v))
+ return arg;
+ // Check if this is a load from a block argument
+ if (auto defOp = v.getDefiningOp()) {
+ if (auto loadOp = mlir::dyn_cast<fir::LoadOp>(defOp)) {
+ return mlir::dyn_cast<BlockArgument>(loadOp.getMemref());
+ }
+ }
+ return {};
+ };
+
+ auto lhsArg = getBlockArgOrLoadSource(lhs);
+ auto rhsArg = getBlockArgOrLoadSource(rhs);
+
+ if (lhsArg && rhsArg && lhsArg.getParentRegion() == rhsArg.getParentRegion()) {
+ if (auto *parentOp = lhsArg.getParentRegion()->getParentOp()) {
+ if (auto privateOp = mlir::dyn_cast<omp::PrivateClauseOp>(parentOp)) {
+ auto ©Region = privateOp.getCopyRegion();
+ if (lhsArg.getParentRegion() == ©Region) {
+ // Both trace to block args from the copy region - check if they're the defined pair
+ auto moldArg = privateOp.getCopyMoldArg();
+ auto privArg = privateOp.getCopyPrivateArg();
+ if ((lhsArg == moldArg && rhsArg == privArg) ||
+ (lhsArg == privArg && rhsArg == moldArg)) {
+ LLVM_DEBUG(llvm::dbgs()
+ << " no alias: omp.private copy region arguments "
+ << "(mold vs private)\n");
+ return AliasResult::NoAlias;
+ }
+ }
+ }
+ }
+ }
+
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-omp-private-copy-region.mlir b/flang/test/Analysis/AliasAnalysis/alias-analysis-omp-private-copy-region.mlir
new file mode 100644
index 0000000000000..23e2cf2441839
--- /dev/null
+++ b/flang/test/Analysis/AliasAnalysis/alias-analysis-omp-private-copy-region.mlir
@@ -0,0 +1,50 @@
+// Test that InlineHLFIRAssign successfully inlines hlfir.assign operations
+// in omp.private copy regions when the fix for omp.private block argument
+// aliasing is present. Without the fix, alias analysis returns MayAlias and
+// InlineHLFIRAssign fails.
+//
+// RUN: fir-opt %s --inline-hlfir-assign --split-input-file | FileCheck %s
+
+// Fortran source (simplified):
+// program minimal
+// integer :: arr(8)
+// arr(1) = 1
+// !$omp target firstprivate(arr)
+// arr(1) = 42
+// !$omp end target
+// end program
+
+omp.private {type = firstprivate} @arr_privatizer : !fir.box<!fir.array<8xi32>> init {
+^bb0(%arg0: !fir.ref<!fir.box<!fir.array<8xi32>>>, %arg1: !fir.ref<!fir.box<!fir.array<8xi32>>>):
+ %c8 = arith.constant 8 : index
+ %c1 = arith.constant 1 : index
+ %0 = fir.shape %c8 : (index) -> !fir.shape<1>
+ %1 = fir.allocmem !fir.array<8xi32> {bindc_name = ".tmp", uniq_name = ""}
+ %2:2 = hlfir.declare %1(%0) {uniq_name = ".tmp"} : (!fir.heap<!fir.array<8xi32>>, !fir.shape<1>) -> (!fir.heap<!fir.array<8xi32>>, !fir.heap<!fir.array<8xi32>>)
+ %3 = fir.shape_shift %c1, %c8 : (index, index) -> !fir.shapeshift<1>
+ %4 = fir.embox %2#0(%3) : (!fir.heap<!fir.array<8xi32>>, !fir.shapeshift<1>) -> !fir.box<!fir.array<8xi32>>
+ fir.store %4 to %arg1 : !fir.ref<!fir.box<!fir.array<8xi32>>>
+ omp.yield(%arg1 : !fir.ref<!fir.box<!fir.array<8xi32>>>)
+} copy {
+^bb0(%arg0: !fir.ref<!fir.box<!fir.array<8xi32>>>, %arg1: !fir.ref<!fir.box<!fir.array<8xi32>>>):
+ // This hlfir.assign should be inlined into a loop by InlineHLFIRAssign
+ // because alias analysis should return NoAlias for %0 (loaded from %arg0 mold)
+ // vs %arg1 (private copy).
+ %0 = fir.load %arg0 : !fir.ref<!fir.box<!fir.array<8xi32>>>
+ hlfir.assign %0 to %arg1 : !fir.box<!fir.array<8xi32>>, !fir.ref<!fir.box<!fir.array<8xi32>>>
+ omp.yield(%arg1 : !fir.ref<!fir.box<!fir.array<8xi32>>>)
+}
+
+// CHECK-LABEL: omp.private {type = firstprivate} @arr_privatizer
+// CHECK: } copy {
+// CHECK: ^bb0(%[[MOLD:.*]]: !fir.ref<!fir.box<!fir.array<8xi32>>>, %[[PRIV:.*]]: !fir.ref<!fir.box<!fir.array<8xi32>>>):
+// CHECK-NOT: hlfir.assign {{.*}} to {{.*}} : !fir.box<!fir.array<8xi32>>
+// CHECK: fir.do_loop
+// CHECK: hlfir.designate
+// CHECK: hlfir.designate
+// CHECK: hlfir.assign {{.*}} to {{.*}} : i32
+// CHECK: omp.yield
+
+func.func @test_omp_private_copy_inlined() {
+ return
+}
>From 7a31cb4186b5fe3b028eb7cf9de946e20668f05c Mon Sep 17 00:00:00 2001
From: Pranav Bhandarkar <pranav.bhandarkar at amd.com>
Date: Tue, 23 Jun 2026 16:59:11 -0500
Subject: [PATCH 2/5] fix clang-format-issues
---
.../lib/Optimizer/Analysis/AliasAnalysis.cpp | 19 +++++++++++--------
1 file changed, 11 insertions(+), 8 deletions(-)
diff --git a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
index 64d63db0e9591..c398227dcb6eb 100644
--- a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
+++ b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
@@ -663,12 +663,13 @@ AliasResult AliasAnalysis::alias(Source lhsSrc, Source rhsSrc, mlir::Value lhs,
return AliasResult::MustAlias;
// OpenMP private clause copy region arguments are guaranteed to be disjoint.
- // The copy region has two arguments: %arg0 (original/mold) and %arg1 (private).
- // By omp.private semantics, %arg1 is initialized with freshly allocated memory
- // (from the init region), while %arg0 references the original variable.
- // These cannot alias. This check handles both direct block argument comparisons
- // and cases where one value is loaded from a block argument (common pattern:
- // hlfir.assign %loaded to %arg where %loaded = fir.load %arg0).
+ // The copy region has two arguments: %arg0 (original/mold) and %arg1
+ // (private). By omp.private semantics, %arg1 is initialized with freshly
+ // allocated memory (from the init region), while %arg0 references the
+ // original variable. These cannot alias. This check handles both direct block
+ // argument comparisons and cases where one value is loaded from a block
+ // argument (common pattern: hlfir.assign %loaded to %arg where %loaded =
+ // fir.load %arg0).
auto getBlockArgOrLoadSource = [](mlir::Value v) -> mlir::BlockArgument {
if (auto arg = mlir::dyn_cast<BlockArgument>(v))
return arg;
@@ -684,12 +685,14 @@ AliasResult AliasAnalysis::alias(Source lhsSrc, Source rhsSrc, mlir::Value lhs,
auto lhsArg = getBlockArgOrLoadSource(lhs);
auto rhsArg = getBlockArgOrLoadSource(rhs);
- if (lhsArg && rhsArg && lhsArg.getParentRegion() == rhsArg.getParentRegion()) {
+ if (lhsArg && rhsArg &&
+ lhsArg.getParentRegion() == rhsArg.getParentRegion()) {
if (auto *parentOp = lhsArg.getParentRegion()->getParentOp()) {
if (auto privateOp = mlir::dyn_cast<omp::PrivateClauseOp>(parentOp)) {
auto ©Region = privateOp.getCopyRegion();
if (lhsArg.getParentRegion() == ©Region) {
- // Both trace to block args from the copy region - check if they're the defined pair
+ // Both trace to block args from the copy region - check if they're
+ // the defined pair
auto moldArg = privateOp.getCopyMoldArg();
auto privArg = privateOp.getCopyPrivateArg();
if ((lhsArg == moldArg && rhsArg == privArg) ||
>From 0731d7c4bf9b92351c12c465abc94666d2440086 Mon Sep 17 00:00:00 2001
From: Pranav Bhandarkar <pranav.bhandarkar at amd.com>
Date: Mon, 13 Jul 2026 10:01:26 -0500
Subject: [PATCH 3/5] fix comment
---
flang/lib/Optimizer/Analysis/AliasAnalysis.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
index c398227dcb6eb..8bd99fd037735 100644
--- a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
+++ b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
@@ -668,7 +668,7 @@ AliasResult AliasAnalysis::alias(Source lhsSrc, Source rhsSrc, mlir::Value lhs,
// allocated memory (from the init region), while %arg0 references the
// original variable. These cannot alias. This check handles both direct block
// argument comparisons and cases where one value is loaded from a block
- // argument (common pattern: hlfir.assign %loaded to %arg where %loaded =
+ // argument (common pattern: hlfir.assign %loaded to %arg1 where %loaded =
// fir.load %arg0).
auto getBlockArgOrLoadSource = [](mlir::Value v) -> mlir::BlockArgument {
if (auto arg = mlir::dyn_cast<BlockArgument>(v))
>From 0ea361beea669c70da7131f3fb842a691e451246 Mon Sep 17 00:00:00 2001
From: Pranav Bhandarkar <pranav.bhandarkar at amd.com>
Date: Wed, 15 Jul 2026 13:50:03 -0500
Subject: [PATCH 4/5] update test
flang/test/Integration/OpenMP/parallel-private-reduction-worstcase.f90
---
.../parallel-private-reduction-worstcase.f90 | 100 +++++++++---------
1 file changed, 50 insertions(+), 50 deletions(-)
diff --git a/flang/test/Integration/OpenMP/parallel-private-reduction-worstcase.f90 b/flang/test/Integration/OpenMP/parallel-private-reduction-worstcase.f90
index 94080d1d4f975..500e57350ee7b 100644
--- a/flang/test/Integration/OpenMP/parallel-private-reduction-worstcase.f90
+++ b/flang/test/Integration/OpenMP/parallel-private-reduction-worstcase.f90
@@ -83,72 +83,72 @@ subroutine worst_case(a, b, c, d)
! CHECK: omp.private.copy12: ; preds = %omp.private.copy
! [begin firstprivate copy for first var]
! [read the length, is it non-zero?]
-! CHECK: br i1 %{{.*}}, label %omp.private.copy13, label %omp.private.copy14
+! CHECK: br i1 %{{.*}}, label %omp.private.copy13, label %omp.private.copy22
-! CHECK: omp.private.copy14: ; preds = %omp.private.copy13, %omp.private.copy12
+! CHECK: omp.private.copy22: ; preds = %omp.private.copy21, %omp.private.copy12
! CHECK-NEXT: br label %omp.region.cont11
-! CHECK: omp.region.cont11: ; preds = %omp.private.copy14
+! CHECK: omp.region.cont11: ; preds = %omp.private.copy22
! CHECK-NEXT: %{{.*}} = phi ptr
-! CHECK-NEXT: br label %omp.private.copy16
+! CHECK-NEXT: br label %omp.private.copy24
-! CHECK: omp.private.copy16: ; preds = %omp.region.cont11
+! CHECK: omp.private.copy24: ; preds = %omp.region.cont11
! [begin firstprivate copy for second var]
! [read the length, is it non-zero?]
-! CHECK: br i1 %{{.*}}, label %omp.private.copy17, label %omp.private.copy18
+! CHECK: br i1 %{{.*}}, label %omp.private.copy25, label %omp.private.copy34
-! CHECK: omp.private.copy18: ; preds = %omp.private.copy17, %omp.private.copy16
-! CHECK-NEXT: br label %omp.region.cont15
+! CHECK: omp.private.copy34: ; preds = %omp.private.copy33, %omp.private.copy24
+! CHECK-NEXT: br label %omp.region.cont23
-! CHECK: omp.region.cont15: ; preds = %omp.private.copy18
+! CHECK: omp.region.cont23: ; preds = %omp.private.copy34
! CHECK-NEXT: %{{.*}} = phi ptr
! CHECK-NEXT: br label %omp.reduction.init
-! CHECK: omp.reduction.init: ; preds = %omp.region.cont15
+! CHECK: omp.reduction.init: ; preds = %omp.region.cont23
! [deferred stores for results of reduction alloc regions]
! CHECK: br label %[[VAL_96:.*]]
! CHECK: omp.reduction.neutral: ; preds = %omp.reduction.init
! [start of reduction initialization region]
! [null check:]
-! CHECK: br i1 %{{.*}}, label %omp.reduction.neutral20, label %omp.reduction.neutral21
+! CHECK: br i1 %{{.*}}, label %omp.reduction.neutral36, label %omp.reduction.neutral37
-! CHECK: omp.reduction.neutral21: ; preds = %omp.reduction.neutral
+! CHECK: omp.reduction.neutral37: ; preds = %omp.reduction.neutral
! [malloc and assign the default value to the reduction variable]
-! CHECK: br label %omp.reduction.neutral22
+! CHECK: br label %omp.reduction.neutral38
-! CHECK: omp.reduction.neutral22: ; preds = %omp.reduction.neutral20, %omp.reduction.neutral21
-! CHECK-NEXT: br label %omp.region.cont19
+! CHECK: omp.reduction.neutral38: ; preds = %omp.reduction.neutral36, %omp.reduction.neutral37
+! CHECK-NEXT: br label %omp.region.cont35
-! CHECK: omp.region.cont19: ; preds = %omp.reduction.neutral22
+! CHECK: omp.region.cont35: ; preds = %omp.reduction.neutral38
! CHECK-NEXT: %{{.*}} = phi ptr
-! CHECK-NEXT: br label %omp.reduction.neutral24
+! CHECK-NEXT: br label %omp.reduction.neutral40
-! CHECK: omp.reduction.neutral24: ; preds = %omp.region.cont19
+! CHECK: omp.reduction.neutral40: ; preds = %omp.region.cont35
! [start of reduction initialization region]
! [null check:]
-! CHECK: br i1 %{{.*}}, label %omp.reduction.neutral25, label %omp.reduction.neutral26
+! CHECK: br i1 %{{.*}}, label %omp.reduction.neutral41, label %omp.reduction.neutral42
-! CHECK: omp.reduction.neutral26: ; preds = %omp.reduction.neutral24
+! CHECK: omp.reduction.neutral42: ; preds = %omp.reduction.neutral40
! [malloc and assign the default value to the reduction variable]
-! CHECK: br label %omp.reduction.neutral27
+! CHECK: br label %omp.reduction.neutral43
-! CHECK: omp.reduction.neutral27: ; preds = %omp.reduction.neutral25, %omp.reduction.neutral26
-! CHECK-NEXT: br label %omp.region.cont23
+! CHECK: omp.reduction.neutral43: ; preds = %omp.reduction.neutral41, %omp.reduction.neutral42
+! CHECK-NEXT: br label %omp.region.cont39
-! CHECK: omp.region.cont23: ; preds = %omp.reduction.neutral27
+! CHECK: omp.region.cont39: ; preds = %omp.reduction.neutral43
! CHECK-NEXT: %{{.*}} = phi ptr
-! CHECK-NEXT: br label %omp.par.region29
+! CHECK-NEXT: br label %omp.par.region45
-! CHECK: omp.par.region29: ; preds = %omp.region.cont23
+! CHECK: omp.par.region45: ; preds = %omp.region.cont39
! [call SUM runtime function]
! [if (sum(a) == 1)]
-! CHECK: br i1 %{{.*}}, label %omp.par.region30, label %omp.par.region31
+! CHECK: br i1 %{{.*}}, label %omp.par.region46, label %omp.par.region47
-! CHECK: omp.par.region31: ; preds = %omp.par.region29
-! CHECK-NEXT: br label %omp.region.cont28
+! CHECK: omp.par.region47: ; preds = %omp.par.region45
+! CHECK-NEXT: br label %omp.region.cont44
-! CHECK: omp.region.cont28: ; preds = %omp.par.region30, %omp.par.region31
+! CHECK: omp.region.cont44: ; preds = %omp.par.region46, %omp.par.region47
! [omp parallel region done, call into the runtime to complete reduction]
! CHECK: %[[VAL_233:.*]] = call i32 @__kmpc_reduce(
! CHECK: switch i32 %[[VAL_233]], label %reduce.finalize [
@@ -156,16 +156,16 @@ subroutine worst_case(a, b, c, d)
! CHECK-NEXT: i32 2, label %reduce.switch.atomic
! CHECK-NEXT: ]
-! CHECK: reduce.switch.atomic: ; preds = %omp.region.cont28
+! CHECK: reduce.switch.atomic: ; preds = %omp.region.cont44
! CHECK-NEXT: unreachable
-! CHECK: reduce.switch.nonatomic: ; preds = %omp.region.cont28
+! CHECK: reduce.switch.nonatomic: ; preds = %omp.region.cont44
! CHECK-NEXT: %[[red_private_value_0:.*]] = load ptr, ptr %{{.*}}, align 8
! CHECK-NEXT: br label %omp.reduction.nonatomic.body
! [various blocks implementing the reduction]
-! CHECK: omp.region.cont36: ; preds =
+! CHECK: omp.region.cont52: ; preds =
! CHECK-NEXT: %{{.*}} = phi ptr
! CHECK-NEXT: call void @__kmpc_end_reduce(
! CHECK-NEXT: br label %reduce.finalize
@@ -182,37 +182,37 @@ subroutine worst_case(a, b, c, d)
! CHECK: omp.reduction.cleanup: ; preds = %.fini
! [null check]
-! CHECK: br i1 %{{.*}}, label %omp.reduction.cleanup42, label %omp.reduction.cleanup43
+! CHECK: br i1 %{{.*}}, label %omp.reduction.cleanup58, label %omp.reduction.cleanup59
-! CHECK: omp.reduction.cleanup43: ; preds = %omp.reduction.cleanup42, %omp.reduction.cleanup
-! CHECK-NEXT: br label %omp.region.cont41
+! CHECK: omp.reduction.cleanup59: ; preds = %omp.reduction.cleanup58, %omp.reduction.cleanup
+! CHECK-NEXT: br label %omp.region.cont57
-! CHECK: omp.region.cont41: ; preds = %omp.reduction.cleanup43
+! CHECK: omp.region.cont57: ; preds = %omp.reduction.cleanup59
! CHECK-NEXT: %{{.*}} = load ptr, ptr
-! CHECK-NEXT: br label %omp.reduction.cleanup45
+! CHECK-NEXT: br label %omp.reduction.cleanup61
-! CHECK: omp.reduction.cleanup45: ; preds = %omp.region.cont41
+! CHECK: omp.reduction.cleanup61: ; preds = %omp.region.cont57
! [null check]
-! CHECK: br i1 %{{.*}}, label %omp.reduction.cleanup46, label %omp.reduction.cleanup47
+! CHECK: br i1 %{{.*}}, label %omp.reduction.cleanup62, label %omp.reduction.cleanup63
-! CHECK: omp.par.region30: ; preds = %omp.par.region29
+! CHECK: omp.par.region46: ; preds = %omp.par.region45
! CHECK-NEXT: call void @_FortranAStopStatement
-! CHECK: omp.reduction.neutral25: ; preds = %omp.reduction.neutral24
+! CHECK: omp.reduction.neutral41: ; preds = %omp.reduction.neutral40
! [source length was zero: finish initializing array]
-! CHECK: br label %omp.reduction.neutral27
+! CHECK: br label %omp.reduction.neutral43
-! CHECK: omp.reduction.neutral20: ; preds = %omp.reduction.neutral
+! CHECK: omp.reduction.neutral36: ; preds = %omp.reduction.neutral
! [source length was zero: finish initializing array]
-! CHECK: br label %omp.reduction.neutral22
+! CHECK: br label %omp.reduction.neutral38
-! CHECK: omp.private.copy17: ; preds = %omp.private.copy16
+! CHECK: omp.private.copy33: ; preds = %omp.private.copy32, %omp.private.copy29
! [source length was non-zero: call assign runtime]
-! CHECK: br label %omp.private.copy18
+! CHECK: br label %omp.private.copy34
-! CHECK: omp.private.copy13: ; preds = %omp.private.copy12
+! CHECK: omp.private.copy21: ; preds = %omp.private.copy20, %omp.private.copy17
! [source length was non-zero: call assign runtime]
-! CHECK: br label %omp.private.copy14
+! CHECK: br label %omp.private.copy22
! CHECK: omp.private.init8: ; preds = %omp.private.init7
! [var extent was non-zero: malloc a private array]
@@ -222,5 +222,5 @@ subroutine worst_case(a, b, c, d)
! [var extent was non-zero: malloc a private array]
! CHECK: br label %omp.private.init5
-! CHECK: omp.par.exit.exitStub: ; preds = %omp.region.cont51
+! CHECK: omp.par.exit.exitStub: ; preds = %omp.region.cont67
! CHECK-NEXT: ret void
>From da4182dd8076cf9a3e373744a4f95740f36df0ac Mon Sep 17 00:00:00 2001
From: Pranav Bhandarkar <pranav.bhandarkar at amd.com>
Date: Thu, 16 Jul 2026 23:51:53 -0500
Subject: [PATCH 5/5] [flang] Fix alias analysis for POINTER in omp.private
copy regions
The alias analysis was incorrectly returning NoAlias for all omp.private
copy region arguments, assuming fresh allocation. This is correct for
ALLOCATABLE variables but wrong for POINTER variables.
For POINTER + FIRSTPRIVATE:
- The descriptor is copied (private)
- But pointer association is preserved
- Both descriptors point to the SAME target data
- Therefore: the data ALIASES (should return MayAlias)
For ALLOCATABLE + FIRSTPRIVATE:
- Fresh memory is allocated
- No aliasing occurs (NoAlias is correct)
This fix adds a check to distinguish between POINTER and ALLOCATABLE
types in omp.private copy regions, using BaseBoxType::isPointer().
Co-Authored-By: Claude Sonnet 4 <noreply at anthropic.com>
---
.../lib/Optimizer/Analysis/AliasAnalysis.cpp | 20 +++++++++++
.../alias-analysis-omp-private-pointer.mlir | 34 +++++++++++++++++++
2 files changed, 54 insertions(+)
create mode 100644 flang/test/Analysis/AliasAnalysis/alias-analysis-omp-private-pointer.mlir
diff --git a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
index 8bd99fd037735..4322e9ebcd0d1 100644
--- a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
+++ b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
@@ -697,6 +697,26 @@ AliasResult AliasAnalysis::alias(Source lhsSrc, Source rhsSrc, mlir::Value lhs,
auto privArg = privateOp.getCopyPrivateArg();
if ((lhsArg == moldArg && rhsArg == privArg) ||
(lhsArg == privArg && rhsArg == moldArg)) {
+ // Check if this is a POINTER type.
+ // For POINTER + FIRSTPRIVATE: the descriptor is copied (private)
+ // but pointer association is preserved, so the DATA they point to
+ // is the same → ALIASING.
+ // For ALLOCATABLE: fresh memory is allocated → NO ALIASING.
+ auto isPointerType = [](mlir::Type ty) -> bool {
+ ty = fir::unwrapRefType(ty);
+ if (auto boxTy = mlir::dyn_cast<fir::BaseBoxType>(ty))
+ return boxTy.isPointer();
+ return false;
+ };
+
+ if (isPointerType(lhsArg.getType()) ||
+ isPointerType(rhsArg.getType())) {
+ LLVM_DEBUG(llvm::dbgs()
+ << " may alias: omp.private POINTER preserves "
+ << "association (data aliases)\n");
+ return AliasResult::MayAlias;
+ }
+
LLVM_DEBUG(llvm::dbgs()
<< " no alias: omp.private copy region arguments "
<< "(mold vs private)\n");
diff --git a/flang/test/Analysis/AliasAnalysis/alias-analysis-omp-private-pointer.mlir b/flang/test/Analysis/AliasAnalysis/alias-analysis-omp-private-pointer.mlir
new file mode 100644
index 0000000000000..5a3d4f24e3da4
--- /dev/null
+++ b/flang/test/Analysis/AliasAnalysis/alias-analysis-omp-private-pointer.mlir
@@ -0,0 +1,34 @@
+// Test alias analysis for POINTER + FIRSTPRIVATE in omp.private copy region.
+// For POINTER variables, the descriptor is copied but pointer association
+// is preserved. Both the mold descriptor and private descriptor point to
+// the SAME target data, therefore they ALIAS.
+//
+// RUN: fir-opt %s -pass-pipeline='builtin.module(test-fir-alias-analysis)' -split-input-file --mlir-disable-threading 2>&1 | FileCheck %s
+
+// CHECK-LABEL: Testing : <<NULL ATTRIBUTE>>
+// CHECK: mold_data#0 <-> priv_data#0: MayAlias
+
+// This test verifies that alias analysis correctly returns MayAlias for POINTER
+// variables in omp.private copy regions. For POINTER, the descriptor is copied
+// but pointer association is preserved, so both descriptors point to the SAME
+// target data.
+
+omp.private {type = firstprivate} @ptr_privatizer : !fir.box<!fir.ptr<!fir.array<8xi32>>> init {
+^bb0(%arg0: !fir.ref<!fir.box<!fir.ptr<!fir.array<8xi32>>>>, %arg1: !fir.ref<!fir.box<!fir.ptr<!fir.array<8xi32>>>>):
+ %c8 = arith.constant 8 : index
+ %c1 = arith.constant 1 : index
+ %0 = fir.zero_bits !fir.ptr<!fir.array<8xi32>>
+ %1 = fir.shape %c8 : (index) -> !fir.shape<1>
+ %2 = fir.embox %0(%1) : (!fir.ptr<!fir.array<8xi32>>, !fir.shape<1>) -> !fir.box<!fir.ptr<!fir.array<8xi32>>>
+ fir.store %2 to %arg1 : !fir.ref<!fir.box<!fir.ptr<!fir.array<8xi32>>>>
+ omp.yield(%arg1 : !fir.ref<!fir.box<!fir.ptr<!fir.array<8xi32>>>>)
+} copy {
+^bb0(%arg0: !fir.ref<!fir.box<!fir.ptr<!fir.array<8xi32>>>>, %arg1: !fir.ref<!fir.box<!fir.ptr<!fir.array<8xi32>>>>):
+ // For POINTER: the descriptor is copied, but pointer association is preserved.
+ // Load both the mold (%arg0) and private (%arg1) to test alias analysis.
+ // Both descriptors point to THE SAME target data => should ALIAS.
+ %mold_desc = fir.load %arg0 {test.ptr = "mold_data"} : !fir.ref<!fir.box<!fir.ptr<!fir.array<8xi32>>>>
+ fir.store %mold_desc to %arg1 : !fir.ref<!fir.box<!fir.ptr<!fir.array<8xi32>>>>
+ %priv_desc = fir.load %arg1 {test.ptr = "priv_data"} : !fir.ref<!fir.box<!fir.ptr<!fir.array<8xi32>>>>
+ omp.yield(%arg1 : !fir.ref<!fir.box<!fir.ptr<!fir.array<8xi32>>>>)
+}
More information about the flang-commits
mailing list