Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 35 additions & 11 deletions flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ static bool hasGlobalOpTargetAttr(mlir::Value v, fir::AddrOfOp op) {
v, fir::GlobalOp::getTargetAttrName(globalOpName));
}

mlir::Value getOriginalDef(mlir::Value v) {
static mlir::Value getOriginalDef(mlir::Value v) {
mlir::Operation *defOp;
bool breakFromLoop = false;
while (!breakFromLoop && (defOp = v.getDefiningOp())) {
Expand Down Expand Up @@ -578,16 +578,6 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
breakFromLoop = true;
})
.Case<fir::LoadOp>([&](auto op) {
// If the load is from a leaf source, return the leaf. Do not track
// through indirections otherwise.
// TODO: Add support to fir.alloca and fir.allocmem
auto def = getOriginalDef(op.getMemref());
if (isDummyArgument(def) ||
def.template getDefiningOp<fir::AddrOfOp>()) {
v = def;
defOp = v.getDefiningOp();
return;
}
// If load is inside target and it points to mapped item,
// continue tracking.
Operation *loadMemrefOp = op.getMemref().getDefiningOp();
Expand All @@ -600,6 +590,40 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
defOp = v.getDefiningOp();
return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any idea why the OpenMP case should not follow the same logic and continue the walk?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is why I added @DominikAdamski to the review. To show that we want to set a better precedent for loads and see if maybe something similar can be done for Omp. I did not quite follow the need for it.

}

// If we are loading a box reference, but following the data,
// we gather the attributes of the box to populate the source
// and stop tracking.
if (auto boxTy = mlir::dyn_cast<fir::BaseBoxType>(ty);
boxTy && followingData) {
Comment on lines +597 to +598
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is a goal of this new condition to ensure cases like fir.alloca !fir.ptr<T> and !fir.ref<!fir.ptr<T>> will never be handled by this code? Thus, T3 and T4 will stay MayAlias, even after we extend this for alloca? If so, that works for my current use cases.

For now (before we extend for alloca), is this PR intended to be NFC?

Copy link
Contributor Author

@Renaud-K Renaud-K Feb 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does do that but it is not really the intention. The functionality should remain vastly the same, so maybe NFCI. Before and after, the load of a fir.alloca will be a SourceKind::Indirect. If we are to extend data vs non-data, we will, it is just code. What is emerging from this change and the fact that everything still works with it, is a new idea for me: it could make sense to have a SourceKind::Emboxed memory source in the future. But this is a topic for a different discussion.


if (mlir::isa<fir::PointerType>(boxTy.getEleTy()))
attributes.set(Attribute::Pointer);

auto def = getOriginalDef(op.getMemref());
if (auto addrOfOp = def.template getDefiningOp<fir::AddrOfOp>()) {
global = addrOfOp.getSymbol();

if (hasGlobalOpTargetAttr(def, addrOfOp))
attributes.set(Attribute::Target);

type = SourceKind::Global;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't here other cases where the Target attribute should be set (local/dummy allocatables with TARGET attribute for instance)?

Why not calling getSource on op.getMemref() and propagating the TARGET/POINTER attribute/whatever is relevant to propagate from the variable containing the address to the address?

Copy link
Contributor Author

@Renaud-K Renaud-K Feb 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plan was to be doing this in getOriginalDef as in #117785 which has the tests that expose the need for it.
I could do it here, if you have concerns that we might be losing something.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though, I have added a test that shows that we are reading the target attribute properly. It's only when we add support for local boxes that the attribute needs to be extracted from fir.declare. This is coming.


// TODO: Add support to fir.alloca and fir.allocmem
// if (auto allocOp = def.template getDefiningOp<fir::AllocaOp>()) {
// ...
// }

if (isDummyArgument(def)) {
defOp = nullptr;
v = def;
}

breakFromLoop = true;
return;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just noticed that we now return here without setting type = SourceKind::Indirect in some cases when we don't have a dummy arg or AddrOfOp. As a result, some cases that used to be Indirect are now Unknown. That seems fine for AliasAnalysis::alias, whose logic treats them equivalently. Is this change intentional?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it was not. Though, offline @jeanPerier has been keen on having getSource replace getOriginalDef. Maybe we can fix it then. #126156 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I am afraid we will end-up duplicating a lot of logic to walk and gather information, and that people will update getSource without getOriginalDef.

I think it would be more powerfull/generic to call getSource on the memref indirection and to propagate the info from the memref source info to the address being analyzed.

For instance, if the memref is a POINTER, technically the loaded address is not a pointer, it is a TARGET. If we cared about OPTIONAL, this aspect would not propagate from the memref to the loaded address, but things like VOLATILE and ASYNCHRONOUS probably would. TARGET propagates (an allocatable component of a deriver type that is TARGET can be taken by a pointer).... INTENT propagates. I believe Fortran committee wants to introduce a way to specify data vs descriptor "constness" (since INTENT always apply to both), and whatever attribute they come up with would not propagate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say, let's proceed with #117785 which is now approved. Then revisit this with getSource.

}

// No further tracking for addresses loaded from memory for now.
type = SourceKind::Indirect;
breakFromLoop = true;
Expand Down
8 changes: 5 additions & 3 deletions flang/test/Analysis/AliasAnalysis/alias-analysis-2.fir
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,15 @@
// CHECK-DAG: arg2.load#0 <-> arg2.addr#0: MustAlias
// CHECK-DAG: boxp1.addr#0 <-> arg2.addr#0: MayAlias

// TODO: Can the address in a pointer alias the address of a pointer, even when the
// TODO: Can the address in a pointer alias the address of a pointer, when the
// pointer has no box. Should this be NoAlias?
// T3: CHECK-DAG: p1.addr#0 <-> p1.tgt#0: MayAlias
// T3 from <https://github.com/llvm/llvm-project/pull/117785#discussion_r1924348480>.
// CHECK-DAG: p1.addr#0 <-> p1.tgt#0: MayAlias

// The addresses stored in two different pointers can alias, even if one has no
// box. In this program, they happen to be the same address.
// T4: CHECK-DAG: p1.tgt#0 <-> boxp1.addr#0: MayAlias
// T4:
// CHECK-DAG: p1.tgt#0 <-> boxp1.addr#0: MayAlias

func.func @_QFPtest(%arg0: !fir.ref<f32> {fir.bindc_name = "v1", fir.target}, %arg1: !fir.ref<f32> {fir.bindc_name = "v2", fir.target}, %arg2: !fir.ref<!fir.box<!fir.ptr<f32>>> ) attributes {test.ptr = "func"} {

Expand Down
82 changes: 82 additions & 0 deletions flang/test/Analysis/AliasAnalysis/alias-analysis-target.fir
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// RUN: fir-opt %s -pass-pipeline='builtin.module(func.func(test-fir-alias-analysis))' 2>&1 | FileCheck %s

// The test was obtained from
// bbc test.f90 -emit-fir
// module mod
// real, pointer :: p0
// real, allocatable :: alloc
// real, allocatable, target :: t_alloc
// real, target :: t
// real :: v
// end module
//
// subroutine test(n)
// use mod
// integer :: n
// real r1
// p0 => t_alloc
// v = alloc
// r1 = p0
// end subroutine test

// Checking that aliasing can only happen with an entity with the target attribute
//
// CHECK-DAG: r1#0 <-> t_alloc#0: NoAlias
// CHECK-DAG: r1#0 <-> alloc#0: NoAlias
// CHECK-DAG: t_alloc#0 <-> alloc#0: NoAlias
// CHECK-DAG: r1#0 <-> p0.ptr#0: NoAlias
// CHECK-DAG: t_alloc#0 <-> p0.ptr#0: MayAlias
// CHECK-DAG: alloc#0 <-> p0.ptr#0: NoAlias

fir.global @_QMmodEalloc : !fir.box<!fir.heap<f32>> {
%0 = fir.zero_bits !fir.heap<f32>
%1 = fir.embox %0 : (!fir.heap<f32>) -> !fir.box<!fir.heap<f32>>
fir.has_value %1 : !fir.box<!fir.heap<f32>>
}
fir.global @_QMmodEp0 : !fir.box<!fir.ptr<f32>> {
%0 = fir.zero_bits !fir.ptr<f32>
%1 = fir.embox %0 : (!fir.ptr<f32>) -> !fir.box<!fir.ptr<f32>>
fir.has_value %1 : !fir.box<!fir.ptr<f32>>
}
fir.global @_QMmodEt target : f32 {
%0 = fir.zero_bits f32
fir.has_value %0 : f32
}
fir.global @_QMmodEt_alloc target : !fir.box<!fir.heap<f32>> {
%0 = fir.zero_bits !fir.heap<f32>
%1 = fir.embox %0 : (!fir.heap<f32>) -> !fir.box<!fir.heap<f32>>
fir.has_value %1 : !fir.box<!fir.heap<f32>>
}
fir.global @_QMmodEv : f32 {
%0 = fir.zero_bits f32
fir.has_value %0 : f32
}
func.func @_QPtest(%arg0: !fir.ref<i32> {fir.bindc_name = "n"}) {
%0 = fir.dummy_scope : !fir.dscope
%1 = fir.address_of(@_QMmodEalloc) : !fir.ref<!fir.box<!fir.heap<f32>>>
%2 = fir.declare %1 {fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "_QMmodEalloc"} : (!fir.ref<!fir.box<!fir.heap<f32>>>) -> !fir.ref<!fir.box<!fir.heap<f32>>>
%3 = fir.declare %arg0 dummy_scope %0 {uniq_name = "_QFtestEn"} : (!fir.ref<i32>, !fir.dscope) -> !fir.ref<i32>
%4 = fir.address_of(@_QMmodEp0) : !fir.ref<!fir.box<!fir.ptr<f32>>>
%5 = fir.declare %4 {fortran_attrs = #fir.var_attrs<pointer>, uniq_name = "_QMmodEp0"} : (!fir.ref<!fir.box<!fir.ptr<f32>>>) -> !fir.ref<!fir.box<!fir.ptr<f32>>>
%6 = fir.alloca f32 {bindc_name = "r1", uniq_name = "_QFtestEr1"}
%7 = fir.declare %6 {test.ptr="r1", uniq_name = "_QFtestEr1"} : (!fir.ref<f32>) -> !fir.ref<f32>
%8 = fir.address_of(@_QMmodEt) : !fir.ref<f32>
%9 = fir.declare %8 {fortran_attrs = #fir.var_attrs<target>, uniq_name = "_QMmodEt"} : (!fir.ref<f32>) -> !fir.ref<f32>
%10 = fir.address_of(@_QMmodEt_alloc) : !fir.ref<!fir.box<!fir.heap<f32>>>
%11 = fir.declare %10 {fortran_attrs = #fir.var_attrs<allocatable, target>, uniq_name = "_QMmodEt_alloc"} : (!fir.ref<!fir.box<!fir.heap<f32>>>) -> !fir.ref<!fir.box<!fir.heap<f32>>>
%12 = fir.address_of(@_QMmodEv) : !fir.ref<f32>
%13 = fir.declare %12 {uniq_name = "_QMmodEv"} : (!fir.ref<f32>) -> !fir.ref<f32>
%14 = fir.load %11 : !fir.ref<!fir.box<!fir.heap<f32>>>
%15 = fir.box_addr %14 {test.ptr="t_alloc"}: (!fir.box<!fir.heap<f32>>) -> !fir.heap<f32>
%16 = fir.embox %15 : (!fir.heap<f32>) -> !fir.box<!fir.ptr<f32>>
fir.store %16 to %5 : !fir.ref<!fir.box<!fir.ptr<f32>>>
%17 = fir.load %2 : !fir.ref<!fir.box<!fir.heap<f32>>>
%18 = fir.box_addr %17 {test.ptr="alloc"} : (!fir.box<!fir.heap<f32>>) -> !fir.heap<f32>
%19 = fir.load %18 : !fir.heap<f32>
fir.store %19 to %13 : !fir.ref<f32>
%20 = fir.load %5 : !fir.ref<!fir.box<!fir.ptr<f32>>>
%21 = fir.box_addr %20 {test.ptr="p0.ptr"} : (!fir.box<!fir.ptr<f32>>) -> !fir.ptr<f32>
%22 = fir.load %21 : !fir.ptr<f32>
fir.store %22 to %7 : !fir.ref<f32>
return
}