1 //===-- BoxValue.h -- internal box values -----------------------*- 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_BOXVALUE_H 14 #define FORTRAN_OPTIMIZER_BUILDER_BOXVALUE_H 15 16 #include "flang/Optimizer/Dialect/FIRType.h" 17 #include "flang/Optimizer/Support/FatalError.h" 18 #include "flang/Optimizer/Support/Matcher.h" 19 #include "mlir/IR/OperationSupport.h" 20 #include "mlir/IR/Value.h" 21 #include "llvm/ADT/SmallVector.h" 22 #include "llvm/Support/Compiler.h" 23 #include "llvm/Support/raw_ostream.h" 24 #include <utility> 25 26 namespace fir { 27 class FirOpBuilder; 28 class ArrayLoadOp; 29 30 class ArrayBoxValue; 31 class BoxValue; 32 class CharBoxValue; 33 class CharArrayBoxValue; 34 class MutableBoxValue; 35 class PolymorphicValue; 36 class ProcBoxValue; 37 38 llvm::raw_ostream &operator<<(llvm::raw_ostream &, const CharBoxValue &); 39 llvm::raw_ostream &operator<<(llvm::raw_ostream &, const ArrayBoxValue &); 40 llvm::raw_ostream &operator<<(llvm::raw_ostream &, const CharArrayBoxValue &); 41 llvm::raw_ostream &operator<<(llvm::raw_ostream &, const ProcBoxValue &); 42 llvm::raw_ostream &operator<<(llvm::raw_ostream &, const MutableBoxValue &); 43 llvm::raw_ostream &operator<<(llvm::raw_ostream &, const BoxValue &); 44 llvm::raw_ostream &operator<<(llvm::raw_ostream &, const PolymorphicValue &); 45 46 //===----------------------------------------------------------------------===// 47 // 48 // Boxed values 49 // 50 // Define a set of containers used internally by the lowering bridge to keep 51 // track of extended values associated with a Fortran subexpression. These 52 // associations are maintained during the construction of FIR. 53 // 54 //===----------------------------------------------------------------------===// 55 56 /// Most expressions of intrinsic type can be passed unboxed. Their properties 57 /// are known statically. 58 using UnboxedValue = mlir::Value; 59 60 /// Abstract base class. 61 class AbstractBox { 62 public: 63 AbstractBox() = delete; 64 AbstractBox(mlir::Value addr) : addr{addr} {} 65 66 /// An abstract box most often contains a memory reference to a value. Despite 67 /// the name here, it is possible that `addr` is a scalar value that is not a 68 /// memory reference. 69 mlir::Value getAddr() const { return addr; } 70 71 protected: 72 mlir::Value addr; 73 }; 74 75 /// Expressions of CHARACTER type have an associated, possibly dynamic LEN 76 /// value. 77 class CharBoxValue : public AbstractBox { 78 public: 79 CharBoxValue(mlir::Value addr, mlir::Value len) 80 : AbstractBox{addr}, len{len} { 81 if (addr && mlir::isa<fir::BoxCharType>(addr.getType())) 82 fir::emitFatalError(addr.getLoc(), 83 "BoxChar should not be in CharBoxValue"); 84 } 85 86 CharBoxValue clone(mlir::Value newBase) const { return {newBase, len}; } 87 88 /// Convenience alias to get the memory reference to the buffer. 89 mlir::Value getBuffer() const { return getAddr(); } 90 91 mlir::Value getLen() const { return len; } 92 93 friend llvm::raw_ostream &operator<<(llvm::raw_ostream &, 94 const CharBoxValue &); 95 LLVM_DUMP_METHOD void dump() const { llvm::errs() << *this; } 96 97 protected: 98 mlir::Value len; 99 }; 100 101 /// Polymorphic value associated with a dynamic type descriptor. 102 class PolymorphicValue : public AbstractBox { 103 public: 104 PolymorphicValue(mlir::Value addr, mlir::Value sourceBox) 105 : AbstractBox{addr}, sourceBox{sourceBox} {} 106 107 PolymorphicValue clone(mlir::Value newBase) const { 108 return {newBase, sourceBox}; 109 } 110 111 mlir::Value getSourceBox() const { return sourceBox; } 112 113 friend llvm::raw_ostream &operator<<(llvm::raw_ostream &, 114 const PolymorphicValue &); 115 LLVM_DUMP_METHOD void dump() const { llvm::errs() << *this; } 116 117 protected: 118 mlir::Value sourceBox; 119 }; 120 121 /// Abstract base class. 122 /// Expressions of type array have at minimum a shape. These expressions may 123 /// have lbound attributes (dynamic values) that affect the interpretation of 124 /// indexing expressions. 125 class AbstractArrayBox { 126 public: 127 AbstractArrayBox() = default; 128 AbstractArrayBox(llvm::ArrayRef<mlir::Value> extents, 129 llvm::ArrayRef<mlir::Value> lbounds) 130 : extents{extents}, lbounds{lbounds} {} 131 132 // Every array has extents that describe its shape. 133 const llvm::SmallVectorImpl<mlir::Value> &getExtents() const { 134 return extents; 135 } 136 137 // An array expression may have user-defined lower bound values. 138 // If this vector is empty, the default in all dimensions in `1`. 139 const llvm::SmallVectorImpl<mlir::Value> &getLBounds() const { 140 return lbounds; 141 } 142 143 bool lboundsAllOne() const { return lbounds.empty(); } 144 std::size_t rank() const { return extents.size(); } 145 146 protected: 147 llvm::SmallVector<mlir::Value, 4> extents; 148 llvm::SmallVector<mlir::Value, 4> lbounds; 149 }; 150 151 /// Expressions with rank > 0 have extents. They may also have lbounds that are 152 /// not 1. 153 class ArrayBoxValue : public PolymorphicValue, public AbstractArrayBox { 154 public: 155 ArrayBoxValue(mlir::Value addr, llvm::ArrayRef<mlir::Value> extents, 156 llvm::ArrayRef<mlir::Value> lbounds = {}, 157 mlir::Value sourceBox = {}) 158 : PolymorphicValue{addr, sourceBox}, AbstractArrayBox{extents, lbounds} {} 159 160 ArrayBoxValue clone(mlir::Value newBase) const { 161 return {newBase, extents, lbounds}; 162 } 163 164 friend llvm::raw_ostream &operator<<(llvm::raw_ostream &, 165 const ArrayBoxValue &); 166 LLVM_DUMP_METHOD void dump() const { llvm::errs() << *this; } 167 }; 168 169 /// Expressions of type CHARACTER and with rank > 0. 170 class CharArrayBoxValue : public CharBoxValue, public AbstractArrayBox { 171 public: 172 CharArrayBoxValue(mlir::Value addr, mlir::Value len, 173 llvm::ArrayRef<mlir::Value> extents, 174 llvm::ArrayRef<mlir::Value> lbounds = {}) 175 : CharBoxValue{addr, len}, AbstractArrayBox{extents, lbounds} {} 176 177 CharArrayBoxValue clone(mlir::Value newBase) const { 178 return {newBase, len, extents, lbounds}; 179 } 180 181 CharBoxValue cloneElement(mlir::Value newBase) const { 182 return {newBase, len}; 183 } 184 185 friend llvm::raw_ostream &operator<<(llvm::raw_ostream &, 186 const CharArrayBoxValue &); 187 LLVM_DUMP_METHOD void dump() const { llvm::errs() << *this; } 188 }; 189 190 /// Expressions that are procedure POINTERs may need a set of references to 191 /// variables in the host scope. 192 class ProcBoxValue : public AbstractBox { 193 public: 194 ProcBoxValue(mlir::Value addr, mlir::Value context) 195 : AbstractBox{addr}, hostContext{context} {} 196 197 ProcBoxValue clone(mlir::Value newBase) const { 198 return {newBase, hostContext}; 199 } 200 201 mlir::Value getHostContext() const { return hostContext; } 202 203 friend llvm::raw_ostream &operator<<(llvm::raw_ostream &, 204 const ProcBoxValue &); 205 LLVM_DUMP_METHOD void dump() const { llvm::errs() << *this; } 206 207 protected: 208 mlir::Value hostContext; 209 }; 210 211 /// Base class for values associated to a fir.box or fir.ref<fir.box>. 212 class AbstractIrBox : public AbstractBox, public AbstractArrayBox { 213 public: 214 AbstractIrBox(mlir::Value addr) : AbstractBox{addr} {} 215 AbstractIrBox(mlir::Value addr, llvm::ArrayRef<mlir::Value> lbounds, 216 llvm::ArrayRef<mlir::Value> extents) 217 : AbstractBox{addr}, AbstractArrayBox(extents, lbounds) {} 218 /// Get the fir.box<type> part of the address type. 219 fir::BaseBoxType getBoxTy() const { 220 auto type = getAddr().getType(); 221 if (auto pointedTy = fir::dyn_cast_ptrEleTy(type)) 222 type = pointedTy; 223 return mlir::cast<fir::BaseBoxType>(type); 224 } 225 /// Return the part of the address type after memory and box types. That is 226 /// the element type, maybe wrapped in a fir.array type. 227 mlir::Type getBaseTy() const { 228 return fir::dyn_cast_ptrOrBoxEleTy(getBoxTy()); 229 } 230 231 /// Return the memory type of the data address inside the box: 232 /// - for fir.box<fir.ptr<T>>, return fir.ptr<T> 233 /// - for fir.box<fir.heap<T>>, return fir.heap<T> 234 /// - for fir.box<T>, return fir.ref<T> 235 mlir::Type getMemTy() const { 236 auto ty = getBoxTy().getEleTy(); 237 if (fir::isa_ref_type(ty)) 238 return ty; 239 return fir::ReferenceType::get(ty); 240 } 241 242 /// Get the scalar type related to the described entity 243 mlir::Type getEleTy() const { 244 auto type = getBaseTy(); 245 if (auto seqTy = mlir::dyn_cast<fir::SequenceType>(type)) 246 return seqTy.getEleTy(); 247 return type; 248 } 249 250 /// Is the entity an array or an assumed rank ? 251 bool hasRank() const { return mlir::isa<fir::SequenceType>(getBaseTy()); } 252 /// Is this an assumed rank ? 253 bool hasAssumedRank() const { 254 auto seqTy = mlir::dyn_cast<fir::SequenceType>(getBaseTy()); 255 return seqTy && seqTy.hasUnknownShape(); 256 } 257 /// Returns the rank of the entity. Beware that zero will be returned for 258 /// both scalars and assumed rank. 259 unsigned rank() const { 260 if (auto seqTy = mlir::dyn_cast<fir::SequenceType>(getBaseTy())) 261 return seqTy.getDimension(); 262 return 0; 263 } 264 265 /// Is this a character entity ? 266 bool isCharacter() const { return fir::isa_char(getEleTy()); } 267 268 /// Is this a derived type entity ? 269 bool isDerived() const { return mlir::isa<fir::RecordType>(getEleTy()); } 270 271 bool isDerivedWithLenParameters() const { 272 return fir::isRecordWithTypeParameters(getEleTy()); 273 } 274 275 /// Is this a polymorphic entity? 276 bool isPolymorphic() const { return fir::isPolymorphicType(getBoxTy()); } 277 278 /// Is this a CLASS(*)/TYPE(*)? 279 bool isUnlimitedPolymorphic() const { 280 return fir::isUnlimitedPolymorphicType(getBoxTy()); 281 } 282 }; 283 284 /// An entity described by a fir.box value that cannot be read into 285 /// another ExtendedValue category, either because the fir.box may be an 286 /// absent optional and we need to wait until the user is referencing it 287 /// to read it, or because it contains important information that cannot 288 /// be exposed in FIR (e.g. non contiguous byte stride). 289 /// It may also store explicit bounds or LEN parameters that were specified 290 /// for the entity. 291 class BoxValue : public AbstractIrBox { 292 public: 293 BoxValue(mlir::Value addr) : AbstractIrBox{addr} { assert(verify()); } 294 BoxValue(mlir::Value addr, llvm::ArrayRef<mlir::Value> lbounds, 295 llvm::ArrayRef<mlir::Value> explicitParams, 296 llvm::ArrayRef<mlir::Value> explicitExtents = {}) 297 : AbstractIrBox{addr, lbounds, explicitExtents}, 298 explicitParams{explicitParams} { 299 assert(verify()); 300 } 301 // TODO: check contiguous attribute of addr 302 bool isContiguous() const { return false; } 303 304 // Replace the fir.box, keeping any non-deferred parameters. 305 BoxValue clone(mlir::Value newBox) const { 306 return {newBox, lbounds, explicitParams, extents}; 307 } 308 309 friend llvm::raw_ostream &operator<<(llvm::raw_ostream &, const BoxValue &); 310 LLVM_DUMP_METHOD void dump() const { llvm::errs() << *this; } 311 312 llvm::ArrayRef<mlir::Value> getLBounds() const { return lbounds; } 313 314 // The extents member is not guaranteed to be field for arrays. It is only 315 // guaranteed to be field for explicit shape arrays. In general, 316 // explicit-shape will not come as descriptors, so this field will be empty in 317 // most cases. The exception are derived types with LEN parameters and 318 // polymorphic dummy argument arrays. It may be possible for the explicit 319 // extents to conflict with the shape information that is in the box according 320 // to 15.5.2.11 sequence association rules. 321 llvm::ArrayRef<mlir::Value> getExplicitExtents() const { return extents; } 322 323 llvm::ArrayRef<mlir::Value> getExplicitParameters() const { 324 return explicitParams; 325 } 326 327 protected: 328 // Verify constructor invariants. 329 bool verify() const; 330 331 // Only field when the BoxValue has explicit LEN parameters. 332 // Otherwise, the LEN parameters are in the fir.box. 333 llvm::SmallVector<mlir::Value, 2> explicitParams; 334 }; 335 336 /// Set of variables (addresses) holding the allocatable properties. These may 337 /// be empty in case it is not deemed safe to duplicate the descriptor 338 /// information locally (For instance, a volatile allocatable will always be 339 /// lowered to a descriptor to preserve the integrity of the entity and its 340 /// associated properties. As such, all references to the entity and its 341 /// property will go through the descriptor explicitly.). 342 class MutableProperties { 343 public: 344 bool isEmpty() const { return !addr; } 345 mlir::Value addr; 346 llvm::SmallVector<mlir::Value, 2> extents; 347 llvm::SmallVector<mlir::Value, 2> lbounds; 348 /// Only keep track of the deferred LEN parameters through variables, since 349 /// they are the only ones that can change as per the deferred type parameters 350 /// definition in F2018 standard section 3.147.12.2. 351 /// Non-deferred values are returned by 352 /// MutableBoxValue.nonDeferredLenParams(). 353 llvm::SmallVector<mlir::Value, 2> deferredParams; 354 }; 355 356 /// MutableBoxValue is used for entities that are represented by the address of 357 /// a box. This is intended to be used for entities whose base address, shape 358 /// and type are not constant in the entity lifetime (e.g Allocatables and 359 /// Pointers). 360 class MutableBoxValue : public AbstractIrBox { 361 public: 362 /// Create MutableBoxValue given the address \p addr of the box and the non 363 /// deferred LEN parameters \p lenParameters. The non deferred LEN parameters 364 /// must always be provided, even if they are constant and already reflected 365 /// in the address type. 366 MutableBoxValue(mlir::Value addr, mlir::ValueRange lenParameters, 367 MutableProperties mutableProperties) 368 : AbstractIrBox(addr), lenParams{lenParameters.begin(), 369 lenParameters.end()}, 370 mutableProperties{mutableProperties} { 371 // Currently only accepts fir.(ref/ptr/heap)<fir.box<type>> mlir::Value for 372 // the address. This may change if we accept 373 // fir.(ref/ptr/heap)<fir.heap<type>> for scalar without LEN parameters. 374 assert(verify() && 375 "MutableBoxValue requires mem ref to fir.box<fir.[heap|ptr]<type>>"); 376 } 377 /// Is this a Fortran pointer ? 378 bool isPointer() const { 379 return mlir::isa<fir::PointerType>(getBoxTy().getEleTy()); 380 } 381 /// Is this an allocatable ? 382 bool isAllocatable() const { 383 return mlir::isa<fir::HeapType>(getBoxTy().getEleTy()); 384 } 385 // Replace the fir.ref<fir.box>, keeping any non-deferred parameters. 386 MutableBoxValue clone(mlir::Value newBox) const { 387 return {newBox, lenParams, mutableProperties}; 388 } 389 /// Does this entity has any non deferred LEN parameters? 390 bool hasNonDeferredLenParams() const { return !lenParams.empty(); } 391 /// Return the non deferred LEN parameters. 392 llvm::ArrayRef<mlir::Value> nonDeferredLenParams() const { return lenParams; } 393 friend llvm::raw_ostream &operator<<(llvm::raw_ostream &, 394 const MutableBoxValue &); 395 LLVM_DUMP_METHOD void dump() const { llvm::errs() << *this; } 396 397 /// Set of variable is used instead of a descriptor to hold the entity 398 /// properties instead of a fir.ref<fir.box<>>. 399 bool isDescribedByVariables() const { return !mutableProperties.isEmpty(); } 400 401 const MutableProperties &getMutableProperties() const { 402 return mutableProperties; 403 } 404 405 protected: 406 /// Validate the address type form in the constructor. 407 bool verify() const; 408 /// Hold the non-deferred LEN parameter values (both for characters and 409 /// derived). Non-deferred LEN parameters cannot change dynamically, as 410 /// opposed to deferred type parameters (3.147.12.2). 411 llvm::SmallVector<mlir::Value, 2> lenParams; 412 /// Set of variables holding the extents, lower bounds and 413 /// base address when it is deemed safe to work with these variables rather 414 /// than directly with a descriptor. 415 MutableProperties mutableProperties; 416 }; 417 418 class ExtendedValue; 419 420 /// Get the base value of an extended value. Every type of extended value has a 421 /// base value or is null. 422 mlir::Value getBase(const ExtendedValue &exv); 423 424 /// Get the LEN property value of an extended value. CHARACTER values have a LEN 425 /// property. 426 mlir::Value getLen(const ExtendedValue &exv); 427 428 /// Pretty-print an extended value. 429 llvm::raw_ostream &operator<<(llvm::raw_ostream &, const ExtendedValue &); 430 431 /// Return a clone of the extended value `exv` with the base value `base` 432 /// substituted. 433 ExtendedValue substBase(const ExtendedValue &exv, mlir::Value base); 434 435 /// Is the extended value `exv` an array? Note that this returns true for 436 /// assumed-ranks that could actually be scalars at runtime. 437 bool isArray(const ExtendedValue &exv); 438 439 /// Get the type parameters for `exv`. 440 llvm::SmallVector<mlir::Value> getTypeParams(const ExtendedValue &exv); 441 442 //===----------------------------------------------------------------------===// 443 // Functions that may generate IR to recover properties from extended values. 444 //===----------------------------------------------------------------------===// 445 namespace factory { 446 447 /// Generalized function to recover dependent type parameters. This does away 448 /// with the distinction between deferred and non-deferred LEN type parameters 449 /// (Fortran definition), since that categorization is irrelevant when getting 450 /// all type parameters for a value of dependent type. 451 llvm::SmallVector<mlir::Value> getTypeParams(mlir::Location loc, 452 FirOpBuilder &builder, 453 const ExtendedValue &exv); 454 455 /// Specialization of get type parameters for an ArrayLoadOp. An array load must 456 /// either have all type parameters given as arguments or be a boxed value. 457 llvm::SmallVector<mlir::Value> 458 getTypeParams(mlir::Location loc, FirOpBuilder &builder, ArrayLoadOp load); 459 460 // The generalized function to get a vector of extents is 461 /// Get extents from \p box. For fir::BoxValue and 462 /// fir::MutableBoxValue, this will generate code to read the extents. 463 llvm::SmallVector<mlir::Value> 464 getExtents(mlir::Location loc, FirOpBuilder &builder, const ExtendedValue &box); 465 466 /// Get exactly one extent for any array-like extended value, \p exv. If \p exv 467 /// is not an array or has rank less then \p dim, the result will be a nullptr. 468 mlir::Value getExtentAtDimension(mlir::Location loc, FirOpBuilder &builder, 469 const ExtendedValue &exv, unsigned dim); 470 471 } // namespace factory 472 473 /// An extended value is a box of values pertaining to a discrete entity. It is 474 /// used in lowering to track all the runtime values related to an entity. For 475 /// example, an entity may have an address in memory that contains its value(s) 476 /// as well as various attribute values that describe the shape and starting 477 /// indices if it is an array entity. 478 class ExtendedValue : public details::matcher<ExtendedValue> { 479 public: 480 using VT = 481 std::variant<UnboxedValue, CharBoxValue, ArrayBoxValue, CharArrayBoxValue, 482 ProcBoxValue, BoxValue, MutableBoxValue, PolymorphicValue>; 483 484 ExtendedValue() : box{UnboxedValue{}} {} 485 template <typename A, typename = std::enable_if_t< 486 !std::is_same_v<std::decay_t<A>, ExtendedValue>>> 487 constexpr ExtendedValue(A &&a) : box{std::forward<A>(a)} { 488 if (const auto *b = getUnboxed()) { 489 if (*b) { 490 auto type = b->getType(); 491 if (mlir::isa<fir::BoxCharType>(type)) 492 fir::emitFatalError(b->getLoc(), "BoxChar should be unboxed"); 493 type = fir::unwrapSequenceType(fir::unwrapRefType(type)); 494 if (fir::isa_char(type)) 495 fir::emitFatalError(b->getLoc(), 496 "character buffer should be in CharBoxValue"); 497 } 498 } 499 } 500 501 template <typename A> 502 constexpr const A *getBoxOf() const { 503 return std::get_if<A>(&box); 504 } 505 506 constexpr const CharBoxValue *getCharBox() const { 507 return getBoxOf<CharBoxValue>(); 508 } 509 510 constexpr const UnboxedValue *getUnboxed() const { 511 return getBoxOf<UnboxedValue>(); 512 } 513 514 unsigned rank() const { 515 return match([](const fir::UnboxedValue &box) -> unsigned { return 0; }, 516 [](const fir::CharBoxValue &box) -> unsigned { return 0; }, 517 [](const fir::ProcBoxValue &box) -> unsigned { return 0; }, 518 [](const fir::PolymorphicValue &box) -> unsigned { return 0; }, 519 [](const auto &box) -> unsigned { return box.rank(); }); 520 } 521 522 bool isPolymorphic() const { 523 return match([](const fir::PolymorphicValue &box) -> bool { return true; }, 524 [](const fir::ArrayBoxValue &box) -> bool { 525 return box.getSourceBox() ? true : false; 526 }, 527 [](const auto &box) -> bool { return false; }); 528 } 529 530 bool hasAssumedRank() const { 531 return match( 532 [](const fir::BoxValue &box) -> bool { return box.hasAssumedRank(); }, 533 [](const fir::MutableBoxValue &box) -> bool { 534 return box.hasAssumedRank(); 535 }, 536 [](const auto &box) -> bool { return false; }); 537 } 538 539 /// LLVM style debugging of extended values 540 LLVM_DUMP_METHOD void dump() const { llvm::errs() << *this << '\n'; } 541 542 friend llvm::raw_ostream &operator<<(llvm::raw_ostream &, 543 const ExtendedValue &); 544 545 const VT &matchee() const { return box; } 546 547 private: 548 VT box; 549 }; 550 551 /// Is the extended value `exv` unboxed and non-null? 552 inline bool isUnboxedValue(const ExtendedValue &exv) { 553 return exv.match( 554 [](const fir::UnboxedValue &box) { return box ? true : false; }, 555 [](const auto &) { return false; }); 556 } 557 558 /// Returns the base type of \p exv. This is the type of \p exv 559 /// without any memory or box type. The sequence type, if any, is kept. 560 inline mlir::Type getBaseTypeOf(const ExtendedValue &exv) { 561 return exv.match( 562 [](const fir::MutableBoxValue &box) { return box.getBaseTy(); }, 563 [](const fir::BoxValue &box) { return box.getBaseTy(); }, 564 [&](const auto &) { 565 return fir::unwrapRefType(fir::getBase(exv).getType()); 566 }); 567 } 568 569 /// Return the scalar type of \p exv type. This removes all 570 /// reference, box, or sequence type from \p exv base. 571 inline mlir::Type getElementTypeOf(const ExtendedValue &exv) { 572 return fir::unwrapSequenceType(getBaseTypeOf(exv)); 573 } 574 575 /// Is the extended value `exv` a derived type with LEN parameters? 576 inline bool isDerivedWithLenParameters(const ExtendedValue &exv) { 577 return fir::isRecordWithTypeParameters(getElementTypeOf(exv)); 578 } 579 580 } // namespace fir 581 582 #endif // FORTRAN_OPTIMIZER_BUILDER_BOXVALUE_H 583