diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.td b/flang/include/flang/Optimizer/Dialect/FIROps.td index bae52d63fda45..87e0ce33fa78d 100644 --- a/flang/include/flang/Optimizer/Dialect/FIROps.td +++ b/flang/include/flang/Optimizer/Dialect/FIROps.td @@ -806,7 +806,8 @@ def fir_HasValueOp : fir_Op<"has_value", [Terminator, HasParent<"GlobalOp">]> { // Operations on !fir.box type objects //===----------------------------------------------------------------------===// -def fir_EmboxOp : fir_Op<"embox", [NoMemoryEffect, AttrSizedOperandSegments]> { +def fir_EmboxOp : fir_Op<"embox", [NoMemoryEffect, AttrSizedOperandSegments, + fir_FortranObjectViewOpInterface]> { let summary = "boxes a given reference and (optional) dimension information"; let description = [{ @@ -870,10 +871,14 @@ def fir_EmboxOp : fir_Op<"embox", [NoMemoryEffect, AttrSizedOperandSegments]> { return 1 + (getShape() ? 1 : 0) + (getSlice() ? 1 : 0) + numLenParams(); } + // FortranObjectViewOpInterface methods: + mlir::Value getViewSource(mlir::OpResult) { return getMemref(); } + std::optional getViewOffset(mlir::OpResult); }]; } -def fir_ReboxOp : fir_Op<"rebox", [NoMemoryEffect, AttrSizedOperandSegments]> { +def fir_ReboxOp : fir_Op<"rebox", [NoMemoryEffect, AttrSizedOperandSegments, + fir_FortranObjectViewOpInterface]> { let summary = "create a box given another box and (optional) dimension information"; @@ -923,6 +928,12 @@ def fir_ReboxOp : fir_Op<"rebox", [NoMemoryEffect, AttrSizedOperandSegments]> { }]; let hasVerifier = 1; + + let extraClassDeclaration = [{ + // FortranObjectViewOpInterface methods: + mlir::Value getViewSource(mlir::OpResult) { return getBox(); } + std::optional getViewOffset(mlir::OpResult); + }]; } def fir_ReboxAssumedRankOp : fir_Op<"rebox_assumed_rank", @@ -1071,7 +1082,9 @@ def fir_UnboxProcOp : fir_SimpleOp<"unboxproc", [NoMemoryEffect]> { let results = (outs FunctionType, fir_ReferenceType:$refTuple); } -def fir_BoxAddrOp : fir_SimpleOneResultOp<"box_addr", [NoMemoryEffect]> { +def fir_BoxAddrOp + : fir_SimpleOneResultOp<"box_addr", [NoMemoryEffect, + fir_FortranObjectViewOpInterface]> { let summary = "return a memory reference to the boxed value"; let description = [{ @@ -1094,6 +1107,12 @@ def fir_BoxAddrOp : fir_SimpleOneResultOp<"box_addr", [NoMemoryEffect]> { let hasFolder = 1; let builders = [OpBuilder<(ins "mlir::Value":$val)>]; + + let extraClassDeclaration = [{ + // FortranObjectViewOpInterface methods: + mlir::Value getViewSource(mlir::OpResult) { return getVal(); } + std::optional getViewOffset(mlir::OpResult); + }]; } def fir_BoxCharLenOp : fir_SimpleOp<"boxchar_len", [NoMemoryEffect]> { @@ -1766,8 +1785,9 @@ def fir_ArrayMergeStoreOp : fir_Op<"array_merge_store", // Record and array type operations //===----------------------------------------------------------------------===// -def fir_ArrayCoorOp : fir_Op<"array_coor", - [NoMemoryEffect, AttrSizedOperandSegments]> { +def fir_ArrayCoorOp + : fir_Op<"array_coor", [NoMemoryEffect, AttrSizedOperandSegments, + fir_FortranObjectViewOpInterface]> { let summary = "Find the coordinate of an element of an array"; @@ -1810,9 +1830,16 @@ def fir_ArrayCoorOp : fir_Op<"array_coor", let hasVerifier = 1; let hasCanonicalizer = 1; + let extraClassDeclaration = [{ + // FortranObjectViewOpInterface methods: + mlir::Value getViewSource(mlir::OpResult) { return getMemref(); } + std::optional getViewOffset(mlir::OpResult); + }]; } -def fir_CoordinateOp : fir_Op<"coordinate_of", [NoMemoryEffect]> { +def fir_CoordinateOp + : fir_Op<"coordinate_of", [NoMemoryEffect, + fir_FortranObjectViewOpInterface]> { let summary = "Finds the coordinate (location) of a value in memory"; @@ -1864,6 +1891,10 @@ def fir_CoordinateOp : fir_Op<"coordinate_of", [NoMemoryEffect]> { let extraClassDeclaration = [{ constexpr static int32_t kDynamicIndex = std::numeric_limits::min(); CoordinateIndicesAdaptor getIndices(); + + // FortranObjectViewOpInterface methods: + mlir::Value getViewSource(mlir::OpResult) { return getRef(); } + std::optional getViewOffset(mlir::OpResult); }]; } @@ -2830,7 +2861,8 @@ def fir_VolatileCastOp : fir_SimpleOneResultOp<"volatile_cast", [Pure]> { } def fir_ConvertOp - : fir_SimpleOneResultOp<"convert", [NoMemoryEffect, ViewLikeOpInterface]> { + : fir_SimpleOneResultOp<"convert", [NoMemoryEffect, ViewLikeOpInterface, + fir_FortranObjectViewOpInterface]> { let summary = "encapsulates all Fortran entity type conversions"; let description = [{ @@ -2868,7 +2900,13 @@ def fir_ConvertOp static bool isPointerCompatible(mlir::Type ty); static bool canBeConverted(mlir::Type inType, mlir::Type outType); static bool areVectorsCompatible(mlir::Type inTy, mlir::Type outTy); + + // ViewLikeOpInterface methods: mlir::Value getViewSource() { return getValue(); } + + // FortranObjectViewOpInterface methods: + mlir::Value getViewSource(mlir::OpResult) { return getValue(); } + std::optional getViewOffset(mlir::OpResult) { return 0; } }]; let hasCanonicalizer = 1; } @@ -3221,7 +3259,8 @@ def fir_DeclareOp : fir_Op<"declare", [AttrSizedOperandSegments, MemoryEffects<[MemAlloc]>, DeclareOpInterfaceMethods< - fir_FortranVariableStorageOpInterface>]> { + fir_FortranVariableStorageOpInterface>, + fir_FortranObjectViewOpInterface]> { let summary = "declare a variable"; let description = [{ @@ -3285,6 +3324,12 @@ def fir_DeclareOp attr-dict `:` functional-type(operands, results) }]; + let extraClassDeclaration = [{ + // FortranObjectViewOpInterface methods: + mlir::Value getViewSource(mlir::OpResult) { return getMemref(); } + std::optional getViewOffset(mlir::OpResult) { return 0; } + }]; + let hasVerifier = 1; } diff --git a/flang/include/flang/Optimizer/Dialect/FortranVariableInterface.td b/flang/include/flang/Optimizer/Dialect/FortranVariableInterface.td index bd65a042e6dba..60a7470813321 100644 --- a/flang/include/flang/Optimizer/Dialect/FortranVariableInterface.td +++ b/flang/include/flang/Optimizer/Dialect/FortranVariableInterface.td @@ -265,4 +265,59 @@ def fir_FortranVariableStorageOpInterface [{ return detail::verifyFortranVariableStorageOpInterface($_op); }]; } +def fir_FortranObjectViewOpInterface + : OpInterface<"FortranObjectViewOpInterface"> { + let description = [{ + Interface for operations that may produce results that allow accessing + objects in memory based on the operands of these operations. + For example: + ``` + %0 = fir.convert %x : (!llvm.ptr) -> !fir.llvm_ptr + ``` + When the result of `fir.convert` is used to access an object in memory, + FIR alias analysis may want to pass through `fir.convert` to be able + to find the original Fortran object that is being accessed. + This interface provides methods that allow discovering information + about the accessed object and the characteristics of the resulting + "view" of the object, e.g. whether the result and the input + access the object from the same location within the object or + whether they are offsetted (which may happen, for example, in case of + `fir.array_coor` operation). + }]; + let cppNamespace = "::fir"; + + let methods = + [InterfaceMethod< + /*desc=*/ + [{ Returns the operand which the given OpResult is based on. }], + /*retTy=*/"::mlir::Value", + /*methodName=*/"getViewSource", + /*args=*/(ins "::mlir::OpResult":$resultView), + /*methodBody=*/"", + /*defaultImplementation=*/[{ + assert($_op.getOperation() == resultView.getOwner() && + "resultView must be a result of this operation"); + return $_op.getViewSource(resultView); + }]>, + InterfaceMethod< + /*desc=*/[{ + Returns the constant offset (in bytes) applied to the corresponding + operand to produce the resulting view. + If it is not possible to identify the constant offset, + the result in nullopt. + Note that the offset may be negative, e.g. when addressing + an array section with a negative stride. + }], + /*retTy=*/"std::optional", + /*methodName=*/"getViewOffset", + /*args=*/(ins "::mlir::OpResult":$resultView), + /*methodBody=*/"", + /*defaultImplementation=*/[{ + assert($_op.getOperation() == resultView.getOwner() && + "resultView must be a result of this operation"); + return $_op.getViewOffset(resultView); + }]>, + ]; +} + #endif // FORTRANVARIABLEINTERFACE diff --git a/flang/include/flang/Optimizer/HLFIR/HLFIROps.td b/flang/include/flang/Optimizer/HLFIR/HLFIROps.td index b7563a2f752f0..7cdf6646babb1 100644 --- a/flang/include/flang/Optimizer/HLFIR/HLFIROps.td +++ b/flang/include/flang/Optimizer/HLFIR/HLFIROps.td @@ -39,7 +39,8 @@ def hlfir_DeclareOp : hlfir_Op<"declare", [AttrSizedOperandSegments, MemoryEffects<[MemAlloc]>, DeclareOpInterfaceMethods< - fir_FortranVariableStorageOpInterface>]> { + fir_FortranVariableStorageOpInterface>, + fir_FortranObjectViewOpInterface]> { let summary = "declare a variable and produce an SSA value that can be used as a variable in HLFIR operations"; let description = [{ @@ -140,6 +141,10 @@ def hlfir_DeclareOp /// Given a FIR memory type, and information about non default lower /// bounds, get the related HLFIR variable type. static mlir::Type getHLFIRVariableType(mlir::Type type, bool hasLowerBounds); + + // FortranObjectViewOpInterface methods: + mlir::Value getViewSource(mlir::OpResult) { return getMemref(); } + std::optional getViewOffset(mlir::OpResult) { return 0; } }]; let hasVerifier = 1; @@ -213,8 +218,11 @@ def fir_AssignOp : hlfir_Op<"assign", [DeclareOpInterfaceMethods, NoMemoryEffect]> { +def hlfir_DesignateOp + : hlfir_Op<"designate", + [AttrSizedOperandSegments, + DeclareOpInterfaceMethods, + NoMemoryEffect, fir_FortranObjectViewOpInterface]> { let summary = "Designate a Fortran variable"; let description = [{ @@ -278,6 +286,9 @@ def hlfir_DesignateOp : hlfir_Op<"designate", [AttrSizedOperandSegments, void setFortranAttrs(fir::FortranVariableFlagsEnum flags) { this->setFortranAttrs(std::optional(flags)); } + // FortranObjectViewOpInterface methods: + mlir::Value getViewSource(mlir::OpResult) { return getMemref(); } + std::optional getViewOffset(mlir::OpResult); }]; let builders = [ @@ -938,8 +949,9 @@ def hlfir_EndAssociateOp : hlfir_Op<"end_associate", [MemoryEffects<[MemFree]>]> let hasVerifier = 1; } -def hlfir_AsExprOp : hlfir_Op<"as_expr", - [DeclareOpInterfaceMethods]> { +def hlfir_AsExprOp + : hlfir_Op<"as_expr", [DeclareOpInterfaceMethods, + fir_FortranObjectViewOpInterface]> { let summary = "Take the value of an array, character or derived variable"; let description = [{ @@ -961,8 +973,11 @@ def hlfir_AsExprOp : hlfir_Op<"as_expr", let results = (outs hlfir_ExprType); let extraClassDeclaration = [{ - // Is this a "move" ? - bool isMove() { return getMustFree() != mlir::Value{}; } + // Is this a "move" ? + bool isMove() { return getMustFree() != mlir::Value{}; } + // FortranObjectViewOpInterface methods: + mlir::Value getViewSource(mlir::OpResult) { return getVar(); } + std::optional getViewOffset(mlir::OpResult) { return 0; } }]; let assemblyFormat = [{ diff --git a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp index 73ddd1ff80126..a45f6f7b8ea89 100644 --- a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp +++ b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp @@ -214,6 +214,17 @@ AliasResult AliasAnalysis::alias(Source lhsSrc, Source rhsSrc, mlir::Value lhs, << " aliasing because same source kind and origin\n"); if (approximateSource) return AliasResult::MayAlias; + // One should be careful about relying on MustAlias. + // The LLVM definition implies that the two MustAlias + // memory objects start at exactly the same location. + // With Fortran array slices two objects may have + // the same starting location, but otherwise represent + // partially overlapping memory locations, e.g.: + // integer :: a(10) + // ... a(5:1:-1) ! starts at a(5) and addresses a(5), ..., a(1) + // ... a(5:10:1) ! starts at a(5) and addresses a(5), ..., a(10) + // The current implementation of FIR alias analysis will always + // return MayAlias for such cases. return AliasResult::MustAlias; } // If one value is the address of a composite, and if the other value is the @@ -534,13 +545,16 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v, Source::Attributes attributes; mlir::Operation *instantiationPoint{nullptr}; while (defOp && !breakFromLoop) { - ty = defOp->getResultTypes()[0]; + // Operations may have multiple results, so we need to analyze + // the result for which the source is queried. + auto opResult = mlir::cast(v); + assert(opResult.getOwner() == defOp && "v must be a result of defOp"); + ty = opResult.getType(); llvm::TypeSwitch(defOp) - .Case([&](auto op) { - v = op.getVar(); - defOp = v.getDefiningOp(); - }) .Case([&](auto op) { + assert(opResult != op.getMustFreeStrorageFlag() && + "MustFreeStorageFlag result is not an aliasing candidate"); + mlir::Value source = op.getSource(); if (fir::isa_trivial(source.getType())) { // Trivial values will always use distinct temp memory, @@ -559,11 +573,6 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v, type = SourceKind::Allocate; breakFromLoop = true; }) - .Case([&](auto op) { - // Skip ConvertOp's and track further through the operand. - v = op->getOperand(0); - defOp = v.getDefiningOp(); - }) .Case([&](auto op) { // The packed array is not distinguishable from the original // array, so skip PackArrayOp and track further through @@ -572,28 +581,6 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v, defOp = v.getDefiningOp(); approximateSource = true; }) - .Case([&](auto op) { - v = op->getOperand(0); - defOp = v.getDefiningOp(); - if (mlir::isa(v.getType())) - followBoxData = true; - }) - .Case([&](auto op) { - if (isPointerReference(ty)) - attributes.set(Attribute::Pointer); - v = op->getOperand(0); - defOp = v.getDefiningOp(); - if (mlir::isa(v.getType())) - followBoxData = true; - approximateSource = true; - }) - .Case([&](auto op) { - if (followBoxData) { - v = op->getOperand(0); - defOp = v.getDefiningOp(); - } else - breakFromLoop = true; - }) .Case([&](auto op) { // If load is inside target and it points to mapped item, // continue tracking. @@ -663,6 +650,9 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v, breakFromLoop = true; }) .Case([&](auto op) { + // The declare operations support FortranObjectViewOpInterface, + // but their handling is more complex. Maybe we can find better + // abstractions to handle them in a general fashion. bool isPrivateItem = false; if (omp::BlockArgOpenMPOpInterface argIface = dyn_cast(op->getParentOp())) { @@ -713,7 +703,7 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v, // 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); + v = opResult; // 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. @@ -748,21 +738,45 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v, v = op.getMemref(); defOp = v.getDefiningOp(); }) - .Case([&](auto op) { - auto varIf = llvm::cast(defOp); - attributes |= getAttrsFromVariable(varIf); - // 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(); + .Case([&](auto op) { + // This case must be located after the cases for concrete + // operations that support FortraObjectViewOpInterface, + // so that their special handling kicks in. + + // fir.embox/rebox case: this is the only case where we check + // for followBoxData. + // TODO: it looks like we do not have LIT tests that fail + // upon removal of the followBoxData code. We should come up + // with a test or remove this code. + if (!followBoxData && + (mlir::isa(op) || mlir::isa(op))) { + breakFromLoop = true; + return; + } + + // Collect attributes from FortranVariableOpInterface operations. + if (auto varIf = + mlir::dyn_cast(defOp)) + attributes |= getAttrsFromVariable(varIf); + // Set Pointer attribute based on the reference type. + if (isPointerReference(ty)) + attributes.set(Attribute::Pointer); + + // Update v to point to the operand that represents the object + // referenced by the operation's result. + v = op.getViewSource(opResult); 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(v.getType())) + // If the input the resulting object references are offsetted, + // then set approximateSource. + auto offset = op.getViewOffset(opResult); + if (!offset || *offset != 0) + approximateSource = true; + + // If the source is a box, and the result is not a box, + // then this is one of the box "unpacking" operations, + // so we should set followBoxData. + if (mlir::isa(v.getType()) && + !mlir::isa(ty)) followBoxData = true; }) .Default([&](auto op) { diff --git a/flang/lib/Optimizer/Dialect/FIROps.cpp b/flang/lib/Optimizer/Dialect/FIROps.cpp index 4f97acaa88b7a..6895736138eb0 100644 --- a/flang/lib/Optimizer/Dialect/FIROps.cpp +++ b/flang/lib/Optimizer/Dialect/FIROps.cpp @@ -834,6 +834,11 @@ void fir::ArrayCoorOp::getCanonicalizationPatterns( patterns.add(context); } +std::optional fir::ArrayCoorOp::getViewOffset(mlir::OpResult) { + // TODO: we can try to compute the constant offset. + return std::nullopt; +} + //===----------------------------------------------------------------------===// // ArrayLoadOp //===----------------------------------------------------------------------===// @@ -1086,6 +1091,13 @@ mlir::OpFoldResult fir::BoxAddrOp::fold(FoldAdaptor adaptor) { return {}; } +std::optional fir::BoxAddrOp::getViewOffset(mlir::OpResult) { + // fir.box_addr just returns the base address stored inside a box, + // so the direct accesses through the base address and through the box + // are not offsetted. + return 0; +} + //===----------------------------------------------------------------------===// // BoxCharLenOp //===----------------------------------------------------------------------===// @@ -1820,6 +1832,11 @@ fir::CoordinateIndicesAdaptor fir::CoordinateOp::getIndices() { return CoordinateIndicesAdaptor(getFieldIndicesAttr(), getCoor()); } +std::optional fir::CoordinateOp::getViewOffset(mlir::OpResult) { + // TODO: we can try to compute the constant offset. + return std::nullopt; +} + //===----------------------------------------------------------------------===// // DispatchOp //===----------------------------------------------------------------------===// @@ -2066,6 +2083,14 @@ bool fir::isContiguousEmbox(fir::EmboxOp embox, bool checkWhole) { return false; } +std::optional fir::EmboxOp::getViewOffset(mlir::OpResult) { + // The address offset is zero, unless there is a slice. + // TODO: we can handle slices that leave the base address untouched. + if (!getSlice()) + return 0; + return std::nullopt; +} + //===----------------------------------------------------------------------===// // EmboxCharOp //===----------------------------------------------------------------------===// @@ -3313,6 +3338,14 @@ llvm::LogicalResult fir::ReboxOp::verify() { return mlir::success(); } +std::optional fir::ReboxOp::getViewOffset(mlir::OpResult) { + // The address offset is zero, unless there is a slice. + // TODO: we can handle slices that leave the base address untouched. + if (!getSlice()) + return 0; + return std::nullopt; +} + //===----------------------------------------------------------------------===// // ReboxAssumedRankOp //===----------------------------------------------------------------------===// diff --git a/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp b/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp index 1332dc57fb086..c305cc5242ef3 100644 --- a/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp +++ b/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp @@ -591,6 +591,12 @@ llvm::LogicalResult hlfir::DesignateOp::verify() { return mlir::success(); } +std::optional hlfir::DesignateOp::getViewOffset(mlir::OpResult) { + // TODO: we can compute the constant offset + // based on the component/indices/etc. + return std::nullopt; +} + //===----------------------------------------------------------------------===// // ParentComponentOp //===----------------------------------------------------------------------===//