1 //===-- HLFIRTools.h -- HLFIR tools -----------------------*- C++ -*-===// 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 // 9 // Coding style: https://mlir.llvm.org/getting_started/DeveloperGuide/ 10 // 11 //===----------------------------------------------------------------------===// 12 13 #ifndef FORTRAN_OPTIMIZER_BUILDER_HLFIRTOOLS_H 14 #define FORTRAN_OPTIMIZER_BUILDER_HLFIRTOOLS_H 15 16 #include "flang/Optimizer/Builder/BoxValue.h" 17 #include "flang/Optimizer/Dialect/FIROps.h" 18 #include "flang/Optimizer/Dialect/FortranVariableInterface.h" 19 #include "flang/Optimizer/HLFIR/HLFIRDialect.h" 20 #include <optional> 21 22 namespace fir { 23 class FirOpBuilder; 24 } 25 26 namespace mlir { 27 class IRMapping; 28 } 29 30 namespace hlfir { 31 32 class AssociateOp; 33 class ElementalOp; 34 class ElementalOpInterface; 35 class ElementalAddrOp; 36 class EvaluateInMemoryOp; 37 class YieldElementOp; 38 39 /// Is this a Fortran variable for which the defining op carrying the Fortran 40 /// attributes is visible? 41 inline bool isFortranVariableWithAttributes(mlir::Value value) { 42 return value.getDefiningOp<fir::FortranVariableOpInterface>(); 43 } 44 45 /// Is this a Fortran expression value, or a Fortran variable for which the 46 /// defining op carrying the Fortran attributes is visible? 47 inline bool isFortranEntityWithAttributes(mlir::Value value) { 48 return isFortranValue(value) || isFortranVariableWithAttributes(value); 49 } 50 51 class Entity : public mlir::Value { 52 public: 53 explicit Entity(mlir::Value value) : mlir::Value(value) { 54 assert(isFortranEntity(value) && 55 "must be a value representing a Fortran value or variable like"); 56 } 57 Entity(fir::FortranVariableOpInterface variable) 58 : mlir::Value(variable.getBase()) {} 59 bool isValue() const { return isFortranValue(*this); } 60 bool isVariable() const { return !isValue(); } 61 bool isMutableBox() const { return hlfir::isBoxAddressType(getType()); } 62 bool isProcedurePointer() const { 63 return fir::isBoxProcAddressType(getType()); 64 } 65 bool isBoxAddressOrValue() const { 66 return hlfir::isBoxAddressOrValueType(getType()); 67 } 68 69 /// Is this entity a procedure designator? 70 bool isProcedure() const { return isFortranProcedureValue(getType()); } 71 72 /// Is this an array or an assumed ranked entity? 73 bool isArray() const { return getRank() != 0; } 74 75 /// Is this an assumed ranked entity? 76 bool isAssumedRank() const { return getRank() == -1; } 77 78 /// Return the rank of this entity or -1 if it is an assumed rank. 79 int getRank() const { 80 mlir::Type type = fir::unwrapPassByRefType(fir::unwrapRefType(getType())); 81 if (auto seqTy = mlir::dyn_cast<fir::SequenceType>(type)) { 82 if (seqTy.hasUnknownShape()) 83 return -1; 84 return seqTy.getDimension(); 85 } 86 if (auto exprType = mlir::dyn_cast<hlfir::ExprType>(type)) 87 return exprType.getRank(); 88 return 0; 89 } 90 bool isScalar() const { return !isArray(); } 91 92 bool isPolymorphic() const { return hlfir::isPolymorphicType(getType()); } 93 94 mlir::Type getFortranElementType() const { 95 return hlfir::getFortranElementType(getType()); 96 } 97 mlir::Type getElementOrSequenceType() const { 98 return hlfir::getFortranElementOrSequenceType(getType()); 99 } 100 101 bool hasLengthParameters() const { 102 mlir::Type eleTy = getFortranElementType(); 103 return mlir::isa<fir::CharacterType>(eleTy) || 104 fir::isRecordWithTypeParameters(eleTy); 105 } 106 107 bool isCharacter() const { 108 return mlir::isa<fir::CharacterType>(getFortranElementType()); 109 } 110 111 bool hasIntrinsicType() const { 112 mlir::Type eleTy = getFortranElementType(); 113 return fir::isa_trivial(eleTy) || mlir::isa<fir::CharacterType>(eleTy); 114 } 115 116 bool isDerivedWithLengthParameters() const { 117 return fir::isRecordWithTypeParameters(getFortranElementType()); 118 } 119 120 bool mayHaveNonDefaultLowerBounds() const; 121 122 // Is this entity known to be contiguous at compile time? 123 // Note that when this returns false, the entity may still 124 // turn-out to be contiguous at runtime. 125 bool isSimplyContiguous() const { 126 // If this can be described without a fir.box in FIR, this must 127 // be contiguous. 128 if (!hlfir::isBoxAddressOrValueType(getFirBase().getType())) 129 return true; 130 // Otherwise, if this entity has a visible declaration in FIR, 131 // or is the dereference of an allocatable or contiguous pointer 132 // it is simply contiguous. 133 if (auto varIface = getMaybeDereferencedVariableInterface()) 134 return varIface.isAllocatable() || varIface.hasContiguousAttr(); 135 return false; 136 } 137 138 fir::FortranVariableOpInterface getIfVariableInterface() const { 139 return this->getDefiningOp<fir::FortranVariableOpInterface>(); 140 } 141 142 // Return a "declaration" operation for this variable if visible, 143 // or the "declaration" operation of the allocatable/pointer this 144 // variable was dereferenced from (if it is visible). 145 fir::FortranVariableOpInterface 146 getMaybeDereferencedVariableInterface() const { 147 mlir::Value base = *this; 148 if (auto loadOp = base.getDefiningOp<fir::LoadOp>()) 149 base = loadOp.getMemref(); 150 return base.getDefiningOp<fir::FortranVariableOpInterface>(); 151 } 152 153 bool isOptional() const { 154 auto varIface = getIfVariableInterface(); 155 return varIface ? varIface.isOptional() : false; 156 } 157 158 bool isParameter() const { 159 auto varIface = getIfVariableInterface(); 160 return varIface ? varIface.isParameter() : false; 161 } 162 163 bool isAllocatable() const { 164 auto varIface = getIfVariableInterface(); 165 return varIface ? varIface.isAllocatable() : false; 166 } 167 168 bool isPointer() const { 169 auto varIface = getIfVariableInterface(); 170 return varIface ? varIface.isPointer() : false; 171 } 172 173 // Get the entity as an mlir SSA value containing all the shape, type 174 // parameters and dynamic shape information. 175 mlir::Value getBase() const { return *this; } 176 177 // Get the entity as a FIR base. This may not carry the shape and type 178 // parameters information, and even when it is a box with shape information. 179 // it will not contain the local lower bounds of the entity. This should 180 // be used with care when generating FIR code that does not need this 181 // information, or has access to it in other ways. Its advantage is that 182 // it will never be a fir.box for explicit shape arrays, leading to simpler 183 // FIR code generation. 184 mlir::Value getFirBase() const; 185 }; 186 187 /// Wrapper over an mlir::Value that can be viewed as a Fortran entity. 188 /// This provides some Fortran specific helpers as well as a guarantee 189 /// in the compiler source that a certain mlir::Value must be a Fortran 190 /// entity, and if it is a variable, its defining operation carrying its 191 /// Fortran attributes must be visible. 192 class EntityWithAttributes : public Entity { 193 public: 194 explicit EntityWithAttributes(mlir::Value value) : Entity(value) { 195 assert(isFortranEntityWithAttributes(value) && 196 "must be a value representing a Fortran value or variable"); 197 } 198 EntityWithAttributes(fir::FortranVariableOpInterface variable) 199 : Entity(variable) {} 200 fir::FortranVariableOpInterface getIfVariable() const { 201 return getIfVariableInterface(); 202 } 203 }; 204 205 /// Functions to translate hlfir::EntityWithAttributes to fir::ExtendedValue. 206 /// For Fortran arrays, character, and derived type values, this require 207 /// allocating a storage since these can only be represented in memory in FIR. 208 /// In that case, a cleanup function is provided to generate the finalization 209 /// code after the end of the fir::ExtendedValue use. 210 using CleanupFunction = std::function<void()>; 211 std::pair<fir::ExtendedValue, std::optional<CleanupFunction>> 212 translateToExtendedValue(mlir::Location loc, fir::FirOpBuilder &builder, 213 Entity entity, bool contiguousHint = false); 214 215 /// Function to translate FortranVariableOpInterface to fir::ExtendedValue. 216 /// It may generates IR to unbox fir.boxchar, but has otherwise no side effects 217 /// on the IR. 218 fir::ExtendedValue 219 translateToExtendedValue(mlir::Location loc, fir::FirOpBuilder &builder, 220 fir::FortranVariableOpInterface fortranVariable, 221 bool forceHlfirBase = false); 222 223 /// Generate declaration for a fir::ExtendedValue in memory. 224 fir::FortranVariableOpInterface 225 genDeclare(mlir::Location loc, fir::FirOpBuilder &builder, 226 const fir::ExtendedValue &exv, llvm::StringRef name, 227 fir::FortranVariableFlagsAttr flags, 228 mlir::Value dummyScope = nullptr, 229 cuf::DataAttributeAttr dataAttr = {}); 230 231 /// Generate an hlfir.associate to build a variable from an expression value. 232 /// The type of the variable must be provided so that scalar logicals are 233 /// properly typed when placed in memory. 234 hlfir::AssociateOp 235 genAssociateExpr(mlir::Location loc, fir::FirOpBuilder &builder, 236 hlfir::Entity value, mlir::Type variableType, 237 llvm::StringRef name, 238 std::optional<mlir::NamedAttribute> attr = std::nullopt); 239 240 /// Get the raw address of a variable (simple fir.ref/fir.ptr, or fir.heap 241 /// value). The returned value should be used with care, it does not contain any 242 /// stride, shape, and type parameter information. For pointers and 243 /// allocatables, this returns the address of the target. 244 mlir::Value genVariableRawAddress(mlir::Location loc, 245 fir::FirOpBuilder &builder, 246 hlfir::Entity var); 247 248 /// Get a fir.boxchar for character scalar or array variable (the shape is lost 249 /// for arrays). 250 mlir::Value genVariableBoxChar(mlir::Location loc, fir::FirOpBuilder &builder, 251 hlfir::Entity var); 252 253 /// Get or create a fir.box or fir.class from a variable. 254 hlfir::Entity genVariableBox(mlir::Location loc, fir::FirOpBuilder &builder, 255 hlfir::Entity var); 256 257 /// If the entity is a variable, load its value (dereference pointers and 258 /// allocatables if needed). Do nothing if the entity is already a value, and 259 /// only dereference pointers and allocatables if it is not a scalar entity 260 /// of numerical or logical type. 261 Entity loadTrivialScalar(mlir::Location loc, fir::FirOpBuilder &builder, 262 Entity entity); 263 264 /// If \p entity is a POINTER or ALLOCATABLE, dereference it and return the 265 /// target entity. Return \p entity otherwise. 266 hlfir::Entity derefPointersAndAllocatables(mlir::Location loc, 267 fir::FirOpBuilder &builder, 268 Entity entity); 269 270 /// Get element entity(oneBasedIndices) if entity is an array, or return entity 271 /// if it is a scalar. The indices are one based. If the entity has non default 272 /// lower bounds, the function will adapt the indices in the indexing operation. 273 hlfir::Entity getElementAt(mlir::Location loc, fir::FirOpBuilder &builder, 274 Entity entity, mlir::ValueRange oneBasedIndices); 275 /// Compute the lower and upper bounds of an entity. 276 llvm::SmallVector<std::pair<mlir::Value, mlir::Value>> 277 genBounds(mlir::Location loc, fir::FirOpBuilder &builder, Entity entity); 278 /// Compute the lower and upper bounds given a fir.shape or fir.shape_shift 279 /// (fir.shift is not allowed here). 280 llvm::SmallVector<std::pair<mlir::Value, mlir::Value>> 281 genBounds(mlir::Location loc, fir::FirOpBuilder &builder, mlir::Value shape); 282 283 /// Generate lower bounds from a shape. If \p shape is null or is a fir.shape, 284 /// the returned vector will contain \p rank ones. 285 llvm::SmallVector<mlir::Value> genLowerbounds(mlir::Location loc, 286 fir::FirOpBuilder &builder, 287 mlir::Value shape, unsigned rank); 288 289 /// Compute fir.shape<> (no lower bounds) for an entity. 290 mlir::Value genShape(mlir::Location loc, fir::FirOpBuilder &builder, 291 Entity entity); 292 293 /// Compute the extent of \p entity in dimension \p dim. Crashes 294 /// if dim is bigger than the entity's rank. 295 mlir::Value genExtent(mlir::Location loc, fir::FirOpBuilder &builder, 296 hlfir::Entity entity, unsigned dim); 297 298 /// Compute the lower bound of \p entity in dimension \p dim. Crashes 299 /// if dim is bigger than the entity's rank. 300 mlir::Value genLBound(mlir::Location loc, fir::FirOpBuilder &builder, 301 hlfir::Entity entity, unsigned dim); 302 303 /// Generate a vector of extents with index type from a fir.shape 304 /// of fir.shape_shift value. 305 llvm::SmallVector<mlir::Value> getIndexExtents(mlir::Location loc, 306 fir::FirOpBuilder &builder, 307 mlir::Value shape); 308 309 /// Return explicit extents. If the base is a fir.box, this won't read it to 310 /// return the extents and will instead return an empty vector. 311 llvm::SmallVector<mlir::Value> 312 getExplicitExtentsFromShape(mlir::Value shape, fir::FirOpBuilder &builder); 313 314 /// Read length parameters into result if this entity has any. 315 void genLengthParameters(mlir::Location loc, fir::FirOpBuilder &builder, 316 Entity entity, 317 llvm::SmallVectorImpl<mlir::Value> &result); 318 319 /// Get the length of a character entity. Crashes if the entity is not 320 /// a character entity. 321 mlir::Value genCharLength(mlir::Location loc, fir::FirOpBuilder &builder, 322 Entity entity); 323 324 mlir::Value genRank(mlir::Location loc, fir::FirOpBuilder &builder, 325 Entity entity, mlir::Type resultType); 326 327 /// Return the fir base, shape, and type parameters for a variable. Note that 328 /// type parameters are only added if the entity is not a box and the type 329 /// parameters is not a constant in the base type. This matches the arguments 330 /// expected by fir.embox/fir.array_coor. 331 std::pair<mlir::Value, mlir::Value> genVariableFirBaseShapeAndParams( 332 mlir::Location loc, fir::FirOpBuilder &builder, Entity entity, 333 llvm::SmallVectorImpl<mlir::Value> &typeParams); 334 335 /// Get the variable type for an element of an array type entity. Returns the 336 /// input entity type if it is scalar. Will crash if the entity is not a 337 /// variable. 338 mlir::Type getVariableElementType(hlfir::Entity variable); 339 /// Get the entity type for an element of an array entity. Returns the 340 /// input type if it is a scalar. If the entity is a variable, this 341 /// is like getVariableElementType, otherwise, this will return a value 342 /// type (that may be an hlfir.expr type). 343 mlir::Type getEntityElementType(hlfir::Entity entity); 344 345 using ElementalKernelGenerator = std::function<hlfir::Entity( 346 mlir::Location, fir::FirOpBuilder &, mlir::ValueRange)>; 347 /// Generate an hlfir.elementalOp given call back to generate the element 348 /// value at for each iteration. 349 /// If exprType is specified, this will be the return type of the elemental op. 350 /// If exprType is not specified, the resulting expression type is computed 351 /// from the given \p elementType and \p shape, and the type is polymorphic 352 /// if \p polymorphicMold is present. 353 hlfir::ElementalOp genElementalOp( 354 mlir::Location loc, fir::FirOpBuilder &builder, mlir::Type elementType, 355 mlir::Value shape, mlir::ValueRange typeParams, 356 const ElementalKernelGenerator &genKernel, bool isUnordered = false, 357 mlir::Value polymorphicMold = {}, mlir::Type exprType = mlir::Type{}); 358 359 /// Structure to describe a loop nest. 360 struct LoopNest { 361 mlir::Operation *outerOp = nullptr; 362 mlir::Block *body = nullptr; 363 llvm::SmallVector<mlir::Value> oneBasedIndices; 364 }; 365 366 /// Generate a fir.do_loop nest looping from 1 to extents[i]. 367 /// \p isUnordered specifies whether the loops in the loop nest 368 /// are unordered. 369 /// 370 /// NOTE: genLoopNestWithReductions() should be used in favor 371 /// of this method, though, it cannot generate OpenMP workshare 372 /// loop constructs currently. 373 LoopNest genLoopNest(mlir::Location loc, fir::FirOpBuilder &builder, 374 mlir::ValueRange extents, bool isUnordered = false, 375 bool emitWorkshareLoop = false); 376 inline LoopNest genLoopNest(mlir::Location loc, fir::FirOpBuilder &builder, 377 mlir::Value shape, bool isUnordered = false, 378 bool emitWorkshareLoop = false) { 379 return genLoopNest(loc, builder, getIndexExtents(loc, builder, shape), 380 isUnordered, emitWorkshareLoop); 381 } 382 383 /// The type of a callback that generates the body of a reduction 384 /// loop nest. It takes a location and a builder, as usual. 385 /// In addition, the first set of values are the values of the loops' 386 /// induction variables. The second set of values are the values 387 /// of the reductions on entry to the innermost loop. 388 /// The callback must return the updated values of the reductions. 389 using ReductionLoopBodyGenerator = std::function<llvm::SmallVector<mlir::Value>( 390 mlir::Location, fir::FirOpBuilder &, mlir::ValueRange, mlir::ValueRange)>; 391 392 /// Generate a loop nest loopong from 1 to \p extents[i] and reducing 393 /// a set of values. 394 /// \p isUnordered specifies whether the loops in the loop nest 395 /// are unordered. 396 /// \p reductionInits are the initial values of the reductions 397 /// on entry to the outermost loop. 398 /// \p genBody callback is repsonsible for generating the code 399 /// that updates the reduction values in the innermost loop. 400 /// 401 /// NOTE: the implementation of this function may decide 402 /// to perform the reductions on SSA or in memory. 403 /// In the latter case, this function is responsible for 404 /// allocating/loading/storing the reduction variables, 405 /// and making sure they have proper data sharing attributes 406 /// in case any parallel constructs are present around the point 407 /// of the loop nest insertion, or if the function decides 408 /// to use any worksharing loop constructs for the loop nest. 409 llvm::SmallVector<mlir::Value> genLoopNestWithReductions( 410 mlir::Location loc, fir::FirOpBuilder &builder, mlir::ValueRange extents, 411 mlir::ValueRange reductionInits, const ReductionLoopBodyGenerator &genBody, 412 bool isUnordered = false); 413 414 /// Inline the body of an hlfir.elemental at the current insertion point 415 /// given a list of one based indices. This generates the computation 416 /// of one element of the elemental expression. Return the YieldElementOp 417 /// whose value argument is the element value. 418 /// The original hlfir::ElementalOp is left untouched. 419 hlfir::YieldElementOp inlineElementalOp(mlir::Location loc, 420 fir::FirOpBuilder &builder, 421 hlfir::ElementalOp elemental, 422 mlir::ValueRange oneBasedIndices); 423 424 /// Inline the body of an hlfir.elemental or hlfir.elemental_addr without 425 /// cloning the resulting hlfir.yield_element/hlfir.yield, and return the cloned 426 /// operand of the hlfir.yield_element/hlfir.yield. The mapper must be provided 427 /// to cover complex cases where the inlined elemental is not defined in the 428 /// current context and uses values that have been cloned already. A callback is 429 /// provided to indicate if an hlfir.apply inside the hlfir.elemental must be 430 /// immediately replaced by the inlining of the applied hlfir.elemental. 431 mlir::Value inlineElementalOp( 432 mlir::Location loc, fir::FirOpBuilder &builder, 433 hlfir::ElementalOpInterface elemental, mlir::ValueRange oneBasedIndices, 434 mlir::IRMapping &mapper, 435 const std::function<bool(hlfir::ElementalOp)> &mustRecursivelyInline); 436 437 /// Create a new temporary with the shape and parameters of the provided 438 /// hlfir.eval_in_mem operation and clone the body of the hlfir.eval_in_mem 439 /// operating on this new temporary. returns the temporary and whether the 440 /// temporary is heap or stack allocated. 441 std::pair<hlfir::Entity, bool> 442 computeEvaluateOpInNewTemp(mlir::Location, fir::FirOpBuilder &, 443 hlfir::EvaluateInMemoryOp evalInMem, 444 mlir::Value shape, mlir::ValueRange typeParams); 445 446 // Clone the body of the hlfir.eval_in_mem operating on this the provided 447 // storage. The provided storage must be a contiguous "raw" memory reference 448 // (not a fir.box) big enough to hold the value computed by hlfir.eval_in_mem. 449 // No runtime check is inserted by this utility to enforce that. It is also 450 // usually invalid to provide some storage that is already addressed directly 451 // or indirectly inside the hlfir.eval_in_mem body. 452 void computeEvaluateOpIn(mlir::Location, fir::FirOpBuilder &, 453 hlfir::EvaluateInMemoryOp, mlir::Value storage); 454 455 std::pair<fir::ExtendedValue, std::optional<hlfir::CleanupFunction>> 456 convertToValue(mlir::Location loc, fir::FirOpBuilder &builder, 457 hlfir::Entity entity); 458 459 std::pair<fir::ExtendedValue, std::optional<hlfir::CleanupFunction>> 460 convertToAddress(mlir::Location loc, fir::FirOpBuilder &builder, 461 hlfir::Entity entity, mlir::Type targetType); 462 463 std::pair<fir::ExtendedValue, std::optional<hlfir::CleanupFunction>> 464 convertToBox(mlir::Location loc, fir::FirOpBuilder &builder, 465 hlfir::Entity entity, mlir::Type targetType); 466 467 /// Clone an hlfir.elemental_addr into an hlfir.elemental value. 468 hlfir::ElementalOp cloneToElementalOp(mlir::Location loc, 469 fir::FirOpBuilder &builder, 470 hlfir::ElementalAddrOp elementalAddrOp); 471 472 /// Return true, if \p elemental must produce a temporary array, 473 /// for example, for the purpose of finalization. Note that such 474 /// ElementalOp's must be optimized with caution. For example, 475 /// completely inlining such ElementalOp into another one 476 /// would be incorrect. 477 bool elementalOpMustProduceTemp(hlfir::ElementalOp elemental); 478 479 std::pair<hlfir::Entity, mlir::Value> 480 createTempFromMold(mlir::Location loc, fir::FirOpBuilder &builder, 481 hlfir::Entity mold); 482 483 // TODO: this does not support polymorphic molds 484 hlfir::Entity createStackTempFromMold(mlir::Location loc, 485 fir::FirOpBuilder &builder, 486 hlfir::Entity mold); 487 488 hlfir::EntityWithAttributes convertCharacterKind(mlir::Location loc, 489 fir::FirOpBuilder &builder, 490 hlfir::Entity scalarChar, 491 int toKind); 492 493 /// Materialize an implicit Fortran type conversion from \p source to \p toType. 494 /// This is a no-op if the Fortran category and KIND of \p source are 495 /// the same as the one in \p toType. This is also a no-op if \p toType is an 496 /// unlimited polymorphic. For characters, this implies that a conversion is 497 /// only inserted in case of KIND mismatch (and not in case of length mismatch), 498 /// and that the resulting entity length is the same as the one from \p source. 499 /// It is valid to call this helper if \p source is an array. If a conversion is 500 /// inserted for arrays, a clean-up will be returned. If no conversion is 501 /// needed, the source is returned. 502 /// Beware that the resulting entity mlir type may not be toType: it will be a 503 /// Fortran entity with the same Fortran category and KIND. 504 /// If preserveLowerBounds is set, the returned entity will have the same lower 505 /// bounds as \p source. 506 std::pair<hlfir::Entity, std::optional<hlfir::CleanupFunction>> 507 genTypeAndKindConvert(mlir::Location loc, fir::FirOpBuilder &builder, 508 hlfir::Entity source, mlir::Type toType, 509 bool preserveLowerBounds); 510 511 /// A shortcut for loadTrivialScalar(getElementAt()), 512 /// which designates and loads an element of an array. 513 Entity loadElementAt(mlir::Location loc, fir::FirOpBuilder &builder, 514 Entity entity, mlir::ValueRange oneBasedIndices); 515 516 /// Return a vector of extents for the given entity. 517 /// The function creates new operations, but tries to clean-up 518 /// after itself. 519 llvm::SmallVector<mlir::Value, Fortran::common::maxRank> 520 genExtentsVector(mlir::Location loc, fir::FirOpBuilder &builder, Entity entity); 521 522 } // namespace hlfir 523 524 #endif // FORTRAN_OPTIMIZER_BUILDER_HLFIRTOOLS_H 525