Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
5 changes: 5 additions & 0 deletions flang/include/flang/Lower/AbstractConverter.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class SymMap;
struct SymbolBox;
namespace pft {
struct Variable;
struct FunctionLikeUnit;
}

using SomeExpr = Fortran::evaluate::Expr<Fortran::evaluate::SomeType>;
Expand Down Expand Up @@ -233,6 +234,10 @@ class AbstractConverter {
virtual bool
isRegisteredDummySymbol(Fortran::semantics::SymbolRef symRef) const = 0;

/// Returns the FunctionLikeUnit being lowered, if any.
virtual const Fortran::lower::pft::FunctionLikeUnit *
getCurrentFunctionUnit() const = 0;

//===--------------------------------------------------------------------===//
// Types
//===--------------------------------------------------------------------===//
Expand Down
1 change: 1 addition & 0 deletions flang/include/flang/Lower/PFTBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,7 @@ struct FunctionLikeUnit : public ProgramUnit {
/// Return the host associations for this function like unit. The list of host
/// associations are kept in the host procedure.
HostAssociations &getHostAssoc() { return hostAssociations; }
const HostAssociations &getHostAssoc() const { return hostAssociations; };

LLVM_DUMP_METHOD void dump() const;

Expand Down
11 changes: 8 additions & 3 deletions flang/include/flang/Optimizer/Analysis/AliasAnalysis.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ struct AliasAnalysis {
/// inlining happens an inlined fir.declare of the callee's
/// dummy argument identifies the scope where the source
/// may be treated as a dummy argument.
mlir::Value instantiationPoint;
mlir::Operation *instantiationPoint;

/// Whether the source was reached following data or box reference
bool isData{false};
Expand All @@ -146,6 +146,8 @@ struct AliasAnalysis {
/// Have we lost precision following the source such that
/// even an exact match cannot be MustAlias?
bool approximateSource;
/// Source object is used in an internal procedure via host association.
bool isCapturedInInternalProcedure{false};

/// Print information about the memory source to `os`.
void print(llvm::raw_ostream &os) const;
Expand All @@ -157,6 +159,9 @@ struct AliasAnalysis {
bool isData() const;
bool isBoxData() const;

/// Is this source a variable from the Fortran source?
bool isFortranUserVariable() const;

/// @name Dummy Argument Aliasing
///
/// Check conditions related to dummy argument aliasing.
Expand Down Expand Up @@ -194,11 +199,11 @@ struct AliasAnalysis {
mlir::ModRefResult getModRef(mlir::Operation *op, mlir::Value location);

/// Return the memory source of a value.
/// If getInstantiationPoint is true, the search for the source
/// If getLastInstantiationPoint is true, the search for the source
/// will stop at [hl]fir.declare if it represents a dummy
/// argument declaration (i.e. it has the dummy_scope operand).
fir::AliasAnalysis::Source getSource(mlir::Value,
bool getInstantiationPoint = false);
bool getLastInstantiationPoint = false);

private:
/// Return true, if `ty` is a reference type to an object of derived type
Expand Down
5 changes: 4 additions & 1 deletion flang/include/flang/Optimizer/Dialect/FIRAttr.td
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,17 @@ def FIRpointer : I32BitEnumAttrCaseBit<"pointer", 9>;
def FIRtarget : I32BitEnumAttrCaseBit<"target", 10>;
def FIRvalue : I32BitEnumAttrCaseBit<"value", 11>;
def FIRvolatile : I32BitEnumAttrCaseBit<"fortran_volatile", 12, "volatile">;
// Used inside internal procedure to flag variables host associated from parent procedure.
def FIRHostAssoc : I32BitEnumAttrCaseBit<"host_assoc", 13>;
// Used inside parent procedure to flag variables host associated in internal procedure.
def FIRInternalAssoc : I32BitEnumAttrCaseBit<"internal_assoc", 14>;

def fir_FortranVariableFlagsEnum : I32BitEnumAttr<
"FortranVariableFlagsEnum",
"Fortran variable attributes",
[FIRnoAttributes, FIRallocatable, FIRasynchronous, FIRbind_c, FIRcontiguous,
FIRintent_in, FIRintent_inout, FIRintent_out, FIRoptional, FIRparameter,
FIRpointer, FIRtarget, FIRvalue, FIRvolatile, FIRHostAssoc]> {
FIRpointer, FIRtarget, FIRvalue, FIRvolatile, FIRHostAssoc, FIRInternalAssoc]> {
let separator = ", ";
let cppNamespace = "::fir";
let printBitEnumPrimaryGroups = 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,13 @@ def fir_FortranVariableOpInterface : OpInterface<"FortranVariableOpInterface"> {
fir::FortranVariableFlagsEnum::target);
}

/// Is this variable captured in an internal procedure via Fortran host association?
bool isCapturedInInternalProcedure() {
auto attrs = getFortranAttrs();
return attrs && bitEnumContainsAny(*attrs,
fir::FortranVariableFlagsEnum::internal_assoc);
}

/// Is this variable a Fortran intent(in)?
bool isIntentIn() {
auto attrs = getFortranAttrs();
Expand Down
12 changes: 12 additions & 0 deletions flang/lib/Lower/Bridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1058,6 +1058,11 @@ class FirConverter : public Fortran::lower::AbstractConverter {
return registeredDummySymbols.contains(sym);
}

const Fortran::lower::pft::FunctionLikeUnit *
getCurrentFunctionUnit() const override final {
return currentFunctionUnit;
}

void registerTypeInfo(mlir::Location loc,
Fortran::lower::SymbolRef typeInfoSym,
const Fortran::semantics::DerivedTypeSpec &typeSpec,
Expand Down Expand Up @@ -5595,6 +5600,7 @@ class FirConverter : public Fortran::lower::AbstractConverter {
/// Lower a procedure (nest).
void lowerFunc(Fortran::lower::pft::FunctionLikeUnit &funit) {
setCurrentPosition(funit.getStartingSourceLoc());
setCurrentFunctionUnit(&funit);
for (int entryIndex = 0, last = funit.entryPointList.size();
entryIndex < last; ++entryIndex) {
funit.setActiveEntry(entryIndex);
Expand All @@ -5604,6 +5610,7 @@ class FirConverter : public Fortran::lower::AbstractConverter {
endNewFunction(funit);
}
funit.setActiveEntry(0);
setCurrentFunctionUnit(nullptr);
for (Fortran::lower::pft::ContainedUnit &unit : funit.containedUnitList)
if (auto *f = std::get_if<Fortran::lower::pft::FunctionLikeUnit>(&unit))
lowerFunc(*f); // internal procedure
Expand Down Expand Up @@ -5967,12 +5974,17 @@ class FirConverter : public Fortran::lower::AbstractConverter {
/// Reset all registered dummy symbols.
void resetRegisteredDummySymbols() { registeredDummySymbols.clear(); }

void setCurrentFunctionUnit(Fortran::lower::pft::FunctionLikeUnit *unit) {
currentFunctionUnit = unit;
}

//===--------------------------------------------------------------------===//

Fortran::lower::LoweringBridge &bridge;
Fortran::evaluate::FoldingContext foldingContext;
fir::FirOpBuilder *builder = nullptr;
Fortran::lower::pft::Evaluation *evalPtr = nullptr;
Fortran::lower::pft::FunctionLikeUnit *currentFunctionUnit = nullptr;
Fortran::lower::SymMap localSymbols;
Fortran::parser::CharBlock currentPosition;
TypeInfoConverter typeInfoConverter;
Expand Down
27 changes: 26 additions & 1 deletion flang/lib/Lower/ConvertVariable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1670,6 +1670,25 @@ cuf::DataAttributeAttr Fortran::lower::translateSymbolCUFDataAttribute(
return cuf::getDataAttribute(mlirContext, cudaAttr);
}

static bool
isCapturedInInternalProcedure(Fortran::lower::AbstractConverter &converter,
const Fortran::semantics::Symbol &sym) {
const Fortran::lower::pft::FunctionLikeUnit *funit =
converter.getCurrentFunctionUnit();
if (!funit || funit->getHostAssoc().empty())
return false;
if (funit->getHostAssoc().isAssociated(sym))
return true;
// Consider that any capture of a variable that is in an equivalence with the
// symbol imply that the storage of the symbol may also be accessed inside
// the internal procedure and flag it as captured.
if (const auto *equivSet = Fortran::semantics::FindEquivalenceSet(sym))
for (const Fortran::semantics::EquivalenceObject &eqObj : *equivSet)
if (funit->getHostAssoc().isAssociated(eqObj.symbol))
return true;
return false;
}

/// Map a symbol to its FIR address and evaluated specification expressions.
/// Not for symbols lowered to fir.box.
/// Will optionally create fir.declare.
Expand Down Expand Up @@ -1705,8 +1724,12 @@ static void genDeclareSymbol(Fortran::lower::AbstractConverter &converter,
if (len)
lenParams.emplace_back(len);
auto name = converter.mangleName(sym);
fir::FortranVariableFlagsEnum extraFlags = {};
if (isCapturedInInternalProcedure(converter, sym))
extraFlags = extraFlags | fir::FortranVariableFlagsEnum::internal_assoc;
fir::FortranVariableFlagsAttr attributes =
Fortran::lower::translateSymbolAttributes(builder.getContext(), sym);
Fortran::lower::translateSymbolAttributes(builder.getContext(), sym,
extraFlags);
cuf::DataAttributeAttr dataAttr =
Fortran::lower::translateSymbolCUFDataAttribute(builder.getContext(),
sym);
Expand Down Expand Up @@ -1793,6 +1816,8 @@ void Fortran::lower::genDeclareSymbol(
!sym.detailsIf<Fortran::semantics::CommonBlockDetails>()) {
fir::FirOpBuilder &builder = converter.getFirOpBuilder();
const mlir::Location loc = genLocation(converter, sym);
if (isCapturedInInternalProcedure(converter, sym))
extraFlags = extraFlags | fir::FortranVariableFlagsEnum::internal_assoc;
// FIXME: Using the ultimate symbol for translating symbol attributes will
// lead to situations where the VOLATILE/ASYNCHRONOUS attributes are not
// propagated to the hlfir.declare (these attributes can be added when
Expand Down
111 changes: 104 additions & 7 deletions flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "flang/Optimizer/Dialect/FIRType.h"
#include "flang/Optimizer/Dialect/FortranVariableInterface.h"
#include "flang/Optimizer/HLFIR/HLFIROps.h"
#include "flang/Optimizer/Support/InternalNames.h"
#include "mlir/Analysis/AliasAnalysis.h"
#include "mlir/Dialect/OpenMP/OpenMPDialect.h"
#include "mlir/Dialect/OpenMP/OpenMPInterfaces.h"
Expand Down Expand Up @@ -96,6 +97,17 @@ bool AliasAnalysis::Source::isBoxData() const {
origin.isData;
}

bool AliasAnalysis::Source::isFortranUserVariable() const {
if (!origin.instantiationPoint)
return false;
return llvm::TypeSwitch<mlir::Operation *, bool>(origin.instantiationPoint)
.template Case<fir::DeclareOp, hlfir::DeclareOp>([&](auto declOp) {
return fir::NameUniquer::deconstruct(declOp.getUniqName()).first ==
fir::NameUniquer::NameKind::VARIABLE;
})
.Default([&](auto op) { return false; });
}

bool AliasAnalysis::Source::mayBeDummyArgOrHostAssoc() const {
return kind != SourceKind::Allocate && kind != SourceKind::Global;
}
Expand Down Expand Up @@ -329,14 +341,92 @@ AliasResult AliasAnalysis::alias(Source lhsSrc, Source rhsSrc, mlir::Value lhs,
// AliasAnalysis: getModRef
//===----------------------------------------------------------------------===//

static bool isSavedLocal(const fir::AliasAnalysis::Source &src) {
if (auto symRef = llvm::dyn_cast<mlir::SymbolRefAttr>(src.origin.u)) {
auto [nameKind, deconstruct] =
fir::NameUniquer::deconstruct(symRef.getLeafReference().getValue());
return nameKind == fir::NameUniquer::NameKind::VARIABLE &&
!deconstruct.procs.empty();
}
return false;
}

static bool isCallToFortranUserProcedure(fir::CallOp call) {
// TODO: indirect calls are excluded by these checks. Maybe some attribute is
// needed to flag user calls in this case.
if (fir::hasBindcAttr(call))
return true;
if (std::optional<mlir::SymbolRefAttr> callee = call.getCallee())
return fir::NameUniquer::deconstruct(callee->getLeafReference().getValue())
.first == fir::NameUniquer::NameKind::PROCEDURE;
return false;
}

static ModRefResult getCallModRef(fir::CallOp call, mlir::Value var) {
// TODO: limit to Fortran functions??
// 1. Detect variables that can be accessed indirectly.
fir::AliasAnalysis aliasAnalysis;
fir::AliasAnalysis::Source varSrc = aliasAnalysis.getSource(var);
// If the variable is not a user variable, we cannot safely assume that
// Fortran semantics apply (e.g., a bare alloca/allocmem result may very well
// be placed in an allocatable/pointer descriptor and escape).

// All the logic bellows are based on Fortran semantics and only holds if this
// is a call to a procedure form the Fortran source and this is a variable
// from the Fortran source. Compiler generated temporaries or functions may
// not adhere to this semantic.
// TODO: add some opt-in or op-out mechanism for compiler generated temps.
// An example of something currently problematic is the allocmem generated for
// ALLOCATE of allocatable target. It currently does not have the target
// attribute, which would lead this analysis to believe it cannot escape.
if (!varSrc.isFortranUserVariable() || !isCallToFortranUserProcedure(call))
return ModRefResult::getModAndRef();
// Pointer and target may have been captured.
if (varSrc.isTargetOrPointer())
return ModRefResult::getModAndRef();
// Host associated variables may be addressed indirectly via an internal
// function call, whether the call is in the parent or an internal procedure.
// Note that the host associated/internal procedure may be referenced
// indirectly inside calls to non internal procedure. This is because internal
// procedures may be captured or passed. As this is tricky to analyze, always
// consider such variables may be accessed in any calls.
if (varSrc.kind == fir::AliasAnalysis::SourceKind::HostAssoc ||
varSrc.isCapturedInInternalProcedure)
return ModRefResult::getModAndRef();
// At that stage, it has been ruled out that local (including the saved ones)
// and dummy cannot be indirectly accessed in the call.
if (varSrc.kind != fir::AliasAnalysis::SourceKind::Allocate &&
!varSrc.isDummyArgument()) {
if (varSrc.kind != fir::AliasAnalysis::SourceKind::Global ||
!isSavedLocal(varSrc))
return ModRefResult::getModAndRef();
}
// 2. Check if the variable is passed via the arguments.
for (auto arg : call.getArgs()) {
if (fir::conformsWithPassByRef(arg.getType()) &&
!aliasAnalysis.alias(arg, var).isNo()) {
// TODO: intent(in) would allow returning Ref here. This can be obtained
// in the func.func attributes for direct calls, but the module lookup is
// linear with the number of MLIR symbols, which would introduce a pseudo
// quadratic behavior num_calls * num_func.
Comment on lines +408 to +411
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe lookups in an mlir::SymbolTable are constant time. Constructing a SymbolTable is linear, but perhaps one could be re-used from a calling context. Or fir::AliasAnalysis could have a LazySymbolTable (AbstractResult.cpp).

It is fine by me to leave this as a TODO in this PR and only attempt this if the optimization turns out to be useful on some real code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fir::AliasAnalysis could have a LazySymbolTable.

Makes some sense to me. The only limitation I see here is that any changes to the ModuleOp symbols (name change/new functions) would not propagate to it, so fir::AliasAnalysis users would have to ensure they do not modify symbols during the lifetime of the AliasAnalysis object (or to somehow update the symbol table too).

I am planning to chase the moduleOp lookups at some point and try to think of a way to keep and maintain symbol tables in the pipeline.

Copy link
Contributor

Choose a reason for hiding this comment

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

That sounds great!

return ModRefResult::getModAndRef();
}
}
// The call cannot access the variable.
return ModRefResult::getNoModRef();
}

/// This is mostly inspired by MLIR::LocalAliasAnalysis with 2 notable
/// differences 1) Regions are not handled here but will be handled by a data
/// flow analysis to come 2) Allocate and Free effects are considered
/// modifying
ModRefResult AliasAnalysis::getModRef(Operation *op, Value location) {
MemoryEffectOpInterface interface = dyn_cast<MemoryEffectOpInterface>(op);
if (!interface)
if (!interface) {
if (auto call = llvm::dyn_cast<fir::CallOp>(op))
return getCallModRef(call, location);
return ModRefResult::getModAndRef();
}

// Build a ModRefResult by merging the behavior of the effects of this
// operation.
Expand Down Expand Up @@ -408,19 +498,20 @@ static Value getPrivateArg(omp::BlockArgOpenMPOpInterface &argIface,
}

AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
bool getInstantiationPoint) {
bool getLastInstantiationPoint) {
auto *defOp = v.getDefiningOp();
SourceKind type{SourceKind::Unknown};
mlir::Type ty;
bool breakFromLoop{false};
bool approximateSource{false};
bool isCapturedInInternalProcedure{false};
bool followBoxData{mlir::isa<fir::BaseBoxType>(v.getType())};
bool isBoxRef{fir::isa_ref_type(v.getType()) &&
mlir::isa<fir::BaseBoxType>(fir::unwrapRefType(v.getType()))};
bool followingData = !isBoxRef;
mlir::SymbolRefAttr global;
Source::Attributes attributes;
mlir::Value instantiationPoint;
mlir::Operation *instantiationPoint{nullptr};
while (defOp && !breakFromLoop) {
ty = defOp->getResultTypes()[0];
llvm::TypeSwitch<Operation *>(defOp)
Expand Down Expand Up @@ -548,6 +639,8 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
// is the only carrier of the variable attributes,
// so we have to collect them here.
attributes |= getAttrsFromVariable(varIf);
isCapturedInInternalProcedure |=
varIf.isCapturedInInternalProcedure();
if (varIf.isHostAssoc()) {
// Do not track past such DeclareOp, because it does not
// currently provide any useful information. The host associated
Expand All @@ -561,10 +654,10 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
breakFromLoop = true;
return;
}
if (getInstantiationPoint) {
if (getLastInstantiationPoint) {
// Fetch only the innermost instantiation point.
if (!instantiationPoint)
instantiationPoint = op->getResult(0);
instantiationPoint = op;

if (op.getDummyScope()) {
// Do not track past DeclareOp that has the dummy_scope
Expand All @@ -575,6 +668,8 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
breakFromLoop = true;
return;
}
} else {
instantiationPoint = op;
}
// TODO: Look for the fortran attributes present on the operation
// Track further through the operand
Expand Down Expand Up @@ -620,13 +715,15 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
type,
ty,
attributes,
approximateSource};
approximateSource,
isCapturedInInternalProcedure};
}
return {{v, instantiationPoint, followingData},
type,
ty,
attributes,
approximateSource};
approximateSource,
isCapturedInInternalProcedure};
}

} // namespace fir
1 change: 1 addition & 0 deletions flang/lib/Optimizer/Analysis/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ add_flang_library(FIRAnalysis

DEPENDS
FIRDialect
FIRSupport
HLFIRDialect
MLIRIR
MLIROpenMPDialect
Expand Down
Loading
Loading