CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
1436 lines
54 KiB
C++
1436 lines
54 KiB
C++
//===-- FIRToMemRef.cpp - Convert FIR loads and stores to MemRef ---------===//
|
||
//
|
||
// 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
|
||
//
|
||
//===----------------------------------------------------------------------===//
|
||
//
|
||
// This pass lowers FIR dialect memory operations to the MemRef dialect.
|
||
// In particular it:
|
||
//
|
||
// - Rewrites `fir.alloca` to `memref.alloca`.
|
||
//
|
||
// - Rewrites `fir.load` / `fir.store` to `memref.load` / `memref.store`.
|
||
//
|
||
// - Allows FIR and MemRef to coexist by introducing `fir.convert` at
|
||
// memory-use sites. Memory operations (`memref.load`, `memref.store`,
|
||
// `memref.reinterpret_cast`, etc.) see MemRef-typed values, while the
|
||
// original FIR-typed values remain available for non-memory uses. For
|
||
// example:
|
||
//
|
||
// %fir_ref = ... : !fir.ref<!fir.array<...>>
|
||
// %memref = fir.convert %fir_ref
|
||
// : !fir.ref<!fir.array<...>> -> memref<...>
|
||
// %val = memref.load %memref[...] : memref<...>
|
||
// fir.call @callee(%fir_ref) : (!fir.ref<!fir.array<...>>) -> ()
|
||
//
|
||
// Here the MemRef-typed value is used for `memref.load`, while the
|
||
// original FIR-typed value is preserved for `fir.call`.
|
||
//
|
||
// - Computes shapes, strides, and indices as needed for slices and shifts
|
||
// and emits `memref.reinterpret_cast` when dynamic layout is required
|
||
// (TODO: use memref.cast instead).
|
||
//
|
||
//===----------------------------------------------------------------------===//
|
||
|
||
#include "flang/Optimizer/Builder/CUFCommon.h"
|
||
#include "flang/Optimizer/Dialect/CUF/Attributes/CUFAttr.h"
|
||
#include "flang/Optimizer/Dialect/FIROps.h"
|
||
#include "flang/Optimizer/Dialect/FIROpsSupport.h"
|
||
#include "flang/Optimizer/Dialect/FIRType.h"
|
||
#include "flang/Optimizer/Dialect/Support/FIRContext.h"
|
||
#include "flang/Optimizer/Dialect/Support/KindMapping.h"
|
||
#include "flang/Optimizer/Transforms/FIRToMemRefTypeConverter.h"
|
||
#include "flang/Optimizer/Transforms/Passes.h"
|
||
#include "mlir/Dialect/Arith/IR/Arith.h"
|
||
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
||
#include "mlir/Dialect/MemRef/IR/MemRef.h"
|
||
#include "mlir/Dialect/OpenACC/OpenACC.h"
|
||
#include "mlir/Dialect/OpenMP/OpenMPDialect.h"
|
||
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||
#include "mlir/IR/Block.h"
|
||
#include "mlir/IR/Builders.h"
|
||
#include "mlir/IR/BuiltinAttributes.h"
|
||
#include "mlir/IR/BuiltinTypes.h"
|
||
#include "mlir/IR/Dominance.h"
|
||
#include "mlir/IR/Location.h"
|
||
#include "mlir/IR/MLIRContext.h"
|
||
#include "mlir/IR/Operation.h"
|
||
#include "mlir/IR/PatternMatch.h"
|
||
#include "mlir/IR/Region.h"
|
||
#include "mlir/IR/Value.h"
|
||
#include "mlir/IR/ValueRange.h"
|
||
#include "mlir/IR/Verifier.h"
|
||
#include "mlir/Pass/Pass.h"
|
||
#include "mlir/Support/LLVM.h"
|
||
#include "mlir/Transforms/DialectConversion.h"
|
||
#include "llvm/ADT/SetVector.h"
|
||
#include "llvm/ADT/SmallPtrSet.h"
|
||
#include "llvm/ADT/SmallVector.h"
|
||
#include "llvm/ADT/StringRef.h"
|
||
#include "llvm/ADT/TypeSwitch.h"
|
||
#include "llvm/Support/Casting.h"
|
||
#include "llvm/Support/CommandLine.h"
|
||
#include "llvm/Support/Debug.h"
|
||
#include "llvm/Support/ErrorHandling.h"
|
||
|
||
#define DEBUG_TYPE "fir-to-memref"
|
||
|
||
using namespace mlir;
|
||
|
||
namespace fir {
|
||
|
||
#define GEN_PASS_DEF_FIRTOMEMREF
|
||
#include "flang/Optimizer/Transforms/Passes.h.inc"
|
||
|
||
static bool isMarshalLike(Operation *op) {
|
||
auto convert = dyn_cast_if_present<fir::ConvertOp>(op);
|
||
if (!convert)
|
||
return false;
|
||
|
||
bool resIsMemRef = isa<MemRefType>(convert.getType());
|
||
bool argIsMemRef = isa<MemRefType>(convert.getValue().getType());
|
||
|
||
assert(!(resIsMemRef && argIsMemRef) &&
|
||
"unexpected fir.convert memref -> memref in isMarshalLike");
|
||
|
||
return resIsMemRef || argIsMemRef;
|
||
}
|
||
|
||
using MemRefInfo = FailureOr<std::pair<Value, SmallVector<Value>>>;
|
||
|
||
static llvm::cl::opt<bool> enableFIRConvertOptimizations(
|
||
"enable-fir-convert-opts",
|
||
llvm::cl::desc("enable emilinating redundant fir.convert in FIR-to-MemRef"),
|
||
llvm::cl::init(false), llvm::cl::Hidden);
|
||
|
||
class FIRToMemRef : public fir::impl::FIRToMemRefBase<FIRToMemRef> {
|
||
public:
|
||
void runOnOperation() override;
|
||
|
||
private:
|
||
llvm::SmallSetVector<Operation *, 32> eraseOps;
|
||
|
||
DominanceInfo *domInfo = nullptr;
|
||
|
||
void rewriteAlloca(fir::AllocaOp, PatternRewriter &,
|
||
FIRToMemRefTypeConverter &);
|
||
|
||
void rewriteLoadOp(fir::LoadOp, PatternRewriter &,
|
||
FIRToMemRefTypeConverter &);
|
||
|
||
void rewriteStoreOp(fir::StoreOp, PatternRewriter &,
|
||
FIRToMemRefTypeConverter &);
|
||
|
||
MemRefInfo getMemRefInfo(Value, PatternRewriter &, FIRToMemRefTypeConverter &,
|
||
Operation *);
|
||
|
||
MemRefInfo convertArrayCoorOp(Operation *memOp, fir::ArrayCoorOp,
|
||
PatternRewriter &, FIRToMemRefTypeConverter &);
|
||
|
||
/// Returns true if \p coordinateOp can be lowered to an indexed memref access
|
||
/// by convertCoordinateArrayOp. This is true when the base is a reference to
|
||
/// a statically-shaped scalar array.
|
||
bool isArrayIndexingCoordinateOp(fir::CoordinateOp coordinateOp,
|
||
FIRToMemRefTypeConverter &) const;
|
||
|
||
/// Lower a fir.coordinate_of that indexes into a static-extent scalar array
|
||
/// (e.g. a struct component like `A%v(i)`) to a memref + index pair.
|
||
MemRefInfo convertCoordinateArrayOp(Operation *memOp, fir::CoordinateOp,
|
||
PatternRewriter &,
|
||
FIRToMemRefTypeConverter &);
|
||
|
||
void replaceFIRMemrefs(Value, Value, PatternRewriter &) const;
|
||
|
||
FailureOr<Value> getFIRConvert(Operation *memOp, Operation *memref,
|
||
PatternRewriter &, FIRToMemRefTypeConverter &);
|
||
|
||
FailureOr<SmallVector<Value>> getMemrefIndices(fir::ArrayCoorOp, Operation *,
|
||
PatternRewriter &, Value,
|
||
Value) const;
|
||
|
||
bool memrefIsOptional(Operation *) const;
|
||
|
||
Value canonicalizeIndex(Value, PatternRewriter &) const;
|
||
|
||
// Logical section information used by FIRToMemRef. For projected slices, the
|
||
// descriptor still owns the physical layout, so `sliceVec` intentionally
|
||
// stays empty while `shapeVec`/`shiftVec` remain available for index math.
|
||
struct SliceInfo {
|
||
SmallVector<Value> shapeVec;
|
||
SmallVector<Value> shiftVec;
|
||
SmallVector<Value> sliceVec;
|
||
bool hasProjectedSlice = false;
|
||
// Constant value of the first projected-slice field, if any.
|
||
std::optional<std::int64_t> projectedSliceStart;
|
||
};
|
||
|
||
template <typename OpTy>
|
||
void collectSliceInfoFrom(OpTy op, SliceInfo &info) const;
|
||
|
||
void populateShapeAndShift(SmallVectorImpl<Value> &shapeVec,
|
||
SmallVectorImpl<Value> &shiftVec,
|
||
fir::ShapeShiftOp shift) const;
|
||
|
||
void populateShift(SmallVectorImpl<Value> &vec, fir::ShiftOp shift) const;
|
||
|
||
void populateShape(SmallVectorImpl<Value> &vec, fir::ShapeOp shape) const;
|
||
|
||
static fir::SliceOp getSliceOp(Value sliceVal) {
|
||
return sliceVal ? sliceVal.getDefiningOp<fir::SliceOp>() : fir::SliceOp{};
|
||
}
|
||
|
||
static bool hasProjectedSlice(fir::SliceOp sliceOp) {
|
||
return sliceOp && !sliceOp.getFields().empty();
|
||
}
|
||
|
||
// Returns the constant first projected-slice field, if available.
|
||
static std::optional<std::int64_t>
|
||
getProjectedSliceStartIfConstant(fir::SliceOp sliceOp) {
|
||
auto fields = sliceOp.getFields();
|
||
if (fields.empty())
|
||
return std::nullopt;
|
||
return fir::getIntIfConstant(fields.front());
|
||
}
|
||
|
||
unsigned getRankFromEmbox(fir::EmboxOp embox) const {
|
||
auto memrefType = embox.getMemref().getType();
|
||
Type unwrappedType = fir::unwrapRefType(memrefType);
|
||
if (auto seqType = dyn_cast<fir::SequenceType>(unwrappedType))
|
||
return seqType.getDimension();
|
||
return 0;
|
||
}
|
||
|
||
bool isCompilerGeneratedAlloca(Operation *op) const;
|
||
|
||
void copyAttribute(Operation *from, Operation *to,
|
||
llvm::StringRef name) const;
|
||
|
||
Type getBaseType(Type type, bool complexBaseTypes = false) const;
|
||
|
||
bool memrefIsDeviceData(Operation *memref) const;
|
||
|
||
mlir::Attribute findCudaDataAttr(Value val) const;
|
||
|
||
Value materializeBoxAddressIfNeeded(Value basePtr, PatternRewriter &rewriter,
|
||
Location loc) const;
|
||
};
|
||
|
||
void FIRToMemRef::populateShapeAndShift(SmallVectorImpl<Value> &shapeVec,
|
||
SmallVectorImpl<Value> &shiftVec,
|
||
fir::ShapeShiftOp shift) const {
|
||
for (mlir::OperandRange::iterator i = shift.getPairs().begin(),
|
||
endIter = shift.getPairs().end();
|
||
i != endIter;) {
|
||
shiftVec.push_back(*i++);
|
||
shapeVec.push_back(*i++);
|
||
}
|
||
}
|
||
|
||
bool FIRToMemRef::isCompilerGeneratedAlloca(Operation *op) const {
|
||
if (!isa<fir::AllocaOp, memref::AllocaOp>(op))
|
||
llvm_unreachable("expected alloca op");
|
||
|
||
return !op->getAttr("bindc_name") && !op->getAttr("uniq_name");
|
||
}
|
||
|
||
void FIRToMemRef::copyAttribute(Operation *from, Operation *to,
|
||
llvm::StringRef name) const {
|
||
if (Attribute value = from->getAttr(name))
|
||
to->setAttr(name, value);
|
||
}
|
||
|
||
Type FIRToMemRef::getBaseType(Type type, bool complexBaseTypes) const {
|
||
if (fir::isa_fir_type(type)) {
|
||
type = fir::getFortranElementType(type);
|
||
} else if (auto memrefTy = dyn_cast<MemRefType>(type)) {
|
||
type = memrefTy.getElementType();
|
||
}
|
||
|
||
if (!complexBaseTypes)
|
||
if (auto complexTy = dyn_cast<ComplexType>(type))
|
||
type = complexTy.getElementType();
|
||
return type;
|
||
}
|
||
|
||
bool FIRToMemRef::memrefIsDeviceData(Operation *memref) const {
|
||
if (isa<ACC_DATA_ENTRY_OPS>(memref))
|
||
return true;
|
||
|
||
return cuf::hasDeviceDataAttr(memref);
|
||
}
|
||
|
||
mlir::Attribute FIRToMemRef::findCudaDataAttr(Value val) const {
|
||
Value currentVal = val;
|
||
llvm::SmallPtrSet<Operation *, 8> visited;
|
||
|
||
while (currentVal) {
|
||
Operation *defOp = currentVal.getDefiningOp();
|
||
if (!defOp || !visited.insert(defOp).second)
|
||
break;
|
||
|
||
if (cuf::DataAttributeAttr cudaAttr = cuf::getDataAttr(defOp))
|
||
return cudaAttr;
|
||
|
||
// TODO: This is a best-effort backward walk; it is easy to miss attributes
|
||
// as FIR evolves. Long term, it would be preferable if the necessary
|
||
// information was carried in the type system (or otherwise made available
|
||
// without relying on a walk-back through defining ops).
|
||
if (auto reboxOp = dyn_cast<fir::ReboxOp>(defOp)) {
|
||
currentVal = reboxOp.getBox();
|
||
} else if (auto convertOp = dyn_cast<fir::ConvertOp>(defOp)) {
|
||
currentVal = convertOp->getOperand(0);
|
||
} else if (auto emboxOp = dyn_cast<fir::EmboxOp>(defOp)) {
|
||
currentVal = emboxOp.getMemref();
|
||
} else if (auto boxAddrOp = dyn_cast<fir::BoxAddrOp>(defOp)) {
|
||
currentVal = boxAddrOp.getVal();
|
||
} else if (auto declareOp = dyn_cast<fir::DeclareOp>(defOp)) {
|
||
currentVal = declareOp.getMemref();
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
return nullptr;
|
||
}
|
||
|
||
Value FIRToMemRef::materializeBoxAddressIfNeeded(Value basePtr,
|
||
PatternRewriter &rewriter,
|
||
Location loc) const {
|
||
if (!isa<fir::BoxType>(basePtr.getType()))
|
||
return basePtr;
|
||
|
||
auto boxAddrOp = fir::BoxAddrOp::create(rewriter, loc, basePtr);
|
||
if (auto cudaAttr = findCudaDataAttr(basePtr))
|
||
boxAddrOp->setAttr(cuf::getDataAttrName(), cudaAttr);
|
||
return boxAddrOp.getResult();
|
||
}
|
||
|
||
void FIRToMemRef::populateShift(SmallVectorImpl<Value> &vec,
|
||
fir::ShiftOp shift) const {
|
||
vec.append(shift.getOrigins().begin(), shift.getOrigins().end());
|
||
}
|
||
|
||
void FIRToMemRef::populateShape(SmallVectorImpl<Value> &vec,
|
||
fir::ShapeOp shape) const {
|
||
vec.append(shape.getExtents().begin(), shape.getExtents().end());
|
||
}
|
||
|
||
template <typename OpTy>
|
||
void FIRToMemRef::collectSliceInfoFrom(OpTy op, SliceInfo &info) const {
|
||
if constexpr (std::is_same_v<OpTy, fir::ArrayCoorOp> ||
|
||
std::is_same_v<OpTy, fir::ReboxOp> ||
|
||
std::is_same_v<OpTy, fir::EmboxOp>) {
|
||
Value shapeVal = op.getShape();
|
||
|
||
if (shapeVal) {
|
||
Operation *shapeValOp = shapeVal.getDefiningOp();
|
||
|
||
if (auto shapeOp = dyn_cast<fir::ShapeOp>(shapeValOp)) {
|
||
populateShape(info.shapeVec, shapeOp);
|
||
} else if (auto shapeShiftOp = dyn_cast<fir::ShapeShiftOp>(shapeValOp)) {
|
||
populateShapeAndShift(info.shapeVec, info.shiftVec, shapeShiftOp);
|
||
} else if (auto shiftOp = dyn_cast<fir::ShiftOp>(shapeValOp)) {
|
||
populateShift(info.shiftVec, shiftOp);
|
||
}
|
||
}
|
||
|
||
if (auto sliceOp = getSliceOp(op.getSlice())) {
|
||
if (hasProjectedSlice(sliceOp)) {
|
||
info.hasProjectedSlice = true;
|
||
info.projectedSliceStart = getProjectedSliceStartIfConstant(sliceOp);
|
||
}
|
||
auto triples = sliceOp.getTriples();
|
||
info.sliceVec.append(triples.begin(), triples.end());
|
||
}
|
||
}
|
||
}
|
||
|
||
void FIRToMemRef::rewriteAlloca(fir::AllocaOp firAlloca,
|
||
PatternRewriter &rewriter,
|
||
FIRToMemRefTypeConverter &typeConverter) {
|
||
if (!typeConverter.convertibleType(firAlloca.getInType()))
|
||
return;
|
||
|
||
if (typeConverter.isEmptyArray(firAlloca.getType()))
|
||
return;
|
||
|
||
rewriter.setInsertionPointAfter(firAlloca);
|
||
|
||
Type type = firAlloca.getType();
|
||
MemRefType memrefTy = typeConverter.convertMemrefType(type);
|
||
|
||
Location loc = firAlloca.getLoc();
|
||
|
||
SmallVector<Value> sizes = firAlloca.getOperands();
|
||
std::reverse(sizes.begin(), sizes.end());
|
||
|
||
auto alloca = memref::AllocaOp::create(rewriter, loc, memrefTy, sizes);
|
||
copyAttribute(firAlloca, alloca, firAlloca.getBindcNameAttrName());
|
||
copyAttribute(firAlloca, alloca, firAlloca.getUniqNameAttrName());
|
||
copyAttribute(firAlloca, alloca, cuf::getDataAttrName());
|
||
copyAttribute(firAlloca, alloca, acc::getVarNameAttrName());
|
||
|
||
auto convert = fir::ConvertOp::create(rewriter, loc, type, alloca);
|
||
|
||
rewriter.replaceOp(firAlloca, convert);
|
||
|
||
if (isCompilerGeneratedAlloca(alloca)) {
|
||
for (Operation *userOp : convert->getUsers()) {
|
||
if (auto declareOp = dyn_cast<fir::DeclareOp>(userOp)) {
|
||
LLVM_DEBUG(llvm::dbgs()
|
||
<< "FIRToMemRef: removing declare for compiler temp:\n";
|
||
declareOp->dump());
|
||
declareOp->replaceAllUsesWith(convert);
|
||
eraseOps.insert(userOp);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
bool FIRToMemRef::memrefIsOptional(Operation *op) const {
|
||
if (auto declare = dyn_cast<fir::DeclareOp>(op)) {
|
||
if (fir::FortranVariableOpInterface(declare).isOptional())
|
||
return true;
|
||
|
||
Value operand = declare.getMemref();
|
||
Operation *operandOp = operand.getDefiningOp();
|
||
if (operandOp && isa<fir::AbsentOp>(operandOp))
|
||
return true;
|
||
}
|
||
|
||
for (mlir::Value result : op->getResults())
|
||
for (mlir::Operation *userOp : result.getUsers())
|
||
if (isa<fir::IsPresentOp>(userOp))
|
||
return true;
|
||
|
||
// TODO: If `op` is not a `fir.declare`, OPTIONAL information may still be
|
||
// present on a related `fir.declare` reached by tracing the address/box
|
||
// through common forwarding ops (e.g. `fir.convert`, `fir.rebox`,
|
||
// `fir.embox`, `fir.box_addr`), then checking `declare.isOptional()`. Add the
|
||
// search after FIR improves on it.
|
||
return false;
|
||
}
|
||
|
||
static Value castTypeToIndexType(Value originalValue,
|
||
PatternRewriter &rewriter) {
|
||
if (originalValue.getType().isIndex())
|
||
return originalValue;
|
||
|
||
Type indexType = rewriter.getIndexType();
|
||
return arith::IndexCastOp::create(rewriter, originalValue.getLoc(), indexType,
|
||
originalValue);
|
||
}
|
||
|
||
static bool shouldUseBoundaryBitcast(mlir::Type fromTy, mlir::Type toTy) {
|
||
auto isBitcastCompatibleScalarType = [](mlir::Type ty) {
|
||
return mlir::isa<mlir::IntegerType, mlir::FloatType, fir::LogicalType>(
|
||
ty) ||
|
||
(mlir::isa<fir::CharacterType>(ty) &&
|
||
mlir::cast<fir::CharacterType>(ty).getLen() ==
|
||
fir::CharacterType::singleton());
|
||
};
|
||
auto getKnownScalarBitWidth = [](mlir::Type ty) -> std::optional<unsigned> {
|
||
if (auto intTy = mlir::dyn_cast<mlir::IntegerType>(ty))
|
||
return intTy.getWidth();
|
||
if (auto floatTy = mlir::dyn_cast<mlir::FloatType>(ty))
|
||
return floatTy.getWidth();
|
||
return std::nullopt;
|
||
};
|
||
|
||
if (fromTy == toTy)
|
||
return false;
|
||
const bool fromStd = fir::isa_std_type(fromTy);
|
||
const bool toStd = fir::isa_std_type(toTy);
|
||
if (fromStd == toStd)
|
||
return false;
|
||
if (!isBitcastCompatibleScalarType(fromTy) ||
|
||
!isBitcastCompatibleScalarType(toTy))
|
||
return false;
|
||
auto fromBits = getKnownScalarBitWidth(fromTy);
|
||
auto toBits = getKnownScalarBitWidth(toTy);
|
||
if (fromBits && toBits && *fromBits != *toBits)
|
||
return false;
|
||
return true;
|
||
}
|
||
|
||
static mlir::Value createTypeConversion(PatternRewriter &rewriter,
|
||
mlir::Location loc, mlir::Type toTy,
|
||
mlir::Value value) {
|
||
if (shouldUseBoundaryBitcast(value.getType(), toTy))
|
||
return fir::BitcastOp::create(rewriter, loc, toTy, value);
|
||
return fir::ConvertOp::create(rewriter, loc, toTy, value);
|
||
}
|
||
|
||
FailureOr<SmallVector<Value>>
|
||
FIRToMemRef::getMemrefIndices(fir::ArrayCoorOp arrayCoorOp, Operation *memref,
|
||
PatternRewriter &rewriter, Value converted,
|
||
Value one) const {
|
||
IndexType indexTy = rewriter.getIndexType();
|
||
SmallVector<Value> indices;
|
||
Location loc = arrayCoorOp->getLoc();
|
||
SliceInfo sliceInfo;
|
||
int rank = arrayCoorOp.getIndices().size();
|
||
collectSliceInfoFrom(arrayCoorOp, sliceInfo);
|
||
|
||
// Only collect shape/shift from fir.embox, never from fir.rebox.
|
||
//
|
||
// The distinction is a Fortran descriptor invariant:
|
||
//
|
||
// | fir.embox | fir.rebox
|
||
// --------------|------------------------|---------------------------
|
||
// base_addr | raw pointer | pre-adjusted by (lb-1)*stride
|
||
// Index formula | index - lb | index - 1
|
||
// Collect shift | yes | no
|
||
//
|
||
// fir.embox creates a box from a raw pointer so base_addr is not adjusted;
|
||
// the shift must be collected so index arithmetic subtracts lb correctly.
|
||
// fir.rebox re-boxes a live descriptor whose base_addr the runtime has
|
||
// already adjusted downward by (lb-1)*stride; collecting the shift here
|
||
// would subtract the lower bound a second time, giving the wrong element.
|
||
if (auto embox = dyn_cast_or_null<fir::EmboxOp>(memref)) {
|
||
collectSliceInfoFrom(embox, sliceInfo);
|
||
rank = getRankFromEmbox(embox);
|
||
}
|
||
|
||
SmallVector<Value> &shiftVec = sliceInfo.shiftVec;
|
||
SmallVector<Value> &sliceVec = sliceInfo.sliceVec;
|
||
SmallVector<Value> sliceLbs, sliceStrides;
|
||
for (size_t i = 0; i < sliceVec.size(); i += 3) {
|
||
sliceLbs.push_back(castTypeToIndexType(sliceVec[i], rewriter));
|
||
sliceStrides.push_back(castTypeToIndexType(sliceVec[i + 2], rewriter));
|
||
}
|
||
|
||
const bool isShifted = !shiftVec.empty();
|
||
const bool isSliced = !sliceVec.empty();
|
||
|
||
ValueRange idxs = arrayCoorOp.getIndices();
|
||
Value zero = arith::ConstantIndexOp::create(rewriter, loc, 0);
|
||
|
||
SmallVector<bool> filledPositions(rank, false);
|
||
for (int i = 0; i < rank; ++i) {
|
||
Value step = isSliced ? sliceStrides[i] : one;
|
||
Operation *stepOp = step.getDefiningOp();
|
||
if (stepOp && mlir::isa_and_nonnull<fir::UndefOp>(stepOp)) {
|
||
Value shift = isShifted ? shiftVec[i] : one;
|
||
Value sliceLb = isSliced ? sliceLbs[i] : shift;
|
||
Value offset = arith::SubIOp::create(rewriter, loc, sliceLb, shift);
|
||
indices.push_back(offset);
|
||
filledPositions[i] = true;
|
||
} else {
|
||
indices.push_back(zero);
|
||
}
|
||
}
|
||
|
||
const int nonScalarRank = llvm::count(filledPositions, false);
|
||
const bool hasReducedRankIndices =
|
||
static_cast<int>(idxs.size()) == nonScalarRank;
|
||
const bool hasFullRankIndices = static_cast<int>(idxs.size()) == rank;
|
||
if (!hasReducedRankIndices && !hasFullRankIndices)
|
||
return failure();
|
||
|
||
int arrayCoorIdx = 0;
|
||
for (int i = 0; i < rank; ++i) {
|
||
if (filledPositions[i])
|
||
continue;
|
||
|
||
Value sourceIndex;
|
||
if (hasFullRankIndices) {
|
||
// Canonicalized rank-reducing array_coor may carry full-rank indices
|
||
// (including scalar-sliced dimensions).
|
||
sourceIndex = idxs[i];
|
||
} else {
|
||
assert((unsigned int)arrayCoorIdx < idxs.size() &&
|
||
"empty dimension should be eliminated\n");
|
||
sourceIndex = idxs[arrayCoorIdx];
|
||
}
|
||
Value index = canonicalizeIndex(sourceIndex, rewriter);
|
||
Type cTy = index.getType();
|
||
if (!llvm::isa<IndexType>(cTy)) {
|
||
assert(cTy.isSignlessInteger() && "expected signless integer type");
|
||
index = arith::IndexCastOp::create(rewriter, loc, indexTy, index);
|
||
}
|
||
|
||
Value shift = isShifted ? shiftVec[i] : one;
|
||
Value stride = isSliced ? sliceStrides[i] : one;
|
||
Value sliceLb = isSliced ? sliceLbs[i] : shift;
|
||
|
||
// When the array_coor has an explicit slice with a shape_shift (i.e.
|
||
// non-default lower bounds), the indices are in the shape_shift
|
||
// coordinate space; subtract the lower bound (shift) to get 0-based
|
||
// memref indices. Otherwise (the slice comes from an embox, or the
|
||
// shape has no shift), the indices are 1-based section indices;
|
||
// subtract 1.
|
||
bool indicesAreFortran = isShifted && arrayCoorOp.getSlice() != nullptr;
|
||
Value indexAdjustment =
|
||
(isSliced && !indicesAreFortran)
|
||
? arith::ConstantIndexOp::create(rewriter, loc, 1)
|
||
: shift;
|
||
Value delta = arith::SubIOp::create(rewriter, loc, index, indexAdjustment);
|
||
|
||
Value scaled = arith::MulIOp::create(rewriter, loc, delta, stride);
|
||
|
||
Value offset = arith::SubIOp::create(rewriter, loc, sliceLb, shift);
|
||
|
||
Value finalIndex = arith::AddIOp::create(rewriter, loc, scaled, offset);
|
||
|
||
indices[i] = finalIndex;
|
||
if (hasReducedRankIndices)
|
||
arrayCoorIdx++;
|
||
}
|
||
|
||
std::reverse(indices.begin(), indices.end());
|
||
|
||
return indices;
|
||
}
|
||
|
||
MemRefInfo
|
||
FIRToMemRef::convertArrayCoorOp(Operation *memOp, fir::ArrayCoorOp arrayCoorOp,
|
||
PatternRewriter &rewriter,
|
||
FIRToMemRefTypeConverter &typeConverter) {
|
||
IndexType indexTy = rewriter.getIndexType();
|
||
Value firMemref = arrayCoorOp.getMemref();
|
||
if (!typeConverter.convertibleMemrefType(firMemref.getType()))
|
||
return failure();
|
||
|
||
if (typeConverter.isEmptyArray(firMemref.getType()))
|
||
return failure();
|
||
|
||
Location loc = arrayCoorOp->getLoc();
|
||
|
||
// Prefer lowering the array-coordinates computation to a memref + indices.
|
||
// This allows erasing fir.array_coor when it is only used by load/store even
|
||
// if the base address is a block argument (e.g. region arguments).
|
||
Operation *memref = nullptr;
|
||
FailureOr<Value> converted;
|
||
if (auto blockArg = dyn_cast<BlockArgument>(firMemref)) {
|
||
rewriter.setInsertionPoint(arrayCoorOp);
|
||
Value basePtr = materializeBoxAddressIfNeeded(blockArg, rewriter, loc);
|
||
Type memrefTy = typeConverter.convertMemrefType(basePtr.getType());
|
||
converted =
|
||
fir::ConvertOp::create(rewriter, loc, memrefTy, basePtr).getResult();
|
||
rewriter.setInsertionPointAfter(arrayCoorOp);
|
||
} else if ((memref = firMemref.getDefiningOp()) &&
|
||
enableFIRConvertOptimizations && isMarshalLike(memref) &&
|
||
!fir::isa_fir_type(firMemref.getType())) {
|
||
converted = firMemref;
|
||
rewriter.setInsertionPoint(arrayCoorOp);
|
||
} else {
|
||
Operation *arrayCoorOperation = arrayCoorOp.getOperation();
|
||
rewriter.setInsertionPoint(arrayCoorOp);
|
||
if (memrefIsOptional(memref)) {
|
||
auto ifOp = arrayCoorOperation->getParentOfType<scf::IfOp>();
|
||
if (ifOp) {
|
||
Operation *condition = ifOp.getCondition().getDefiningOp();
|
||
if (condition && isa<fir::IsPresentOp>(condition))
|
||
if (condition->getOperand(0) == firMemref) {
|
||
if (arrayCoorOperation->getParentRegion() == &ifOp.getThenRegion())
|
||
rewriter.setInsertionPointToStart(
|
||
&(ifOp.getThenRegion().front()));
|
||
else if (arrayCoorOperation->getParentRegion() ==
|
||
&ifOp.getElseRegion())
|
||
rewriter.setInsertionPointToStart(
|
||
&(ifOp.getElseRegion().front()));
|
||
}
|
||
}
|
||
}
|
||
|
||
converted = getFIRConvert(memOp, memref, rewriter, typeConverter);
|
||
if (failed(converted))
|
||
return failure();
|
||
|
||
rewriter.setInsertionPointAfter(arrayCoorOp);
|
||
}
|
||
|
||
Value one = arith::ConstantIndexOp::create(rewriter, loc, 1);
|
||
FailureOr<SmallVector<Value>> failureOrIndices =
|
||
getMemrefIndices(arrayCoorOp, memref, rewriter, *converted, one);
|
||
if (failed(failureOrIndices))
|
||
return failure();
|
||
SmallVector<Value> indices = *failureOrIndices;
|
||
|
||
if (converted == firMemref)
|
||
return std::pair{*converted, indices};
|
||
|
||
Value convertedVal = *converted;
|
||
MemRefType memRefTy = dyn_cast<MemRefType>(convertedVal.getType());
|
||
|
||
bool isDescriptor = mlir::isa<fir::BaseBoxType>(firMemref.getType()) ||
|
||
firMemref.getDefiningOp<fir::BoxAddrOp>() != nullptr;
|
||
|
||
// For complex projections, reinterpret memref<d0×...×complex<T>> as
|
||
// memref<d0×...×2×T> and append the component index (0=re, 1=im) so that
|
||
// each load/store touches exactly sizeof(T) bytes.
|
||
SliceInfo sliceInfo;
|
||
collectSliceInfoFrom(arrayCoorOp, sliceInfo);
|
||
if (auto embox = firMemref.getDefiningOp<fir::EmboxOp>())
|
||
collectSliceInfoFrom(embox, sliceInfo);
|
||
else if (auto rebox = firMemref.getDefiningOp<fir::ReboxOp>())
|
||
collectSliceInfoFrom(rebox, sliceInfo);
|
||
auto srcTy = cast<MemRefType>((*converted).getType());
|
||
std::optional<int64_t> complexPartIdx;
|
||
if (sliceInfo.hasProjectedSlice) {
|
||
if (auto complexTy = dyn_cast<mlir::ComplexType>(srcTy.getElementType())) {
|
||
if (!sliceInfo.projectedSliceStart ||
|
||
(*sliceInfo.projectedSliceStart != 0 &&
|
||
*sliceInfo.projectedSliceStart != 1)) {
|
||
LLVM_DEBUG(
|
||
llvm::dbgs()
|
||
<< "FIRToMemRef: projected complex slice selector must be constant "
|
||
"0 (real) or 1 (imaginary), bailing out of conversion\n");
|
||
return failure();
|
||
}
|
||
complexPartIdx = *sliceInfo.projectedSliceStart;
|
||
SmallVector<int64_t> shape(srcTy.getShape());
|
||
shape.push_back(2);
|
||
convertedVal =
|
||
fir::ConvertOp::create(
|
||
rewriter, loc, MemRefType::get(shape, complexTy.getElementType()),
|
||
*converted)
|
||
.getResult();
|
||
memRefTy = cast<MemRefType>(convertedVal.getType());
|
||
indices.push_back(
|
||
arith::ConstantIndexOp::create(rewriter, loc, *complexPartIdx));
|
||
}
|
||
}
|
||
|
||
// Static shape does not imply contiguous layout for descriptor-backed
|
||
// entities (e.g. boxed array sections with non-unit stride). Projected
|
||
// complex %re/%im also need reinterpret_cast even when the converted type
|
||
// is statically shaped (e.g. memref<Nx2xT>).
|
||
if (!complexPartIdx && memRefTy.hasStaticShape() && !isDescriptor)
|
||
return std::pair{*converted, indices};
|
||
|
||
unsigned rank = arrayCoorOp.getIndices().size();
|
||
if (auto embox = firMemref.getDefiningOp<fir::EmboxOp>())
|
||
rank = getRankFromEmbox(embox);
|
||
|
||
SmallVector<Value> sizes;
|
||
sizes.reserve(rank);
|
||
SmallVector<Value> strides;
|
||
strides.reserve(rank);
|
||
|
||
SmallVector<Value> &shapeVec = sliceInfo.shapeVec;
|
||
const bool firMemrefIsBox = mlir::isa<fir::BaseBoxType>(firMemref.getType());
|
||
const bool firMemrefIsEmbox =
|
||
firMemref.getDefiningOp<fir::EmboxOp>() != nullptr;
|
||
// Pick how to derive sizes/strides for the reinterpret_cast view:
|
||
//
|
||
// shapeVec path: synthesize row-major strides from fir.shape extents.
|
||
// Valid when the converted MemRef describes a contiguous storage block:
|
||
// either the array_coor base is a raw ref/heap/ptr (no descriptor at
|
||
// all), or it is a fir.box produced by fir.embox -- getFIRConvert
|
||
// rewinds to embox.getMemref()/box_addr(embox) in that case, so the
|
||
// reinterpret_cast operates on the underlying contiguous ref. This
|
||
// matches CodeGen XArrayCoorOp's non-boxed branch.
|
||
//
|
||
// box_dims path: query the descriptor at runtime. Required when:
|
||
// (a) we have no shape information at all; or
|
||
// (b) the array_coor base is a fir.box that is NOT a fir.embox result;
|
||
// or a fir.box with a projected slice (layout in the descriptor); or
|
||
// (c) embox cannot supply layout for this coor (non-embox box above).
|
||
// getFIRConvert materializes fir.box_addr(box) -- an opaque pointer
|
||
// with no layout in its type -- so strides must come from the
|
||
// descriptor. This matches CodeGen XArrayCoorOp's boxed branch
|
||
// (getStrideFromBox); shape/shape_shift on array_coor is
|
||
// informational only (lower bounds for index translation).
|
||
// Projected complex %re/%im on a bare ref uses the shapeVec path with
|
||
// strides scaled by two scalar slots per complex.
|
||
const bool boxNeedsDescriptorStrides =
|
||
firMemrefIsBox && (!firMemrefIsEmbox || sliceInfo.hasProjectedSlice);
|
||
const bool descriptorOwnsLayout =
|
||
shapeVec.empty() || boxNeedsDescriptorStrides;
|
||
if (descriptorOwnsLayout) {
|
||
// Complex %re/%im: memref_stride = box_dims_byte_stride / sizeof(T),
|
||
Value boxElementSize =
|
||
complexPartIdx
|
||
? arith::ConstantIndexOp::create(
|
||
rewriter, loc,
|
||
memRefTy.getElementType().getIntOrFloatBitWidth() / 8)
|
||
: fir::BoxEleSizeOp::create(rewriter, loc, indexTy, firMemref)
|
||
.getResult();
|
||
|
||
for (unsigned i = 0; i < rank; ++i) {
|
||
Value dim = arith::ConstantIndexOp::create(rewriter, loc, rank - i - 1);
|
||
auto boxDims = fir::BoxDimsOp::create(rewriter, loc, indexTy, indexTy,
|
||
indexTy, firMemref, dim);
|
||
|
||
// TODO: when an explicit fir.shape/fir.shape_shift is available
|
||
// (shapeVec non-empty), prefer its extents over the descriptor's
|
||
// box_dims extent result. For boxed array_coor the shape extents must
|
||
// agree with the descriptor's runtime extents, so either source is
|
||
// correct; using the shape would let constant extents reach the
|
||
// reinterpret_cast and improve downstream analysis.
|
||
Value extent = boxDims->getResult(1);
|
||
sizes.push_back(castTypeToIndexType(extent, rewriter));
|
||
|
||
Value byteStride = boxDims->getResult(2);
|
||
Value div =
|
||
arith::DivSIOp::create(rewriter, loc, byteStride, boxElementSize);
|
||
strides.push_back(castTypeToIndexType(div, rewriter));
|
||
}
|
||
|
||
} else {
|
||
Value oneIdx =
|
||
arith::ConstantIndexOp::create(rewriter, arrayCoorOp->getLoc(), 1);
|
||
for (unsigned i = rank - 1; i > 0; --i) {
|
||
Value size = shapeVec[i];
|
||
sizes.push_back(castTypeToIndexType(size, rewriter));
|
||
|
||
Value stride = shapeVec[0];
|
||
for (unsigned j = 1; j <= i - 1; ++j)
|
||
stride = arith::MulIOp::create(rewriter, loc, shapeVec[j], stride);
|
||
if (complexPartIdx)
|
||
stride = arith::MulIOp::create(
|
||
rewriter, loc, stride,
|
||
arith::ConstantIndexOp::create(rewriter, loc, 2));
|
||
strides.push_back(castTypeToIndexType(stride, rewriter));
|
||
}
|
||
|
||
sizes.push_back(castTypeToIndexType(shapeVec[0], rewriter));
|
||
// shapeVec strides count array elements (complexes). After fir.convert to
|
||
// memref<...x2xT>, each step along an array dim must skip two scalars (re
|
||
// then im), so multiply by 2. (Box path uses byte_stride / sizeof(T) for
|
||
// the same spacing; no /8 here because extents are already index units.)
|
||
if (complexPartIdx)
|
||
strides.push_back(arith::ConstantIndexOp::create(rewriter, loc, 2));
|
||
else
|
||
strides.push_back(oneIdx);
|
||
}
|
||
|
||
// fir.convert above already made memref<...x2xT>; sizes/strides built so far
|
||
// cover only the array section (rank from array_coor). Finish the
|
||
// reinterpret_cast layout with the pair dim that view already has: extent 2
|
||
// (re and im), stride 1 (contiguous scalars — index 0/1 from array_coor).
|
||
if (complexPartIdx) {
|
||
sizes.push_back(arith::ConstantIndexOp::create(rewriter, loc, 2));
|
||
strides.push_back(arith::ConstantIndexOp::create(rewriter, loc, 1));
|
||
++rank;
|
||
}
|
||
|
||
int64_t dynamicOffset = ShapedType::kDynamic;
|
||
SmallVector<int64_t> dynamicStrides(rank, ShapedType::kDynamic);
|
||
auto stridedLayout = StridedLayoutAttr::get(convertedVal.getContext(),
|
||
dynamicOffset, dynamicStrides);
|
||
|
||
SmallVector<int64_t> dynamicShape(rank, ShapedType::kDynamic);
|
||
memRefTy =
|
||
MemRefType::get(dynamicShape, memRefTy.getElementType(), stridedLayout);
|
||
|
||
Value offset = arith::ConstantIndexOp::create(rewriter, loc, 0);
|
||
|
||
auto reinterpret = memref::ReinterpretCastOp::create(
|
||
rewriter, loc, memRefTy, convertedVal, offset, sizes, strides);
|
||
|
||
Value result = reinterpret->getResult(0);
|
||
return std::pair{result, indices};
|
||
}
|
||
|
||
FailureOr<Value>
|
||
FIRToMemRef::getFIRConvert(Operation *memOp, Operation *op,
|
||
PatternRewriter &rewriter,
|
||
FIRToMemRefTypeConverter &typeConverter) {
|
||
if (enableFIRConvertOptimizations && !op->hasOneUse() &&
|
||
!memrefIsOptional(op)) {
|
||
for (Operation *userOp : op->getUsers()) {
|
||
if (auto convertOp = dyn_cast<fir::ConvertOp>(userOp)) {
|
||
Value converted = convertOp.getResult();
|
||
if (!isa<MemRefType>(converted.getType()))
|
||
continue;
|
||
|
||
if (userOp->getParentOp() == memOp->getParentOp() &&
|
||
domInfo->dominates(userOp, memOp))
|
||
return converted;
|
||
}
|
||
}
|
||
}
|
||
|
||
assert(op->getNumResults() == 1 && "expecting one result");
|
||
|
||
Value basePtr = op->getResult(0);
|
||
|
||
MemRefType memrefTy = typeConverter.convertMemrefType(basePtr.getType());
|
||
Type baseTy = memrefTy.getElementType();
|
||
|
||
if (fir::isa_std_type(baseTy) && memrefTy.getRank() == 0) {
|
||
if (auto convertOp = basePtr.getDefiningOp<fir::ConvertOp>()) {
|
||
Value input = convertOp.getOperand();
|
||
if (auto alloca = input.getDefiningOp<memref::AllocaOp>()) {
|
||
assert(alloca.getType() == memrefTy && "expected same types");
|
||
if (isCompilerGeneratedAlloca(alloca))
|
||
return alloca.getResult();
|
||
}
|
||
}
|
||
}
|
||
|
||
const Location loc = op->getLoc();
|
||
|
||
if (isa<fir::BoxType>(basePtr.getType())) {
|
||
Operation *baseOp = basePtr.getDefiningOp();
|
||
basePtr = materializeBoxAddressIfNeeded(basePtr, rewriter, loc);
|
||
memrefTy = typeConverter.convertMemrefType(basePtr.getType());
|
||
|
||
if (baseOp) {
|
||
auto sameBaseBoxTypes = [&](Type baseType, Type memrefType) -> bool {
|
||
Type emboxBaseTy = getBaseType(baseType, true);
|
||
Type emboxMemrefTy = getBaseType(memrefType, true);
|
||
return emboxBaseTy == emboxMemrefTy;
|
||
};
|
||
|
||
if (auto embox = dyn_cast_or_null<fir::EmboxOp>(baseOp)) {
|
||
// A projected slice changes the element type of the boxed view. We
|
||
// can only lower it here when the storage element is complex<T> and
|
||
// the projection is the real or imaginary part (i.e. %re / %im). For
|
||
// such cases sizeof(complex<T>) == 2*sizeof(T), so
|
||
// divsi(byte_stride, elesize) is always an exact integer.
|
||
//
|
||
// Derived-type component projections (e.g. a%x, a%y) may produce a
|
||
// non-integer element-unit stride (e.g. sizeof(T)=24,
|
||
// sizeof(complex<f64>)=16 -> 24/16 = 1 after truncation, which is
|
||
// wrong). For those, the type-restriction check below fires and we
|
||
// bail out, leaving the ops for downstream FIR-to-LLVM lowering.
|
||
auto isComplexComponentProjection = [&](fir::EmboxOp embox) -> bool {
|
||
if (!hasProjectedSlice(getSliceOp(embox.getSlice())))
|
||
return false;
|
||
Type memTy = fir::unwrapRefType(embox.getMemref().getType());
|
||
if (auto seqTy = dyn_cast<fir::SequenceType>(memTy))
|
||
memTy = seqTy.getEleTy();
|
||
return mlir::isa<mlir::ComplexType>(memTy);
|
||
};
|
||
bool projectedSlice = isComplexComponentProjection(embox);
|
||
if (!projectedSlice &&
|
||
!sameBaseBoxTypes(embox.getType(), embox.getMemref().getType())) {
|
||
LLVM_DEBUG(llvm::dbgs()
|
||
<< "FIRToMemRef: embox base type and memref type are not "
|
||
"the same, bailing out of conversion\n");
|
||
return failure();
|
||
}
|
||
if (embox.getSlice() &&
|
||
embox.getSlice().getDefiningOp<fir::SliceOp>()) {
|
||
Type originalType = embox.getMemref().getType();
|
||
basePtr = embox.getMemref();
|
||
|
||
if (typeConverter.convertibleMemrefType(originalType)) {
|
||
auto convertedMemrefTy =
|
||
typeConverter.convertMemrefType(originalType);
|
||
memrefTy = convertedMemrefTy;
|
||
} else {
|
||
return failure();
|
||
}
|
||
}
|
||
}
|
||
|
||
if (auto rebox = dyn_cast<fir::ReboxOp>(baseOp)) {
|
||
if (!sameBaseBoxTypes(rebox.getType(), rebox.getBox().getType())) {
|
||
LLVM_DEBUG(llvm::dbgs()
|
||
<< "FIRToMemRef: rebox base type and box type are not the "
|
||
"same, bailing out of conversion\n");
|
||
return failure();
|
||
}
|
||
Type originalType = rebox.getBox().getType();
|
||
if (auto boxTy = dyn_cast<fir::BoxType>(originalType))
|
||
originalType = boxTy.getElementType();
|
||
if (!typeConverter.convertibleMemrefType(originalType)) {
|
||
return failure();
|
||
} else {
|
||
auto convertedMemrefTy =
|
||
typeConverter.convertMemrefType(originalType);
|
||
memrefTy = convertedMemrefTy;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
auto convert = fir::ConvertOp::create(rewriter, loc, memrefTy, basePtr);
|
||
return convert->getResult(0);
|
||
}
|
||
|
||
Value FIRToMemRef::canonicalizeIndex(Value index,
|
||
PatternRewriter &rewriter) const {
|
||
if (auto blockArg = dyn_cast<BlockArgument>(index))
|
||
return index;
|
||
|
||
Operation *op = index.getDefiningOp();
|
||
|
||
if (auto constant = dyn_cast<arith::ConstantIntOp>(op)) {
|
||
if (!constant.getType().isIndex()) {
|
||
Value v = arith::ConstantIndexOp::create(rewriter, op->getLoc(),
|
||
constant.value());
|
||
return v;
|
||
}
|
||
return constant;
|
||
}
|
||
|
||
if (auto extsi = dyn_cast<arith::ExtSIOp>(op)) {
|
||
Value operand = extsi.getOperand();
|
||
if (auto indexCast = operand.getDefiningOp<arith::IndexCastOp>()) {
|
||
Value v = indexCast.getOperand();
|
||
return v;
|
||
}
|
||
return canonicalizeIndex(operand, rewriter);
|
||
}
|
||
|
||
if (auto add = dyn_cast<arith::AddIOp>(op)) {
|
||
Value lhs = canonicalizeIndex(add.getLhs(), rewriter);
|
||
Value rhs = canonicalizeIndex(add.getRhs(), rewriter);
|
||
if (lhs.getType() == rhs.getType())
|
||
return arith::AddIOp::create(rewriter, op->getLoc(), lhs, rhs);
|
||
}
|
||
return index;
|
||
}
|
||
|
||
bool FIRToMemRef::isArrayIndexingCoordinateOp(
|
||
fir::CoordinateOp coordinateOp,
|
||
FIRToMemRefTypeConverter &typeConverter) const {
|
||
// The base must be a reference/pointer/heap to a sequence/array type.
|
||
Type baseType = coordinateOp.getRef().getType();
|
||
Type unwrapped = fir::dyn_cast_ptrEleTy(baseType);
|
||
if (!unwrapped)
|
||
return false;
|
||
|
||
auto seqTy = dyn_cast<fir::SequenceType>(unwrapped);
|
||
if (!seqTy)
|
||
return false;
|
||
|
||
// Restrict to fully static extents — dynamic arrays would need a shape
|
||
// operand (which coordinate_of lacks) to build a valid memref descriptor.
|
||
if (fir::hasDynamicSize(seqTy))
|
||
return false;
|
||
|
||
// The element type must be a convertible scalar — no derived types.
|
||
if (!typeConverter.convertibleMemrefType(baseType))
|
||
return false;
|
||
|
||
return true;
|
||
}
|
||
|
||
MemRefInfo FIRToMemRef::convertCoordinateArrayOp(
|
||
Operation *memOp, fir::CoordinateOp coordinateOp, PatternRewriter &rewriter,
|
||
FIRToMemRefTypeConverter &typeConverter) {
|
||
Value firBase = coordinateOp.getRef();
|
||
Location loc = coordinateOp->getLoc();
|
||
IndexType indexTy = rewriter.getIndexType();
|
||
|
||
if (typeConverter.isEmptyArray(firBase.getType()))
|
||
return failure();
|
||
|
||
// Convert the base ref/heap/ptr to a memref. convertMemrefType reverses the
|
||
// FIR column-major shape to row-major, keeping it in sync with index reversal
|
||
// below.
|
||
rewriter.setInsertionPoint(coordinateOp);
|
||
FailureOr<Value> converted;
|
||
if (isa<BlockArgument>(firBase)) {
|
||
Type memrefTy = typeConverter.convertMemrefType(firBase.getType());
|
||
if (!memrefTy)
|
||
return failure();
|
||
converted =
|
||
fir::ConvertOp::create(rewriter, loc, memrefTy, firBase).getResult();
|
||
} else {
|
||
converted =
|
||
getFIRConvert(memOp, firBase.getDefiningOp(), rewriter, typeConverter);
|
||
if (failed(converted))
|
||
return failure();
|
||
}
|
||
rewriter.setInsertionPointAfter(coordinateOp);
|
||
|
||
// The converted memref has static shape — no reinterpret_cast needed.
|
||
assert(cast<MemRefType>(converted->getType()).hasStaticShape() &&
|
||
"expected static shape for coordinate_of array base");
|
||
|
||
// Collect and normalize the 0-based coor indices
|
||
SmallVector<Value> indices;
|
||
for (Value v : coordinateOp.getCoor()) {
|
||
v = canonicalizeIndex(v, rewriter);
|
||
if (!isa<IndexType>(v.getType()))
|
||
v = arith::IndexCastOp::create(rewriter, loc, indexTy, v);
|
||
indices.push_back(v);
|
||
}
|
||
std::reverse(indices.begin(), indices.end());
|
||
|
||
return std::pair{*converted, indices};
|
||
}
|
||
|
||
MemRefInfo FIRToMemRef::getMemRefInfo(Value firMemref,
|
||
PatternRewriter &rewriter,
|
||
FIRToMemRefTypeConverter &typeConverter,
|
||
Operation *memOp) {
|
||
Operation *memrefOp = firMemref.getDefiningOp();
|
||
if (!memrefOp) {
|
||
if (auto blockArg = dyn_cast<BlockArgument>(firMemref)) {
|
||
rewriter.setInsertionPoint(memOp);
|
||
Type memrefTy = typeConverter.convertMemrefType(blockArg.getType());
|
||
if (auto mt = dyn_cast<MemRefType>(memrefTy))
|
||
if (auto inner = llvm::dyn_cast<MemRefType>(mt.getElementType()))
|
||
memrefTy = inner;
|
||
Value converted = fir::ConvertOp::create(rewriter, blockArg.getLoc(),
|
||
memrefTy, blockArg);
|
||
SmallVector<Value> indices;
|
||
return std::pair{converted, indices};
|
||
}
|
||
llvm_unreachable(
|
||
"FIRToMemRef: expected defining op or block argument for FIR memref");
|
||
}
|
||
|
||
if (auto arrayCoorOp = dyn_cast<fir::ArrayCoorOp>(memrefOp)) {
|
||
MemRefInfo memrefInfo =
|
||
convertArrayCoorOp(memOp, arrayCoorOp, rewriter, typeConverter);
|
||
if (succeeded(memrefInfo)) {
|
||
for (auto user : memrefOp->getUsers()) {
|
||
if (!isa<fir::LoadOp, fir::StoreOp>(user)) {
|
||
LLVM_DEBUG(
|
||
llvm::dbgs()
|
||
<< "FIRToMemRef: array memref used by unsupported op:\n";
|
||
firMemref.dump(); user->dump());
|
||
return memrefInfo;
|
||
}
|
||
}
|
||
eraseOps.insert(memrefOp);
|
||
}
|
||
return memrefInfo;
|
||
}
|
||
|
||
rewriter.setInsertionPoint(memOp);
|
||
|
||
if (isMarshalLike(memrefOp)) {
|
||
FailureOr<Value> converted =
|
||
getFIRConvert(memOp, memrefOp, rewriter, typeConverter);
|
||
if (failed(converted)) {
|
||
LLVM_DEBUG(llvm::dbgs()
|
||
<< "FIRToMemRef: expected FIR memref in convert, bailing "
|
||
"out:\n";
|
||
firMemref.dump());
|
||
return failure();
|
||
}
|
||
SmallVector<Value> indices;
|
||
return std::pair{*converted, indices};
|
||
}
|
||
|
||
if (auto declareOp = dyn_cast<fir::DeclareOp>(memrefOp)) {
|
||
FailureOr<Value> converted =
|
||
getFIRConvert(memOp, declareOp, rewriter, typeConverter);
|
||
if (failed(converted)) {
|
||
LLVM_DEBUG(llvm::dbgs()
|
||
<< "FIRToMemRef: unable to create convert for scalar "
|
||
"memref:\n";
|
||
firMemref.dump());
|
||
return failure();
|
||
}
|
||
SmallVector<Value> indices;
|
||
return std::pair{*converted, indices};
|
||
}
|
||
|
||
if (auto coordinateOp = dyn_cast<fir::CoordinateOp>(memrefOp)) {
|
||
// Fast path: coordinate_of used as a plain array indexer on a static-extent
|
||
// scalar array (e.g. a struct component `A%v(i)`).
|
||
if (isArrayIndexingCoordinateOp(coordinateOp, typeConverter)) {
|
||
MemRefInfo memrefInfo = convertCoordinateArrayOp(memOp, coordinateOp,
|
||
rewriter, typeConverter);
|
||
if (succeeded(memrefInfo)) {
|
||
LLVM_DEBUG(llvm::dbgs()
|
||
<< "FIRToMemRef: converted coordinate_of array indexer\n");
|
||
for (auto user : memrefOp->getUsers()) {
|
||
if (!isa<fir::LoadOp, fir::StoreOp>(user)) {
|
||
LLVM_DEBUG(
|
||
llvm::dbgs()
|
||
<< "FIRToMemRef: coordinate_of used by non-load/store, "
|
||
"skipping erase\n";
|
||
firMemref.dump(); user->dump());
|
||
return memrefInfo;
|
||
}
|
||
}
|
||
eraseOps.insert(memrefOp);
|
||
return memrefInfo;
|
||
}
|
||
}
|
||
|
||
// Fallback: struct field access or dynamic array — produce a rank-0 scalar
|
||
// memref from the leaf reference.
|
||
FailureOr<Value> converted =
|
||
getFIRConvert(memOp, coordinateOp, rewriter, typeConverter);
|
||
if (failed(converted)) {
|
||
LLVM_DEBUG(
|
||
llvm::dbgs()
|
||
<< "FIRToMemRef: unable to create convert for derived-type "
|
||
"memref:\n";
|
||
firMemref.dump());
|
||
return failure();
|
||
}
|
||
SmallVector<Value> indices;
|
||
return std::pair{*converted, indices};
|
||
}
|
||
|
||
if (auto convertOp = dyn_cast<fir::ConvertOp>(memrefOp)) {
|
||
Type fromTy = convertOp->getOperand(0).getType();
|
||
Type toTy = firMemref.getType();
|
||
if (isa<fir::ReferenceType>(fromTy) && isa<fir::ReferenceType>(toTy)) {
|
||
FailureOr<Value> converted =
|
||
getFIRConvert(memOp, convertOp, rewriter, typeConverter);
|
||
if (failed(converted)) {
|
||
LLVM_DEBUG(
|
||
llvm::dbgs()
|
||
<< "FIRToMemRef: unable to create convert for conversion "
|
||
"op:\n";
|
||
firMemref.dump());
|
||
return failure();
|
||
}
|
||
SmallVector<Value> indices;
|
||
return std::pair{*converted, indices};
|
||
}
|
||
}
|
||
|
||
if (auto boxAddrOp = dyn_cast<fir::BoxAddrOp>(memrefOp)) {
|
||
FailureOr<Value> converted =
|
||
getFIRConvert(memOp, boxAddrOp, rewriter, typeConverter);
|
||
if (failed(converted)) {
|
||
LLVM_DEBUG(llvm::dbgs()
|
||
<< "FIRToMemRef: unable to create convert for box_addr "
|
||
"op:\n";
|
||
firMemref.dump());
|
||
return failure();
|
||
}
|
||
SmallVector<Value> indices;
|
||
return std::pair{*converted, indices};
|
||
}
|
||
|
||
if (memrefIsDeviceData(memrefOp)) {
|
||
FailureOr<Value> converted =
|
||
getFIRConvert(memOp, memrefOp, rewriter, typeConverter);
|
||
if (failed(converted))
|
||
return failure();
|
||
SmallVector<Value> indices;
|
||
return std::pair{*converted, indices};
|
||
}
|
||
|
||
LLVM_DEBUG(llvm::dbgs()
|
||
<< "FIRToMemRef: unable to create convert for memref value:\n";
|
||
firMemref.dump());
|
||
|
||
return failure();
|
||
}
|
||
|
||
void FIRToMemRef::replaceFIRMemrefs(Value firMemref, Value converted,
|
||
PatternRewriter &rewriter) const {
|
||
Operation *op = firMemref.getDefiningOp();
|
||
if (op && (isa<fir::ArrayCoorOp>(op) || isMarshalLike(op)))
|
||
return;
|
||
|
||
SmallPtrSet<Operation *, 4> worklist;
|
||
for (auto user : firMemref.getUsers()) {
|
||
if (isMarshalLike(user) || isa<fir::LoadOp, fir::StoreOp>(user))
|
||
continue;
|
||
if (!domInfo->dominates(converted, user))
|
||
continue;
|
||
if (!(isa<omp::AtomicCaptureOp>(user->getParentOp()) ||
|
||
isa<acc::AtomicCaptureOp>(user->getParentOp())))
|
||
worklist.insert(user);
|
||
}
|
||
|
||
Type ty = firMemref.getType();
|
||
|
||
for (auto op : worklist) {
|
||
// If op is directly inside a LoopWrapperInterface region, inserting before
|
||
// op would violate the single-nested-op invariant. Walk up the wrapper
|
||
// chain and insert before the outermost wrapper instead.
|
||
Operation *insertBefore = op;
|
||
while (mlir::isa<omp::LoopWrapperInterface>(insertBefore->getParentOp()))
|
||
insertBefore = insertBefore->getParentOp();
|
||
rewriter.setInsertionPoint(insertBefore);
|
||
Location loc = op->getLoc();
|
||
Value replaceConvert = fir::ConvertOp::create(rewriter, loc, ty, converted);
|
||
op->replaceUsesOfWith(firMemref, replaceConvert);
|
||
}
|
||
|
||
worklist.clear();
|
||
|
||
for (auto user : firMemref.getUsers()) {
|
||
if (isMarshalLike(user) || isa<fir::LoadOp, fir::StoreOp>(user))
|
||
continue;
|
||
if (isa<omp::AtomicCaptureOp>(user->getParentOp()) ||
|
||
isa<acc::AtomicCaptureOp>(user->getParentOp()))
|
||
if (domInfo->dominates(converted, user))
|
||
worklist.insert(user);
|
||
}
|
||
|
||
if (worklist.empty())
|
||
return;
|
||
|
||
while (!worklist.empty()) {
|
||
Operation *parentOp = (*worklist.begin())->getParentOp();
|
||
|
||
Value replaceConvert;
|
||
SmallVector<Operation *> erase;
|
||
for (auto op : worklist) {
|
||
if (op->getParentOp() != parentOp)
|
||
continue;
|
||
if (!replaceConvert) {
|
||
rewriter.setInsertionPoint(parentOp);
|
||
replaceConvert =
|
||
fir::ConvertOp::create(rewriter, op->getLoc(), ty, converted);
|
||
}
|
||
op->replaceUsesOfWith(firMemref, replaceConvert);
|
||
erase.push_back(op);
|
||
}
|
||
|
||
for (auto op : erase)
|
||
worklist.erase(op);
|
||
}
|
||
}
|
||
|
||
void FIRToMemRef::rewriteLoadOp(fir::LoadOp load, PatternRewriter &rewriter,
|
||
FIRToMemRefTypeConverter &typeConverter) {
|
||
Value firMemref = load.getMemref();
|
||
if (!typeConverter.convertibleType(firMemref.getType()))
|
||
return;
|
||
|
||
LLVM_DEBUG(llvm::dbgs() << "FIRToMemRef: attempting to convert FIR load:\n";
|
||
load.dump(); firMemref.dump());
|
||
|
||
MemRefInfo memrefInfo =
|
||
getMemRefInfo(firMemref, rewriter, typeConverter, load.getOperation());
|
||
if (failed(memrefInfo))
|
||
return;
|
||
|
||
Type originalType = load.getResult().getType();
|
||
Value converted = memrefInfo->first;
|
||
SmallVector<Value> indices = memrefInfo->second;
|
||
|
||
LLVM_DEBUG(llvm::dbgs()
|
||
<< "FIRToMemRef: convert for FIR load created successfully:\n";
|
||
converted.dump());
|
||
|
||
rewriter.setInsertionPointAfter(load);
|
||
|
||
Attribute attr = (load.getOperation())->getAttr("tbaa");
|
||
memref::LoadOp loadOp =
|
||
rewriter.replaceOpWithNewOp<memref::LoadOp>(load, converted, indices);
|
||
if (attr)
|
||
loadOp.getOperation()->setAttr("tbaa", attr);
|
||
|
||
LLVM_DEBUG(llvm::dbgs() << "FIRToMemRef: new memref.load op:\n";
|
||
loadOp.dump(); assert(succeeded(verify(loadOp))));
|
||
|
||
if (loadOp.getType() != originalType) {
|
||
Value castVal =
|
||
createTypeConversion(rewriter, loadOp.getLoc(), originalType, loadOp);
|
||
loadOp.getResult().replaceAllUsesExcept(castVal, castVal.getDefiningOp());
|
||
}
|
||
|
||
if (!isa<fir::LogicalType>(originalType))
|
||
replaceFIRMemrefs(firMemref, converted, rewriter);
|
||
}
|
||
|
||
void FIRToMemRef::rewriteStoreOp(fir::StoreOp store, PatternRewriter &rewriter,
|
||
FIRToMemRefTypeConverter &typeConverter) {
|
||
Value firMemref = store.getMemref();
|
||
|
||
if (!typeConverter.convertibleType(firMemref.getType()))
|
||
return;
|
||
|
||
LLVM_DEBUG(llvm::dbgs() << "FIRToMemRef: attempting to convert FIR store:\n";
|
||
store.dump(); firMemref.dump());
|
||
|
||
MemRefInfo memrefInfo =
|
||
getMemRefInfo(firMemref, rewriter, typeConverter, store.getOperation());
|
||
if (failed(memrefInfo))
|
||
return;
|
||
|
||
Value converted = memrefInfo->first;
|
||
SmallVector<Value> indices = memrefInfo->second;
|
||
LLVM_DEBUG(
|
||
llvm::dbgs()
|
||
<< "FIRToMemRef: convert for FIR store created successfully:\n";
|
||
converted.dump());
|
||
|
||
Value value = store.getValue();
|
||
rewriter.setInsertionPointAfter(store);
|
||
|
||
Type convertedType = typeConverter.convertType(value.getType());
|
||
if (convertedType != value.getType())
|
||
value =
|
||
createTypeConversion(rewriter, store.getLoc(), convertedType, value);
|
||
|
||
Attribute attr = store.getOperation()->getAttr("tbaa");
|
||
memref::StoreOp storeOp = rewriter.replaceOpWithNewOp<memref::StoreOp>(
|
||
store, value, converted, indices);
|
||
if (attr)
|
||
storeOp.getOperation()->setAttr("tbaa", attr);
|
||
|
||
LLVM_DEBUG(llvm::dbgs() << "FIRToMemRef: new memref.store op:\n";
|
||
storeOp.dump(); assert(succeeded(verify(storeOp))));
|
||
|
||
bool isLogicalRef = false;
|
||
if (fir::ReferenceType refTy =
|
||
llvm::dyn_cast<fir::ReferenceType>(firMemref.getType()))
|
||
isLogicalRef = llvm::isa<fir::LogicalType>(refTy.getEleTy());
|
||
if (!isLogicalRef)
|
||
replaceFIRMemrefs(firMemref, converted, rewriter);
|
||
}
|
||
|
||
// Lower operand and result type of FIR logical operation to get rid
|
||
// of bitcast after loads from memref and before store to memref
|
||
// storing arrays of logicals as integers.
|
||
template <typename Op>
|
||
static void rewriteLogicalOperation(Op op, PatternRewriter &rewriter,
|
||
FIRToMemRefTypeConverter &typeConverter) {
|
||
mlir::Type oldType = op.getResult().getType();
|
||
mlir::Type convertedTy = typeConverter.convertType(oldType);
|
||
if (convertedTy == oldType)
|
||
return;
|
||
rewriter.setInsertionPoint(op);
|
||
mlir::Location loc = op.getLoc();
|
||
// Inserted bitcast from/to logical will be folded with the one created
|
||
// around load/store.
|
||
mlir::Value lhs =
|
||
createTypeConversion(rewriter, loc, convertedTy, op.getLhs());
|
||
mlir::Value rhs =
|
||
createTypeConversion(rewriter, loc, convertedTy, op.getRhs());
|
||
auto newOp = Op::create(rewriter, loc, convertedTy, lhs, rhs);
|
||
mlir::Value result = createTypeConversion(rewriter, loc, oldType, newOp);
|
||
rewriter.replaceOp(op, result);
|
||
}
|
||
|
||
void FIRToMemRef::runOnOperation() {
|
||
LLVM_DEBUG(llvm::dbgs() << "Enter FIRToMemRef()\n");
|
||
|
||
func::FuncOp op = getOperation();
|
||
MLIRContext *context = op.getContext();
|
||
ModuleOp mod = op->getParentOfType<ModuleOp>();
|
||
FIRToMemRefTypeConverter typeConverter(mod);
|
||
|
||
typeConverter.setConvertComplexTypes(true);
|
||
|
||
PatternRewriter rewriter(context);
|
||
domInfo = new DominanceInfo(op);
|
||
|
||
op.walk([&](fir::AllocaOp alloca) {
|
||
rewriteAlloca(alloca, rewriter, typeConverter);
|
||
});
|
||
|
||
op.walk([&](Operation *op) {
|
||
llvm::TypeSwitch<Operation *>(op)
|
||
.Case<fir::LoadOp>([&](auto loadOp) {
|
||
rewriteLoadOp(loadOp, rewriter, typeConverter);
|
||
})
|
||
.Case<fir::StoreOp>([&](auto storeOp) {
|
||
rewriteStoreOp(storeOp, rewriter, typeConverter);
|
||
})
|
||
.Case<fir::LogicalAndOp, fir::LogicalOrOp, fir::EqvOp, fir::NeqvOp>(
|
||
[&](auto logicalOp) {
|
||
rewriteLogicalOperation(logicalOp, rewriter, typeConverter);
|
||
})
|
||
.Default([](Operation *) {});
|
||
});
|
||
|
||
for (auto eraseOp : eraseOps)
|
||
rewriter.eraseOp(eraseOp);
|
||
eraseOps.clear();
|
||
|
||
if (domInfo)
|
||
delete domInfo;
|
||
|
||
LLVM_DEBUG(llvm::dbgs() << "After FIRToMemRef()\n"; op.dump();
|
||
llvm::dbgs() << "Exit FIRToMemRef()\n";);
|
||
}
|
||
|
||
} // namespace fir
|