Skip to content

[flang] AliasAnalysis: More formally define and distinguish between data and non-data #91020

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 16, 2024
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
95 changes: 90 additions & 5 deletions flang/include/flang/Optimizer/Analysis/AliasAnalysis.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,6 @@ struct AliasAnalysis {
/// Represents memory allocated outside of a function
/// and passed to the function via host association tuple.
HostAssoc,
/// Represents direct memory access whose source cannot be further
/// determined
Direct,
/// Represents memory allocated by unknown means and
/// with the memory address defined by a memory reading
/// operation (e.g. fir::LoadOp).
Expand All @@ -50,12 +47,85 @@ struct AliasAnalysis {
/// Attributes of the memory source object.
ENUM_CLASS(Attribute, Target, Pointer, IntentIn);

// See
// https://discourse.llvm.org/t/rfc-distinguish-between-data-and-non-data-in-fir-alias-analysis/78759/1
//
// It is possible, while following the source of a memory reference through
// the use-def chain, to arrive at the same origin, even though the starting
// points were known to not alias.
//
// clang-format off
// Example:
// ------------------- test.f90 --------------------
// module top
// real, pointer :: a(:)
// end module
//
// subroutine test()
// use top
// a(1) = 1
// end subroutine
// -------------------------------------------------
//
// flang-new -fc1 -emit-fir test.f90 -o test.fir
//
// ------------------- test.fir --------------------
// fir.global @_QMtopEa : !fir.box<!fir.ptr<!fir.array<?xf32>>>
//
// func.func @_QPtest() {
// %c1 = arith.constant 1 : index
// %cst = arith.constant 1.000000e+00 : f32
// %0 = fir.address_of(@_QMtopEa) : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>
// %1 = fir.declare %0 {fortran_attrs = #fir.var_attrs<pointer>, uniq_name = "_QMtopEa"} : (!fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>) -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>
// %2 = fir.load %1 : !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>
// ...
// %5 = fir.array_coor %2 %c1 : (!fir.box<!fir.ptr<!fir.array<?xf32>>>, !fir.shift<1>, index) -> !fir.ref<f32>
// fir.store %cst to %5 : !fir.ref<f32>
// return
// }
// -------------------------------------------------
//
// With high level operations, such as fir.array_coor, it is possible to
// reach into the data wrapped by the box (the descriptor). Therefore when
// asking about the memory source of %5, we are really asking about the
// source of the data of box %2.
//
// When asking about the source of %0 which is the address of the box, we
// reach the same source as in the first case: the global @_QMtopEa. Yet one
// source refers to the data while the other refers to the address of the box
// itself.
//
// To distinguish between the two, the isData flag has been added, whereby
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// To distinguish between the two, the isData flag has been added, whereby
// The isData flag distinguishes between the two, whereby

// data is defined as any memory reference that is not a box reference.
// Additionally, because it is relied on in HLFIR lowering, we allow querying
// on a box SSA value, which is interpreted as querying on its data.
//
// So in the above example, !fir.ref<f32> and !fir.box<!fir.ptr<!fir.array<?xf32>>> is data,
// while !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>> is not data.

// This also applies to function arguments. In the example below, %arg0
// is data, %arg1 is not data but a load of %arg1 is.
//
// func.func @_QFPtest2(%arg0: !fir.ref<f32>, %arg1: !fir.ref<!fir.box<!fir.ptr<f32>>> ) {
// %0 = fir.load %arg1 : !fir.ref<!fir.box<!fir.ptr<f32>>>
// ... }
//
// clang-format on

struct Source {
using SourceUnion = llvm::PointerUnion<mlir::SymbolRefAttr, mlir::Value>;
using Attributes = Fortran::common::EnumSet<Attribute, Attribute_enumSize>;

/// Source definition of a value.
SourceUnion u;
struct SourceOrigin {
/// Source definition of a value.
SourceUnion u;

/// Whether the source was reached following data or box reference
bool isData{false};
};

SourceOrigin origin;

/// Kind of the memory source.
SourceKind kind;
/// Value type of the source definition.
Expand All @@ -77,6 +147,12 @@ struct AliasAnalysis {
/// attribute.
bool isRecordWithPointerComponent() const;

bool isDummyArgument() const;
bool isData() const;
bool isBoxData() const;

mlir::Type getType() const;

/// Return true, if `ty` is a reference type to a boxed
/// POINTER object or a raw fir::PointerType.
static bool isPointerReference(mlir::Type ty);
Expand All @@ -95,6 +171,15 @@ struct AliasAnalysis {
Source getSource(mlir::Value);
};

inline bool operator==(const AliasAnalysis::Source::SourceOrigin &lhs,
const AliasAnalysis::Source::SourceOrigin &rhs) {
return lhs.u == rhs.u && lhs.isData == rhs.isData;
}
inline bool operator!=(const AliasAnalysis::Source::SourceOrigin &lhs,
const AliasAnalysis::Source::SourceOrigin &rhs) {
return !(lhs == rhs);
}

inline llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
const AliasAnalysis::Source &op) {
op.print(os);
Expand Down
Loading