| 1 | //===- ConvertToFIR.cpp - Convert HLFIR to FIR ----------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // This file defines a pass to lower HLFIR to FIR |
| 9 | //===----------------------------------------------------------------------===// |
| 10 | |
| 11 | #include "flang/Optimizer/Builder/Character.h" |
| 12 | #include "flang/Optimizer/Builder/FIRBuilder.h" |
| 13 | #include "flang/Optimizer/Builder/HLFIRTools.h" |
| 14 | #include "flang/Optimizer/Builder/MutableBox.h" |
| 15 | #include "flang/Optimizer/Builder/Runtime/Assign.h" |
| 16 | #include "flang/Optimizer/Builder/Runtime/Derived.h" |
| 17 | #include "flang/Optimizer/Builder/Runtime/Inquiry.h" |
| 18 | #include "flang/Optimizer/Builder/Todo.h" |
| 19 | #include "flang/Optimizer/Dialect/FIRDialect.h" |
| 20 | #include "flang/Optimizer/Dialect/FIROps.h" |
| 21 | #include "flang/Optimizer/Dialect/FIRType.h" |
| 22 | #include "flang/Optimizer/Dialect/Support/FIRContext.h" |
| 23 | #include "flang/Optimizer/HLFIR/HLFIROps.h" |
| 24 | #include "flang/Optimizer/HLFIR/Passes.h" |
| 25 | #include "mlir/Transforms/DialectConversion.h" |
| 26 | |
| 27 | namespace hlfir { |
| 28 | #define GEN_PASS_DEF_CONVERTHLFIRTOFIR |
| 29 | #include "flang/Optimizer/HLFIR/Passes.h.inc" |
| 30 | } // namespace hlfir |
| 31 | |
| 32 | using namespace mlir; |
| 33 | |
| 34 | namespace { |
| 35 | /// May \p lhs alias with \p rhs? |
| 36 | /// TODO: implement HLFIR alias analysis. |
| 37 | class AssignOpConversion : public mlir::OpRewritePattern<hlfir::AssignOp> { |
| 38 | public: |
| 39 | explicit AssignOpConversion(mlir::MLIRContext *ctx) : OpRewritePattern{ctx} {} |
| 40 | |
| 41 | llvm::LogicalResult |
| 42 | matchAndRewrite(hlfir::AssignOp assignOp, |
| 43 | mlir::PatternRewriter &rewriter) const override { |
| 44 | mlir::Location loc = assignOp->getLoc(); |
| 45 | hlfir::Entity lhs(assignOp.getLhs()); |
| 46 | hlfir::Entity rhs(assignOp.getRhs()); |
| 47 | auto module = assignOp->getParentOfType<mlir::ModuleOp>(); |
| 48 | fir::FirOpBuilder builder(rewriter, module); |
| 49 | |
| 50 | if (mlir::isa<hlfir::ExprType>(rhs.getType())) { |
| 51 | mlir::emitError(loc, "hlfir must be bufferized with --bufferize-hlfir " |
| 52 | "pass before being converted to FIR" ); |
| 53 | return mlir::failure(); |
| 54 | } |
| 55 | auto [rhsExv, rhsCleanUp] = |
| 56 | hlfir::translateToExtendedValue(loc, builder, rhs); |
| 57 | auto [lhsExv, lhsCleanUp] = |
| 58 | hlfir::translateToExtendedValue(loc, builder, lhs); |
| 59 | assert(!lhsCleanUp && !rhsCleanUp && |
| 60 | "variable to fir::ExtendedValue must not require cleanup" ); |
| 61 | |
| 62 | auto emboxRHS = [&](fir::ExtendedValue &rhsExv) -> mlir::Value { |
| 63 | // There may be overlap between lhs and rhs. The runtime is able to detect |
| 64 | // and to make a copy of the rhs before modifying the lhs if needed. |
| 65 | // The code below relies on this and does not do any compile time alias |
| 66 | // analysis. |
| 67 | const bool rhsIsValue = fir::isa_trivial(fir::getBase(rhsExv).getType()); |
| 68 | if (rhsIsValue) { |
| 69 | // createBox can only be called for fir::ExtendedValue that are |
| 70 | // already in memory. Place the integer/real/complex/logical scalar |
| 71 | // in memory. |
| 72 | // The RHS might be i1, which is not supported for emboxing. |
| 73 | // If LHS is not polymorphic, we may cast the RHS to the LHS type |
| 74 | // before emboxing. If LHS is polymorphic we have to figure out |
| 75 | // the data type for RHS emboxing anyway. |
| 76 | // It is probably a good idea to make sure that the data type |
| 77 | // of the RHS is always a valid Fortran storage data type. |
| 78 | // For the time being, just handle i1 explicitly here. |
| 79 | mlir::Type rhsType = rhs.getFortranElementType(); |
| 80 | mlir::Value rhsVal = fir::getBase(rhsExv); |
| 81 | if (rhsType == builder.getI1Type()) { |
| 82 | rhsType = fir::LogicalType::get(builder.getContext(), 4); |
| 83 | rhsVal = builder.createConvert(loc, rhsType, rhsVal); |
| 84 | } |
| 85 | mlir::Value temp = builder.create<fir::AllocaOp>(loc, rhsType); |
| 86 | builder.create<fir::StoreOp>(loc, rhsVal, temp); |
| 87 | rhsExv = temp; |
| 88 | } |
| 89 | return fir::getBase(builder.createBox(loc, rhsExv)); |
| 90 | }; |
| 91 | |
| 92 | if (assignOp.isAllocatableAssignment()) { |
| 93 | // Whole allocatable assignment: use the runtime to deal with the |
| 94 | // reallocation. |
| 95 | mlir::Value from = emboxRHS(rhsExv); |
| 96 | mlir::Value to = fir::getBase(lhsExv); |
| 97 | if (assignOp.mustKeepLhsLengthInAllocatableAssignment()) { |
| 98 | // Indicate the runtime that it should not reallocate in case of length |
| 99 | // mismatch, and that it should use the LHS explicit/assumed length if |
| 100 | // allocating/reallocation the LHS. |
| 101 | // Note that AssignExplicitLengthCharacter() must be used |
| 102 | // when isTemporaryLHS() is true here: the LHS is known to be |
| 103 | // character allocatable in this case, so finalization will not |
| 104 | // happen (as implied by temporary_lhs attribute), and LHS |
| 105 | // must keep its length (as implied by keep_lhs_length_if_realloc). |
| 106 | fir::runtime::genAssignExplicitLengthCharacter(builder, loc, to, from); |
| 107 | } else if (assignOp.isTemporaryLHS()) { |
| 108 | // Use AssignTemporary, when the LHS is a compiler generated temporary. |
| 109 | // Note that it also works properly for polymorphic LHS (i.e. the LHS |
| 110 | // will have the RHS dynamic type after the assignment). |
| 111 | fir::runtime::genAssignTemporary(builder, loc, to, from); |
| 112 | } else if (lhs.isPolymorphic()) { |
| 113 | // Indicate the runtime that the LHS must have the RHS dynamic type |
| 114 | // after the assignment. |
| 115 | fir::runtime::genAssignPolymorphic(builder, loc, to, from); |
| 116 | } else { |
| 117 | fir::runtime::genAssign(builder, loc, to, from); |
| 118 | } |
| 119 | } else if (lhs.isArray() || |
| 120 | // Special case for element-by-element (or scalar) assignments |
| 121 | // generated for creating polymorphic expressions. |
| 122 | // The LHS of these assignments is a box describing just |
| 123 | // a single element, not the whole allocatable temp. |
| 124 | // They do not have 'realloc' attribute, because reallocation |
| 125 | // must not happen. The only expected effect of such an |
| 126 | // assignment is the copy of the contents, because the dynamic |
| 127 | // types of the LHS and the RHS must match already. We use the |
| 128 | // runtime in this case so that the polymorphic (including |
| 129 | // unlimited) content is copied properly. |
| 130 | (lhs.isPolymorphic() && assignOp.isTemporaryLHS())) { |
| 131 | // Use the runtime for simplicity. An optimization pass will be added to |
| 132 | // inline array assignment when profitable. |
| 133 | mlir::Value from = emboxRHS(rhsExv); |
| 134 | mlir::Value to = fir::getBase(builder.createBox(loc, lhsExv)); |
| 135 | // This is not a whole allocatable assignment: the runtime will not |
| 136 | // reallocate and modify "toMutableBox" even if it is taking it by |
| 137 | // reference. |
| 138 | auto toMutableBox = builder.createTemporary(loc, to.getType()); |
| 139 | builder.create<fir::StoreOp>(loc, to, toMutableBox); |
| 140 | if (assignOp.isTemporaryLHS()) |
| 141 | fir::runtime::genAssignTemporary(builder, loc, toMutableBox, from); |
| 142 | else |
| 143 | fir::runtime::genAssign(builder, loc, toMutableBox, from); |
| 144 | } else { |
| 145 | // TODO: use the type specification to see if IsFinalizable is set, |
| 146 | // or propagate IsFinalizable attribute from lowering. |
| 147 | bool needFinalization = |
| 148 | !assignOp.isTemporaryLHS() && |
| 149 | mlir::isa<fir::RecordType>(fir::getElementTypeOf(lhsExv)); |
| 150 | |
| 151 | // genScalarAssignment() must take care of potential overlap |
| 152 | // between LHS and RHS. Note that the overlap is possible |
| 153 | // also for components of LHS/RHS, and the Assign() runtime |
| 154 | // must take care of it. |
| 155 | fir::factory::genScalarAssignment(builder, loc, lhsExv, rhsExv, |
| 156 | needFinalization, |
| 157 | assignOp.isTemporaryLHS()); |
| 158 | } |
| 159 | rewriter.eraseOp(assignOp); |
| 160 | return mlir::success(); |
| 161 | } |
| 162 | }; |
| 163 | |
| 164 | class CopyInOpConversion : public mlir::OpRewritePattern<hlfir::CopyInOp> { |
| 165 | public: |
| 166 | explicit CopyInOpConversion(mlir::MLIRContext *ctx) : OpRewritePattern{ctx} {} |
| 167 | |
| 168 | struct CopyInResult { |
| 169 | mlir::Value addr; |
| 170 | mlir::Value wasCopied; |
| 171 | }; |
| 172 | |
| 173 | static CopyInResult genNonOptionalCopyIn(mlir::Location loc, |
| 174 | fir::FirOpBuilder &builder, |
| 175 | hlfir::CopyInOp copyInOp) { |
| 176 | mlir::Value inputVariable = copyInOp.getVar(); |
| 177 | mlir::Type resultAddrType = copyInOp.getCopiedIn().getType(); |
| 178 | mlir::Value isContiguous = |
| 179 | fir::runtime::genIsContiguous(builder, loc, inputVariable); |
| 180 | mlir::Value addr = |
| 181 | builder |
| 182 | .genIfOp(loc, {resultAddrType}, isContiguous, |
| 183 | /*withElseRegion=*/true) |
| 184 | .genThen( |
| 185 | [&]() { builder.create<fir::ResultOp>(loc, inputVariable); }) |
| 186 | .genElse([&] { |
| 187 | // Create temporary on the heap. Note that the runtime is used and |
| 188 | // that is desired: since the data copy happens under a runtime |
| 189 | // check (for IsContiguous) the copy loops can hardly provide any |
| 190 | // value to optimizations, instead, the optimizer just wastes |
| 191 | // compilation time on these loops. |
| 192 | mlir::Value temp = copyInOp.getTempBox(); |
| 193 | fir::runtime::genCopyInAssign(builder, loc, temp, inputVariable); |
| 194 | mlir::Value copy = builder.create<fir::LoadOp>(loc, temp); |
| 195 | // Get rid of allocatable flag in the fir.box. |
| 196 | if (mlir::cast<fir::BaseBoxType>(resultAddrType).isAssumedRank()) |
| 197 | copy = builder.create<fir::ReboxAssumedRankOp>( |
| 198 | loc, resultAddrType, copy, |
| 199 | fir::LowerBoundModifierAttribute::Preserve); |
| 200 | else |
| 201 | copy = builder.create<fir::ReboxOp>(loc, resultAddrType, copy, |
| 202 | /*shape=*/mlir::Value{}, |
| 203 | /*slice=*/mlir::Value{}); |
| 204 | builder.create<fir::ResultOp>(loc, copy); |
| 205 | }) |
| 206 | .getResults()[0]; |
| 207 | return {addr, builder.genNot(loc, isContiguous)}; |
| 208 | } |
| 209 | |
| 210 | static CopyInResult genOptionalCopyIn(mlir::Location loc, |
| 211 | fir::FirOpBuilder &builder, |
| 212 | hlfir::CopyInOp copyInOp) { |
| 213 | mlir::Type resultAddrType = copyInOp.getCopiedIn().getType(); |
| 214 | mlir::Value isPresent = copyInOp.getVarIsPresent(); |
| 215 | auto res = |
| 216 | builder |
| 217 | .genIfOp(loc, {resultAddrType, builder.getI1Type()}, isPresent, |
| 218 | /*withElseRegion=*/true) |
| 219 | .genThen([&]() { |
| 220 | CopyInResult res = genNonOptionalCopyIn(loc, builder, copyInOp); |
| 221 | builder.create<fir::ResultOp>( |
| 222 | loc, mlir::ValueRange{res.addr, res.wasCopied}); |
| 223 | }) |
| 224 | .genElse([&] { |
| 225 | mlir::Value absent = |
| 226 | builder.create<fir::AbsentOp>(loc, resultAddrType); |
| 227 | builder.create<fir::ResultOp>( |
| 228 | loc, mlir::ValueRange{absent, isPresent}); |
| 229 | }) |
| 230 | .getResults(); |
| 231 | return {res[0], res[1]}; |
| 232 | } |
| 233 | |
| 234 | llvm::LogicalResult |
| 235 | matchAndRewrite(hlfir::CopyInOp copyInOp, |
| 236 | mlir::PatternRewriter &rewriter) const override { |
| 237 | mlir::Location loc = copyInOp.getLoc(); |
| 238 | fir::FirOpBuilder builder(rewriter, copyInOp.getOperation()); |
| 239 | CopyInResult result = copyInOp.getVarIsPresent() |
| 240 | ? genOptionalCopyIn(loc, builder, copyInOp) |
| 241 | : genNonOptionalCopyIn(loc, builder, copyInOp); |
| 242 | rewriter.replaceOp(copyInOp, {result.addr, result.wasCopied}); |
| 243 | return mlir::success(); |
| 244 | } |
| 245 | }; |
| 246 | |
| 247 | class CopyOutOpConversion : public mlir::OpRewritePattern<hlfir::CopyOutOp> { |
| 248 | public: |
| 249 | explicit CopyOutOpConversion(mlir::MLIRContext *ctx) |
| 250 | : OpRewritePattern{ctx} {} |
| 251 | |
| 252 | llvm::LogicalResult |
| 253 | matchAndRewrite(hlfir::CopyOutOp copyOutOp, |
| 254 | mlir::PatternRewriter &rewriter) const override { |
| 255 | mlir::Location loc = copyOutOp.getLoc(); |
| 256 | fir::FirOpBuilder builder(rewriter, copyOutOp.getOperation()); |
| 257 | |
| 258 | builder.genIfThen(loc, copyOutOp.getWasCopied()) |
| 259 | .genThen([&]() { |
| 260 | mlir::Value temp = copyOutOp.getTemp(); |
| 261 | mlir::Value varMutableBox; |
| 262 | // Generate CopyOutAssign runtime call. |
| 263 | if (mlir::Value var = copyOutOp.getVar()) { |
| 264 | // Set the variable descriptor pointer in order to copy data from |
| 265 | // the temporary to the actualArg. Note that in case the actual |
| 266 | // argument is ALLOCATABLE/POINTER the CopyOutAssign() |
| 267 | // implementation should not engage its reallocation, because the |
| 268 | // temporary is rank, shape and type compatible with it. Moreover, |
| 269 | // CopyOutAssign() guarantees that there will be no finalization for |
| 270 | // the LHS even if it is of a derived type with finalization. |
| 271 | varMutableBox = builder.createTemporary(loc, var.getType()); |
| 272 | builder.create<fir::StoreOp>(loc, var, varMutableBox); |
| 273 | } else { |
| 274 | // Even when there is no need to copy back the data (e.g., the dummy |
| 275 | // argument was intent(in), CopyOutAssign is called to |
| 276 | // destroy/deallocate the temporary. |
| 277 | varMutableBox = builder.create<fir::ZeroOp>(loc, temp.getType()); |
| 278 | } |
| 279 | fir::runtime::genCopyOutAssign(builder, loc, varMutableBox, |
| 280 | copyOutOp.getTemp()); |
| 281 | }) |
| 282 | .end(); |
| 283 | rewriter.eraseOp(copyOutOp); |
| 284 | return mlir::success(); |
| 285 | } |
| 286 | }; |
| 287 | |
| 288 | class DeclareOpConversion : public mlir::OpRewritePattern<hlfir::DeclareOp> { |
| 289 | public: |
| 290 | explicit DeclareOpConversion(mlir::MLIRContext *ctx) |
| 291 | : OpRewritePattern{ctx} {} |
| 292 | |
| 293 | llvm::LogicalResult |
| 294 | matchAndRewrite(hlfir::DeclareOp declareOp, |
| 295 | mlir::PatternRewriter &rewriter) const override { |
| 296 | mlir::Location loc = declareOp->getLoc(); |
| 297 | mlir::Value memref = declareOp.getMemref(); |
| 298 | fir::FortranVariableFlagsAttr fortranAttrs; |
| 299 | cuf::DataAttributeAttr dataAttr; |
| 300 | if (auto attrs = declareOp.getFortranAttrs()) |
| 301 | fortranAttrs = |
| 302 | fir::FortranVariableFlagsAttr::get(rewriter.getContext(), *attrs); |
| 303 | if (auto attr = declareOp.getDataAttr()) |
| 304 | dataAttr = cuf::DataAttributeAttr::get(rewriter.getContext(), *attr); |
| 305 | auto firDeclareOp = rewriter.create<fir::DeclareOp>( |
| 306 | loc, memref.getType(), memref, declareOp.getShape(), |
| 307 | declareOp.getTypeparams(), declareOp.getDummyScope(), |
| 308 | declareOp.getUniqName(), fortranAttrs, dataAttr); |
| 309 | |
| 310 | // Propagate other attributes from hlfir.declare to fir.declare. |
| 311 | // OpenACC's acc.declare is one example. Right now, the propagation |
| 312 | // is verbatim. |
| 313 | mlir::NamedAttrList elidedAttrs = |
| 314 | mlir::NamedAttrList{firDeclareOp->getAttrs()}; |
| 315 | for (const mlir::NamedAttribute &attr : declareOp->getAttrs()) |
| 316 | if (!elidedAttrs.get(attr.getName())) |
| 317 | firDeclareOp->setAttr(attr.getName(), attr.getValue()); |
| 318 | |
| 319 | auto firBase = firDeclareOp.getResult(); |
| 320 | mlir::Value hlfirBase; |
| 321 | mlir::Type hlfirBaseType = declareOp.getBase().getType(); |
| 322 | if (mlir::isa<fir::BaseBoxType>(hlfirBaseType)) { |
| 323 | fir::FirOpBuilder builder(rewriter, declareOp.getOperation()); |
| 324 | // Helper to generate the hlfir fir.box with the local lower bounds and |
| 325 | // type parameters. |
| 326 | auto genHlfirBox = [&]() -> mlir::Value { |
| 327 | if (auto baseBoxType = |
| 328 | mlir::dyn_cast<fir::BaseBoxType>(firBase.getType())) { |
| 329 | // Rebox so that lower bounds are correct. |
| 330 | if (baseBoxType.isAssumedRank()) |
| 331 | return builder.create<fir::ReboxAssumedRankOp>( |
| 332 | loc, hlfirBaseType, firBase, |
| 333 | fir::LowerBoundModifierAttribute::SetToOnes); |
| 334 | return builder.create<fir::ReboxOp>(loc, hlfirBaseType, firBase, |
| 335 | declareOp.getShape(), |
| 336 | /*slice=*/mlir::Value{}); |
| 337 | } else { |
| 338 | llvm::SmallVector<mlir::Value> typeParams; |
| 339 | auto maybeCharType = mlir::dyn_cast<fir::CharacterType>( |
| 340 | fir::unwrapSequenceType(fir::unwrapPassByRefType(hlfirBaseType))); |
| 341 | if (!maybeCharType || maybeCharType.hasDynamicLen()) |
| 342 | typeParams.append(declareOp.getTypeparams().begin(), |
| 343 | declareOp.getTypeparams().end()); |
| 344 | return builder.create<fir::EmboxOp>( |
| 345 | loc, hlfirBaseType, firBase, declareOp.getShape(), |
| 346 | /*slice=*/mlir::Value{}, typeParams); |
| 347 | } |
| 348 | }; |
| 349 | if (!mlir::cast<fir::FortranVariableOpInterface>(declareOp.getOperation()) |
| 350 | .isOptional()) { |
| 351 | hlfirBase = genHlfirBox(); |
| 352 | // If the original base is a box too, we could as well |
| 353 | // use the HLFIR box as the FIR base: otherwise, the two |
| 354 | // boxes are "alive" at the same time, and the FIR box |
| 355 | // is used for accessing the base_addr and the HLFIR box |
| 356 | // is used for accessing the bounds etc. Using the HLFIR box, |
| 357 | // that holds the same base_addr at this point, makes |
| 358 | // the representation a little bit more clear. |
| 359 | if (hlfirBase.getType() == declareOp.getOriginalBase().getType()) |
| 360 | firBase = hlfirBase; |
| 361 | } else { |
| 362 | // Need to conditionally rebox/embox the optional: the input fir.box |
| 363 | // may be null and the rebox would be illegal. It is also important to |
| 364 | // preserve the optional aspect: the hlfir fir.box should be null if |
| 365 | // the entity is absent so that later fir.is_present on the hlfir base |
| 366 | // are valid. |
| 367 | mlir::Value isPresent = |
| 368 | builder.create<fir::IsPresentOp>(loc, builder.getI1Type(), firBase); |
| 369 | hlfirBase = builder |
| 370 | .genIfOp(loc, {hlfirBaseType}, isPresent, |
| 371 | /*withElseRegion=*/true) |
| 372 | .genThen([&] { |
| 373 | builder.create<fir::ResultOp>(loc, genHlfirBox()); |
| 374 | }) |
| 375 | .genElse([&]() { |
| 376 | mlir::Value absent = |
| 377 | builder.create<fir::AbsentOp>(loc, hlfirBaseType); |
| 378 | builder.create<fir::ResultOp>(loc, absent); |
| 379 | }) |
| 380 | .getResults()[0]; |
| 381 | } |
| 382 | } else if (mlir::isa<fir::BoxCharType>(hlfirBaseType)) { |
| 383 | assert(declareOp.getTypeparams().size() == 1 && |
| 384 | "must contain character length" ); |
| 385 | hlfirBase = rewriter.create<fir::EmboxCharOp>( |
| 386 | loc, hlfirBaseType, firBase, declareOp.getTypeparams()[0]); |
| 387 | } else { |
| 388 | if (hlfirBaseType != firBase.getType()) { |
| 389 | declareOp.emitOpError() |
| 390 | << "unhandled HLFIR variable type '" << hlfirBaseType << "'\n" ; |
| 391 | return mlir::failure(); |
| 392 | } |
| 393 | hlfirBase = firBase; |
| 394 | } |
| 395 | rewriter.replaceOp(declareOp, {hlfirBase, firBase}); |
| 396 | return mlir::success(); |
| 397 | } |
| 398 | }; |
| 399 | |
| 400 | class DesignateOpConversion |
| 401 | : public mlir::OpRewritePattern<hlfir::DesignateOp> { |
| 402 | // Helper method to generate the coordinate of the first element |
| 403 | // of an array section. It is also called for cases of non-section |
| 404 | // array element addressing. |
| 405 | static mlir::Value genSubscriptBeginAddr( |
| 406 | fir::FirOpBuilder &builder, mlir::Location loc, |
| 407 | hlfir::DesignateOp designate, mlir::Type baseEleTy, mlir::Value base, |
| 408 | mlir::Value shape, |
| 409 | const llvm::SmallVector<mlir::Value> &firBaseTypeParameters) { |
| 410 | assert(!designate.getIndices().empty()); |
| 411 | llvm::SmallVector<mlir::Value> firstElementIndices; |
| 412 | auto indices = designate.getIndices(); |
| 413 | int i = 0; |
| 414 | auto attrs = designate.getIsTripletAttr(); |
| 415 | for (auto isTriplet : attrs.asArrayRef()) { |
| 416 | // Coordinate of the first element are the index and triplets lower |
| 417 | // bounds. |
| 418 | firstElementIndices.push_back(indices[i]); |
| 419 | i = i + (isTriplet ? 3 : 1); |
| 420 | } |
| 421 | |
| 422 | mlir::Type originalDesignateType = designate.getResult().getType(); |
| 423 | const bool isVolatile = fir::isa_volatile_type(originalDesignateType); |
| 424 | mlir::Type arrayCoorType = fir::ReferenceType::get(baseEleTy, isVolatile); |
| 425 | |
| 426 | base = builder.create<fir::ArrayCoorOp>( |
| 427 | loc, arrayCoorType, base, shape, |
| 428 | /*slice=*/mlir::Value{}, firstElementIndices, firBaseTypeParameters); |
| 429 | return base; |
| 430 | } |
| 431 | |
| 432 | public: |
| 433 | explicit DesignateOpConversion(mlir::MLIRContext *ctx) |
| 434 | : OpRewritePattern{ctx} {} |
| 435 | |
| 436 | llvm::LogicalResult |
| 437 | matchAndRewrite(hlfir::DesignateOp designate, |
| 438 | mlir::PatternRewriter &rewriter) const override { |
| 439 | mlir::Location loc = designate.getLoc(); |
| 440 | fir::FirOpBuilder builder(rewriter, designate.getOperation()); |
| 441 | |
| 442 | hlfir::Entity baseEntity(designate.getMemref()); |
| 443 | |
| 444 | if (baseEntity.isMutableBox()) |
| 445 | TODO(loc, "hlfir::designate load of pointer or allocatable" ); |
| 446 | |
| 447 | mlir::Type designateResultType = designate.getResult().getType(); |
| 448 | llvm::SmallVector<mlir::Value> firBaseTypeParameters; |
| 449 | auto [base, shape] = hlfir::genVariableFirBaseShapeAndParams( |
| 450 | loc, builder, baseEntity, firBaseTypeParameters); |
| 451 | const bool isVolatile = fir::isa_volatile_type(designateResultType) || |
| 452 | fir::isa_volatile_type(base.getType()); |
| 453 | mlir::Type baseEleTy = hlfir::getFortranElementType(base.getType()); |
| 454 | mlir::Type resultEleTy = hlfir::getFortranElementType(designateResultType); |
| 455 | |
| 456 | mlir::Value fieldIndex; |
| 457 | if (designate.getComponent()) { |
| 458 | mlir::Type baseRecordType = baseEntity.getFortranElementType(); |
| 459 | if (fir::isRecordWithTypeParameters(baseRecordType)) |
| 460 | TODO(loc, "hlfir.designate with a parametrized derived type base" ); |
| 461 | fieldIndex = builder.create<fir::FieldIndexOp>( |
| 462 | loc, fir::FieldType::get(builder.getContext()), |
| 463 | designate.getComponent().value(), baseRecordType, |
| 464 | /*typeParams=*/mlir::ValueRange{}); |
| 465 | if (baseEntity.isScalar()) { |
| 466 | // Component refs of scalar base right away: |
| 467 | // - scalar%scalar_component [substring|complex_part] or |
| 468 | // - scalar%static_size_array_comp |
| 469 | // - scalar%array(indices) [substring| complex part] |
| 470 | mlir::Type componentType = |
| 471 | mlir::cast<fir::RecordType>(baseEleTy).getType( |
| 472 | designate.getComponent().value()); |
| 473 | mlir::Type coorTy = fir::ReferenceType::get(componentType, isVolatile); |
| 474 | |
| 475 | base = builder.create<fir::CoordinateOp>(loc, coorTy, base, fieldIndex); |
| 476 | if (mlir::isa<fir::BaseBoxType>(componentType)) { |
| 477 | auto variableInterface = mlir::cast<fir::FortranVariableOpInterface>( |
| 478 | designate.getOperation()); |
| 479 | if (variableInterface.isAllocatable() || |
| 480 | variableInterface.isPointer()) { |
| 481 | rewriter.replaceOp(designate, base); |
| 482 | return mlir::success(); |
| 483 | } |
| 484 | TODO(loc, |
| 485 | "addressing parametrized derived type automatic components" ); |
| 486 | } |
| 487 | baseEleTy = hlfir::getFortranElementType(componentType); |
| 488 | shape = designate.getComponentShape(); |
| 489 | } else { |
| 490 | // array%component[(indices) substring|complex part] cases. |
| 491 | // Component ref of array bases are dealt with below in embox/rebox. |
| 492 | assert(mlir::isa<fir::BaseBoxType>(designateResultType)); |
| 493 | } |
| 494 | } |
| 495 | |
| 496 | if (mlir::isa<fir::BaseBoxType>(designateResultType)) { |
| 497 | // Generate embox or rebox. |
| 498 | mlir::Type eleTy = fir::unwrapPassByRefType(designateResultType); |
| 499 | bool isScalarDesignator = !mlir::isa<fir::SequenceType>(eleTy); |
| 500 | mlir::Value sourceBox; |
| 501 | if (isScalarDesignator) { |
| 502 | // The base box will be used for emboxing the scalar element. |
| 503 | sourceBox = base; |
| 504 | // Generate the coordinate of the element. |
| 505 | base = genSubscriptBeginAddr(builder, loc, designate, baseEleTy, base, |
| 506 | shape, firBaseTypeParameters); |
| 507 | shape = nullptr; |
| 508 | // Type information will be taken from the source box, |
| 509 | // so the type parameters are not needed. |
| 510 | firBaseTypeParameters.clear(); |
| 511 | } |
| 512 | llvm::SmallVector<mlir::Value> triples; |
| 513 | llvm::SmallVector<mlir::Value> sliceFields; |
| 514 | mlir::Type idxTy = builder.getIndexType(); |
| 515 | auto subscripts = designate.getIndices(); |
| 516 | if (fieldIndex && baseEntity.isArray()) { |
| 517 | // array%scalar_comp or array%array_comp(indices) |
| 518 | // Generate triples for array(:, :, ...). |
| 519 | triples = genFullSliceTriples(builder, loc, baseEntity); |
| 520 | sliceFields.push_back(fieldIndex); |
| 521 | // Add indices in the field path for "array%array_comp(indices)" |
| 522 | // case. The indices of components provided to the sliceOp must |
| 523 | // be zero based (fir.slice has no knowledge of the component |
| 524 | // lower bounds). The component lower bounds are applied here. |
| 525 | if (!subscripts.empty()) { |
| 526 | llvm::SmallVector<mlir::Value> lbounds = hlfir::genLowerbounds( |
| 527 | loc, builder, designate.getComponentShape(), subscripts.size()); |
| 528 | for (auto [i, lb] : llvm::zip(subscripts, lbounds)) { |
| 529 | mlir::Value iIdx = builder.createConvert(loc, idxTy, i); |
| 530 | mlir::Value lbIdx = builder.createConvert(loc, idxTy, lb); |
| 531 | sliceFields.emplace_back( |
| 532 | builder.create<mlir::arith::SubIOp>(loc, iIdx, lbIdx)); |
| 533 | } |
| 534 | } |
| 535 | } else if (!isScalarDesignator) { |
| 536 | // Otherwise, this is an array section with triplets. |
| 537 | auto undef = builder.create<fir::UndefOp>(loc, idxTy); |
| 538 | unsigned i = 0; |
| 539 | for (auto isTriplet : designate.getIsTriplet()) { |
| 540 | triples.push_back(subscripts[i++]); |
| 541 | if (isTriplet) { |
| 542 | triples.push_back(subscripts[i++]); |
| 543 | triples.push_back(subscripts[i++]); |
| 544 | } else { |
| 545 | triples.push_back(undef); |
| 546 | triples.push_back(undef); |
| 547 | } |
| 548 | } |
| 549 | } |
| 550 | llvm::SmallVector<mlir::Value, 2> substring; |
| 551 | if (!designate.getSubstring().empty()) { |
| 552 | substring.push_back(designate.getSubstring()[0]); |
| 553 | mlir::Type idxTy = builder.getIndexType(); |
| 554 | // fir.slice op substring expects the zero based lower bound. |
| 555 | mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1); |
| 556 | substring[0] = builder.createConvert(loc, idxTy, substring[0]); |
| 557 | substring[0] = |
| 558 | builder.create<mlir::arith::SubIOp>(loc, substring[0], one); |
| 559 | substring.push_back(designate.getTypeparams()[0]); |
| 560 | } |
| 561 | if (designate.getComplexPart()) { |
| 562 | if (triples.empty()) |
| 563 | triples = genFullSliceTriples(builder, loc, baseEntity); |
| 564 | sliceFields.push_back(builder.createIntegerConstant( |
| 565 | loc, idxTy, *designate.getComplexPart())); |
| 566 | } |
| 567 | mlir::Value slice; |
| 568 | if (!triples.empty()) |
| 569 | slice = |
| 570 | builder.create<fir::SliceOp>(loc, triples, sliceFields, substring); |
| 571 | else |
| 572 | assert(sliceFields.empty() && substring.empty()); |
| 573 | |
| 574 | llvm::SmallVector<mlir::Type> resultType{ |
| 575 | fir::updateTypeWithVolatility(designateResultType, isVolatile)}; |
| 576 | |
| 577 | mlir::Value resultBox; |
| 578 | if (mlir::isa<fir::BaseBoxType>(base.getType())) { |
| 579 | resultBox = |
| 580 | builder.create<fir::ReboxOp>(loc, resultType, base, shape, slice); |
| 581 | } else { |
| 582 | resultBox = |
| 583 | builder.create<fir::EmboxOp>(loc, resultType, base, shape, slice, |
| 584 | firBaseTypeParameters, sourceBox); |
| 585 | } |
| 586 | rewriter.replaceOp(designate, resultBox); |
| 587 | return mlir::success(); |
| 588 | } |
| 589 | |
| 590 | // Otherwise, the result is the address of a scalar, or the address of the |
| 591 | // first element of a contiguous array section with compile time constant |
| 592 | // shape. The base may be an array, or a scalar. |
| 593 | mlir::Type resultAddressType = designateResultType; |
| 594 | if (auto boxCharType = |
| 595 | mlir::dyn_cast<fir::BoxCharType>(designateResultType)) |
| 596 | resultAddressType = |
| 597 | fir::ReferenceType::get(boxCharType.getEleTy(), isVolatile); |
| 598 | |
| 599 | // Array element indexing. |
| 600 | if (!designate.getIndices().empty()) { |
| 601 | // - array(indices) [substring|complex_part] or |
| 602 | // - scalar%array_comp(indices) [substring|complex_part] |
| 603 | // This may be a ranked contiguous array section in which case |
| 604 | // The first element address is being computed. |
| 605 | base = genSubscriptBeginAddr(builder, loc, designate, baseEleTy, base, |
| 606 | shape, firBaseTypeParameters); |
| 607 | } |
| 608 | |
| 609 | // Scalar substring (potentially on the previously built array element or |
| 610 | // component reference). |
| 611 | if (!designate.getSubstring().empty()) |
| 612 | base = fir::factory::CharacterExprHelper{builder, loc}.genSubstringBase( |
| 613 | base, designate.getSubstring()[0], resultAddressType); |
| 614 | |
| 615 | // Scalar complex part ref |
| 616 | if (designate.getComplexPart()) { |
| 617 | // Sequence types should have already been handled by this point |
| 618 | assert(!mlir::isa<fir::SequenceType>(designateResultType)); |
| 619 | auto index = builder.createIntegerConstant(loc, builder.getIndexType(), |
| 620 | *designate.getComplexPart()); |
| 621 | auto coorTy = fir::ReferenceType::get(resultEleTy, isVolatile); |
| 622 | |
| 623 | base = builder.create<fir::CoordinateOp>(loc, coorTy, base, index); |
| 624 | } |
| 625 | |
| 626 | // Cast/embox the computed scalar address if needed. |
| 627 | if (mlir::isa<fir::BoxCharType>(designateResultType)) { |
| 628 | assert(designate.getTypeparams().size() == 1 && |
| 629 | "must have character length" ); |
| 630 | auto emboxChar = builder.create<fir::EmboxCharOp>( |
| 631 | loc, designateResultType, base, designate.getTypeparams()[0]); |
| 632 | |
| 633 | rewriter.replaceOp(designate, emboxChar.getResult()); |
| 634 | } else { |
| 635 | base = builder.createConvert(loc, designateResultType, base); |
| 636 | |
| 637 | rewriter.replaceOp(designate, base); |
| 638 | } |
| 639 | return mlir::success(); |
| 640 | } |
| 641 | |
| 642 | private: |
| 643 | // Generates triple for full slice |
| 644 | // Used for component and complex part slices when a triple is |
| 645 | // not specified |
| 646 | static llvm::SmallVector<mlir::Value> |
| 647 | genFullSliceTriples(fir::FirOpBuilder &builder, mlir::Location loc, |
| 648 | hlfir::Entity baseEntity) { |
| 649 | llvm::SmallVector<mlir::Value> triples; |
| 650 | mlir::Type idxTy = builder.getIndexType(); |
| 651 | auto one = builder.createIntegerConstant(loc, idxTy, 1); |
| 652 | for (auto [lb, ub] : hlfir::genBounds(loc, builder, baseEntity)) { |
| 653 | triples.push_back(builder.createConvert(loc, idxTy, lb)); |
| 654 | triples.push_back(builder.createConvert(loc, idxTy, ub)); |
| 655 | triples.push_back(one); |
| 656 | } |
| 657 | return triples; |
| 658 | } |
| 659 | }; |
| 660 | |
| 661 | class ParentComponentOpConversion |
| 662 | : public mlir::OpRewritePattern<hlfir::ParentComponentOp> { |
| 663 | public: |
| 664 | explicit ParentComponentOpConversion(mlir::MLIRContext *ctx) |
| 665 | : OpRewritePattern{ctx} {} |
| 666 | |
| 667 | llvm::LogicalResult |
| 668 | matchAndRewrite(hlfir::ParentComponentOp parentComponent, |
| 669 | mlir::PatternRewriter &rewriter) const override { |
| 670 | mlir::Location loc = parentComponent.getLoc(); |
| 671 | mlir::Type resultType = parentComponent.getType(); |
| 672 | if (!mlir::isa<fir::BoxType>(parentComponent.getType())) { |
| 673 | mlir::Value baseAddr = parentComponent.getMemref(); |
| 674 | // Scalar parent component ref without any length type parameters. The |
| 675 | // input may be a fir.class if it is polymorphic, since this is a scalar |
| 676 | // and the output will be monomorphic, the base address can be extracted |
| 677 | // from the fir.class. |
| 678 | if (mlir::isa<fir::BaseBoxType>(baseAddr.getType())) |
| 679 | baseAddr = rewriter.create<fir::BoxAddrOp>(loc, baseAddr); |
| 680 | rewriter.replaceOpWithNewOp<fir::ConvertOp>(parentComponent, resultType, |
| 681 | baseAddr); |
| 682 | return mlir::success(); |
| 683 | } |
| 684 | // Array parent component ref or PDTs. |
| 685 | hlfir::Entity base{parentComponent.getMemref()}; |
| 686 | mlir::Value baseAddr = base.getBase(); |
| 687 | if (!mlir::isa<fir::BaseBoxType>(baseAddr.getType())) { |
| 688 | // Embox cannot directly be used to address parent components: it expects |
| 689 | // the output type to match the input type when there are no slices. When |
| 690 | // the types have at least one component, a slice to the first element can |
| 691 | // be built, and the result set to the parent component type. Just create |
| 692 | // a fir.box with the base for now since this covers all cases. |
| 693 | mlir::Type baseBoxType = |
| 694 | fir::BoxType::get(base.getElementOrSequenceType()); |
| 695 | assert(!base.hasLengthParameters() && |
| 696 | "base must be a box if it has any type parameters" ); |
| 697 | baseAddr = rewriter.create<fir::EmboxOp>( |
| 698 | loc, baseBoxType, baseAddr, parentComponent.getShape(), |
| 699 | /*slice=*/mlir::Value{}, /*typeParams=*/mlir::ValueRange{}); |
| 700 | } |
| 701 | rewriter.replaceOpWithNewOp<fir::ReboxOp>(parentComponent, resultType, |
| 702 | baseAddr, |
| 703 | /*shape=*/mlir::Value{}, |
| 704 | /*slice=*/mlir::Value{}); |
| 705 | return mlir::success(); |
| 706 | } |
| 707 | }; |
| 708 | |
| 709 | class NoReassocOpConversion |
| 710 | : public mlir::OpRewritePattern<hlfir::NoReassocOp> { |
| 711 | public: |
| 712 | explicit NoReassocOpConversion(mlir::MLIRContext *ctx) |
| 713 | : OpRewritePattern{ctx} {} |
| 714 | |
| 715 | llvm::LogicalResult |
| 716 | matchAndRewrite(hlfir::NoReassocOp noreassoc, |
| 717 | mlir::PatternRewriter &rewriter) const override { |
| 718 | rewriter.replaceOpWithNewOp<fir::NoReassocOp>(noreassoc, |
| 719 | noreassoc.getVal()); |
| 720 | return mlir::success(); |
| 721 | } |
| 722 | }; |
| 723 | |
| 724 | class NullOpConversion : public mlir::OpRewritePattern<hlfir::NullOp> { |
| 725 | public: |
| 726 | explicit NullOpConversion(mlir::MLIRContext *ctx) : OpRewritePattern{ctx} {} |
| 727 | |
| 728 | llvm::LogicalResult |
| 729 | matchAndRewrite(hlfir::NullOp nullop, |
| 730 | mlir::PatternRewriter &rewriter) const override { |
| 731 | rewriter.replaceOpWithNewOp<fir::ZeroOp>(nullop, nullop.getType()); |
| 732 | return mlir::success(); |
| 733 | } |
| 734 | }; |
| 735 | |
| 736 | class GetExtentOpConversion |
| 737 | : public mlir::OpRewritePattern<hlfir::GetExtentOp> { |
| 738 | public: |
| 739 | using mlir::OpRewritePattern<hlfir::GetExtentOp>::OpRewritePattern; |
| 740 | |
| 741 | llvm::LogicalResult |
| 742 | matchAndRewrite(hlfir::GetExtentOp getExtentOp, |
| 743 | mlir::PatternRewriter &rewriter) const override { |
| 744 | mlir::Value shape = getExtentOp.getShape(); |
| 745 | mlir::Operation *shapeOp = shape.getDefiningOp(); |
| 746 | // the hlfir.shape_of operation which led to the creation of this get_extent |
| 747 | // operation should now have been lowered to a fir.shape operation |
| 748 | if (auto s = mlir::dyn_cast_or_null<fir::ShapeOp>(shapeOp)) { |
| 749 | fir::ShapeType shapeTy = mlir::cast<fir::ShapeType>(shape.getType()); |
| 750 | llvm::APInt dim = getExtentOp.getDim(); |
| 751 | uint64_t dimVal = dim.getLimitedValue(shapeTy.getRank()); |
| 752 | mlir::Value extent = s.getExtents()[dimVal]; |
| 753 | fir::FirOpBuilder builder(rewriter, getExtentOp.getOperation()); |
| 754 | extent = builder.createConvert(getExtentOp.getLoc(), |
| 755 | builder.getIndexType(), extent); |
| 756 | rewriter.replaceOp(getExtentOp, extent); |
| 757 | return mlir::success(); |
| 758 | } |
| 759 | return mlir::failure(); |
| 760 | } |
| 761 | }; |
| 762 | |
| 763 | class ConvertHLFIRtoFIR |
| 764 | : public hlfir::impl::ConvertHLFIRtoFIRBase<ConvertHLFIRtoFIR> { |
| 765 | public: |
| 766 | void runOnOperation() override { |
| 767 | // TODO: like "bufferize-hlfir" pass, runtime signature may be added |
| 768 | // by this pass. This requires the pass to run on the ModuleOp. It would |
| 769 | // probably be more optimal to have it run on FuncOp and find a way to |
| 770 | // generate the signatures in a thread safe way. |
| 771 | auto module = this->getOperation(); |
| 772 | auto *context = &getContext(); |
| 773 | mlir::RewritePatternSet patterns(context); |
| 774 | patterns.insert<AssignOpConversion, CopyInOpConversion, CopyOutOpConversion, |
| 775 | DeclareOpConversion, DesignateOpConversion, |
| 776 | GetExtentOpConversion, NoReassocOpConversion, |
| 777 | NullOpConversion, ParentComponentOpConversion>(context); |
| 778 | mlir::ConversionTarget target(*context); |
| 779 | target.addIllegalDialect<hlfir::hlfirDialect>(); |
| 780 | target.markUnknownOpDynamicallyLegal( |
| 781 | [](mlir::Operation *) { return true; }); |
| 782 | if (mlir::failed(mlir::applyPartialConversion(module, target, |
| 783 | std::move(patterns)))) { |
| 784 | mlir::emitError(mlir::UnknownLoc::get(context), |
| 785 | "failure in HLFIR to FIR conversion pass" ); |
| 786 | signalPassFailure(); |
| 787 | } |
| 788 | } |
| 789 | }; |
| 790 | |
| 791 | } // namespace |
| 792 | |