blob: ed1101dc5e8d8885622fa5eb19ef8ec035c07e74 [file] [log] [blame]
//===- AliasAnalysis.cpp - Alias Analysis for FIR ------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "flang/Optimizer/Analysis/AliasAnalysis.h"
#include "flang/Optimizer/Dialect/FIROps.h"
#include "flang/Optimizer/Dialect/FIROpsSupport.h"
#include "flang/Optimizer/Dialect/FIRType.h"
#include "flang/Optimizer/Dialect/FortranVariableInterface.h"
#include "flang/Optimizer/HLFIR/HLFIROps.h"
#include "mlir/Analysis/AliasAnalysis.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/Value.h"
#include "mlir/Interfaces/SideEffectInterfaces.h"
#include "llvm/ADT/TypeSwitch.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Debug.h"
using namespace mlir;
#define DEBUG_TYPE "fir-alias-analysis"
//===----------------------------------------------------------------------===//
// AliasAnalysis: alias
//===----------------------------------------------------------------------===//
static bool isDummyArgument(mlir::Value v) {
auto blockArg{mlir::dyn_cast<mlir::BlockArgument>(v)};
if (!blockArg)
return false;
mlir::Block *owner = blockArg.getOwner();
if (!owner->isEntryBlock() ||
!mlir::isa<mlir::FunctionOpInterface>(owner->getParentOp()))
return false;
return true;
}
/// Temporary function to skip through all the no op operations
/// TODO: Generalize support of fir.load
static mlir::Value getOriginalDef(mlir::Value v) {
mlir::Operation *defOp;
bool breakFromLoop = false;
while (!breakFromLoop && (defOp = v.getDefiningOp())) {
llvm::TypeSwitch<Operation *>(defOp)
.Case<fir::ConvertOp>([&](fir::ConvertOp op) { v = op.getValue(); })
.Case<fir::DeclareOp, hlfir::DeclareOp>(
[&](auto op) { v = op.getMemref(); })
.Default([&](auto op) { breakFromLoop = true; });
}
return v;
}
namespace fir {
void AliasAnalysis::Source::print(llvm::raw_ostream &os) const {
if (auto v = llvm::dyn_cast<mlir::Value>(u))
os << v;
else if (auto gbl = llvm::dyn_cast<mlir::SymbolRefAttr>(u))
os << gbl;
os << " SourceKind: " << EnumToString(kind);
os << " Type: " << valueType << " ";
attributes.Dump(os, EnumToString);
}
bool AliasAnalysis::Source::isPointerReference(mlir::Type ty) {
auto eleTy = fir::dyn_cast_ptrEleTy(ty);
if (!eleTy)
return false;
return fir::isPointerType(eleTy) || mlir::isa<fir::PointerType>(eleTy);
}
bool AliasAnalysis::Source::isTargetOrPointer() const {
return attributes.test(Attribute::Pointer) ||
attributes.test(Attribute::Target);
}
bool AliasAnalysis::Source::isRecordWithPointerComponent() const {
auto eleTy = fir::dyn_cast_ptrEleTy(valueType);
if (!eleTy)
return false;
// TO DO: Look for pointer components
return mlir::isa<fir::RecordType>(eleTy);
}
AliasResult AliasAnalysis::alias(Value lhs, Value rhs) {
auto lhsSrc = getSource(lhs);
auto rhsSrc = getSource(rhs);
bool approximateSource = lhsSrc.approximateSource || rhsSrc.approximateSource;
LLVM_DEBUG(llvm::dbgs() << "AliasAnalysis::alias\n";
llvm::dbgs() << " lhs: " << lhs << "\n";
llvm::dbgs() << " lhsSrc: " << lhsSrc << "\n";
llvm::dbgs() << " rhs: " << rhs << "\n";
llvm::dbgs() << " rhsSrc: " << rhsSrc << "\n";
llvm::dbgs() << "\n";);
// Indirect case currently not handled. Conservatively assume
// it aliases with everything
if (lhsSrc.kind > SourceKind::Direct || rhsSrc.kind > SourceKind::Direct) {
return AliasResult::MayAlias;
}
// SourceKind::Direct is set for the addresses wrapped in a global boxes.
// ie: fir.global @_QMpointersEp : !fir.box<!fir.ptr<f32>>
// Though nothing is known about them, they would only alias with targets or
// pointers
bool directSourceToNonTargetOrPointer = false;
if (lhsSrc.u != rhsSrc.u || lhsSrc.kind != rhsSrc.kind) {
if ((lhsSrc.kind == SourceKind::Direct && !rhsSrc.isTargetOrPointer()) ||
(rhsSrc.kind == SourceKind::Direct && !lhsSrc.isTargetOrPointer()))
directSourceToNonTargetOrPointer = true;
}
if (lhsSrc.kind == SourceKind::Direct ||
rhsSrc.kind == SourceKind::Direct) {
if (!directSourceToNonTargetOrPointer)
return AliasResult::MayAlias;
}
if (lhsSrc.kind == rhsSrc.kind) {
if (lhsSrc.u == rhsSrc.u) {
if (approximateSource)
return AliasResult::MayAlias;
return AliasResult::MustAlias;
}
// Two host associated accesses may overlap due to an equivalence.
if (lhsSrc.kind == SourceKind::HostAssoc)
return AliasResult::MayAlias;
// Allocate and global memory address cannot physically alias
if (lhsSrc.kind == SourceKind::Allocate ||
lhsSrc.kind == SourceKind::Global)
return AliasResult::NoAlias;
// Dummy TARGET/POINTER arguments may alias.
if (lhsSrc.isTargetOrPointer() && rhsSrc.isTargetOrPointer())
return AliasResult::MayAlias;
// Box for POINTER component inside an object of a derived type
// may alias box of a POINTER object, as well as boxes for POINTER
// components inside two objects of derived types may alias.
if ((lhsSrc.isRecordWithPointerComponent() && rhsSrc.isTargetOrPointer()) ||
(rhsSrc.isRecordWithPointerComponent() && lhsSrc.isTargetOrPointer()) ||
(lhsSrc.isRecordWithPointerComponent() &&
rhsSrc.isRecordWithPointerComponent()))
return AliasResult::MayAlias;
return AliasResult::NoAlias;
}
assert(lhsSrc.kind != rhsSrc.kind && "memory source kinds must be different");
Source *src1, *src2;
if (lhsSrc.kind < rhsSrc.kind) {
src1 = &lhsSrc;
src2 = &rhsSrc;
} else {
src1 = &rhsSrc;
src2 = &lhsSrc;
}
if (src1->kind == SourceKind::Argument &&
src2->kind == SourceKind::HostAssoc) {
// Treat the host entity as TARGET for the purpose of disambiguating
// it with a dummy access. It is required for this particular case:
// subroutine test
// integer :: x(10)
// call inner(x)
// contains
// subroutine inner(y)
// integer, target :: y(:)
// x(1) = y(1)
// end subroutine inner
// end subroutine test
//
// F18 15.5.2.13 (4) (b) allows 'x' and 'y' to address the same object.
// 'y' has an explicit TARGET attribute, but 'x' has neither TARGET
// nor POINTER.
src2->attributes.set(Attribute::Target);
}
// Dummy TARGET/POINTER argument may alias with a global TARGET/POINTER.
if (src1->isTargetOrPointer() && src2->isTargetOrPointer())
return AliasResult::MayAlias;
// Box for POINTER component inside an object of a derived type
// may alias box of a POINTER object, as well as boxes for POINTER
// components inside two objects of derived types may alias.
if ((src1->isRecordWithPointerComponent() && src2->isTargetOrPointer()) ||
(src2->isRecordWithPointerComponent() && src1->isTargetOrPointer()) ||
(src1->isRecordWithPointerComponent() &&
src2->isRecordWithPointerComponent()))
return AliasResult::MayAlias;
return AliasResult::NoAlias;
}
//===----------------------------------------------------------------------===//
// AliasAnalysis: getModRef
//===----------------------------------------------------------------------===//
/// 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)
return ModRefResult::getModAndRef();
// Build a ModRefResult by merging the behavior of the effects of this
// operation.
SmallVector<MemoryEffects::EffectInstance> effects;
interface.getEffects(effects);
ModRefResult result = ModRefResult::getNoModRef();
for (const MemoryEffects::EffectInstance &effect : effects) {
// Check for an alias between the effect and our memory location.
AliasResult aliasResult = AliasResult::MayAlias;
if (Value effectValue = effect.getValue())
aliasResult = alias(effectValue, location);
// If we don't alias, ignore this effect.
if (aliasResult.isNo())
continue;
// Merge in the corresponding mod or ref for this effect.
if (isa<MemoryEffects::Read>(effect.getEffect()))
result = result.merge(ModRefResult::getRef());
else
result = result.merge(ModRefResult::getMod());
if (result.isModAndRef())
break;
}
return result;
}
AliasAnalysis::Source::Attributes
getAttrsFromVariable(fir::FortranVariableOpInterface var) {
AliasAnalysis::Source::Attributes attrs;
if (var.isTarget())
attrs.set(AliasAnalysis::Attribute::Target);
if (var.isPointer())
attrs.set(AliasAnalysis::Attribute::Pointer);
if (var.isIntentIn())
attrs.set(AliasAnalysis::Attribute::IntentIn);
return attrs;
}
AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v) {
auto *defOp = v.getDefiningOp();
SourceKind type{SourceKind::Unknown};
mlir::Type ty;
bool breakFromLoop{false};
bool approximateSource{false};
bool followBoxAddr{mlir::isa<fir::BaseBoxType>(v.getType())};
mlir::SymbolRefAttr global;
Source::Attributes attributes;
while (defOp && !breakFromLoop) {
ty = defOp->getResultTypes()[0];
llvm::TypeSwitch<Operation *>(defOp)
.Case<fir::AllocaOp, fir::AllocMemOp>([&](auto op) {
// Unique memory allocation.
type = SourceKind::Allocate;
breakFromLoop = true;
})
.Case<fir::ConvertOp>([&](auto op) {
// Skip ConvertOp's and track further through the operand.
v = op->getOperand(0);
defOp = v.getDefiningOp();
})
.Case<fir::BoxAddrOp>([&](auto op) {
v = op->getOperand(0);
defOp = v.getDefiningOp();
if (mlir::isa<fir::BaseBoxType>(v.getType()))
followBoxAddr = true;
})
.Case<fir::ArrayCoorOp, fir::CoordinateOp>([&](auto op) {
v = op->getOperand(0);
defOp = v.getDefiningOp();
if (mlir::isa<fir::BaseBoxType>(v.getType()))
followBoxAddr = true;
approximateSource = true;
})
.Case<fir::EmboxOp, fir::ReboxOp>([&](auto op) {
if (followBoxAddr) {
v = op->getOperand(0);
defOp = v.getDefiningOp();
} else
breakFromLoop = true;
})
.Case<fir::LoadOp>([&](auto op) {
if (followBoxAddr && mlir::isa<fir::BaseBoxType>(op.getType())) {
// For now, support the load of an argument or fir.address_of
// TODO: generalize to all operations (in particular fir.alloca and
// fir.allocmem)
auto def = getOriginalDef(op.getMemref());
if (isDummyArgument(def) ||
def.template getDefiningOp<fir::AddrOfOp>()) {
v = def;
defOp = v.getDefiningOp();
return;
}
}
// No further tracking for addresses loaded from memory for now.
type = SourceKind::Indirect;
breakFromLoop = true;
})
.Case<fir::AddrOfOp>([&](auto op) {
// Address of a global scope object.
ty = v.getType();
// When the global is a
// fir.global @_QMpointersEp : !fir.box<!fir.ptr<f32>>
// or
// fir.global @_QMpointersEp : !fir.box<!fir.heap<f32>>
//
// and when following through the wrapped address, capture
// the fact that there is nothing known about it. Therefore setting
// the source to Direct.
//
// When not following the wrapped address, then consider the address
// of the box, which has nothing to do with the wrapped address and
// lies in the global memory space.
if (followBoxAddr &&
mlir::isa<fir::BaseBoxType>(fir::unwrapRefType(ty)))
type = SourceKind::Direct;
else
type = SourceKind::Global;
auto globalOpName = mlir::OperationName(
fir::GlobalOp::getOperationName(), defOp->getContext());
if (fir::valueHasFirAttribute(
v, fir::GlobalOp::getTargetAttrName(globalOpName)))
attributes.set(Attribute::Target);
// TODO: Take followBoxAddr into account when setting the pointer
// attribute
if (Source::isPointerReference(ty))
attributes.set(Attribute::Pointer);
global = llvm::cast<fir::AddrOfOp>(op).getSymbol();
breakFromLoop = true;
})
.Case<hlfir::DeclareOp, fir::DeclareOp>([&](auto op) {
auto varIf = llvm::cast<fir::FortranVariableOpInterface>(defOp);
// While going through a declare operation collect
// the variable attributes from it. Right now, some
// of the attributes are duplicated, e.g. a TARGET dummy
// argument has the target attribute both on its declare
// operation and on the entry block argument.
// In case of host associated use, the declare operation
// is the only carrier of the variable attributes,
// so we have to collect them here.
attributes |= getAttrsFromVariable(varIf);
if (varIf.isHostAssoc()) {
// Do not track past such DeclareOp, because it does not
// currently provide any useful information. The host associated
// access will end up dereferencing the host association tuple,
// so we may as well stop right now.
v = defOp->getResult(0);
// TODO: if the host associated variable is a dummy argument
// of the host, I think, we can treat it as SourceKind::Argument
// for the purpose of alias analysis inside the internal procedure.
type = SourceKind::HostAssoc;
breakFromLoop = true;
return;
}
// TODO: Look for the fortran attributes present on the operation
// Track further through the operand
v = op.getMemref();
defOp = v.getDefiningOp();
})
.Case<hlfir::DesignateOp>([&](auto op) {
// Track further through the memory indexed into
// => if the source arrays/structures don't alias then nor do the
// results of hlfir.designate
v = op.getMemref();
defOp = v.getDefiningOp();
// TODO: there will be some cases which provably don't alias if one
// takes into account the component or indices, which are currently
// ignored here - leading to false positives
// because of this limitation, we need to make sure we never return
// MustAlias after going through a designate operation
approximateSource = true;
if (mlir::isa<fir::BaseBoxType>(v.getType()))
followBoxAddr = true;
})
.Default([&](auto op) {
defOp = nullptr;
breakFromLoop = true;
});
}
if (!defOp && type == SourceKind::Unknown)
// Check if the memory source is coming through a dummy argument.
if (isDummyArgument(v)) {
type = SourceKind::Argument;
ty = v.getType();
if (fir::valueHasFirAttribute(v, fir::getTargetAttrName()))
attributes.set(Attribute::Target);
if (Source::isPointerReference(ty))
attributes.set(Attribute::Pointer);
}
if (type == SourceKind::Global || type == SourceKind::Direct)
return {global, type, ty, attributes, approximateSource};
return {v, type, ty, attributes, approximateSource};
}
} // namespace fir