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