FLANG
DirectivesCommon.h
1//===-- Lower/DirectivesCommon.h --------------------------------*- 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//===----------------------------------------------------------------------===//
16//===----------------------------------------------------------------------===//
17
18#ifndef FORTRAN_LOWER_DIRECTIVES_COMMON_H
19#define FORTRAN_LOWER_DIRECTIVES_COMMON_H
20
21#include "flang/Common/idioms.h"
22#include "flang/Evaluate/tools.h"
23#include "flang/Lower/AbstractConverter.h"
24#include "flang/Lower/Bridge.h"
25#include "flang/Lower/ConvertExpr.h"
26#include "flang/Lower/ConvertVariable.h"
27#include "flang/Lower/OpenACC.h"
28#include "flang/Lower/OpenMP.h"
29#include "flang/Lower/PFTBuilder.h"
30#include "flang/Lower/StatementContext.h"
31#include "flang/Lower/Support/Utils.h"
32#include "flang/Optimizer/Builder/DirectivesCommon.h"
33#include "flang/Optimizer/Builder/HLFIRTools.h"
34#include "flang/Optimizer/Dialect/FIRType.h"
35#include "flang/Parser/parse-tree.h"
36#include "flang/Semantics/openmp-directive-sets.h"
37#include "flang/Semantics/tools.h"
38#include "mlir/Dialect/OpenACC/OpenACC.h"
39#include "mlir/Dialect/OpenMP/OpenMPDialect.h"
40#include "mlir/Dialect/SCF/IR/SCF.h"
41#include "mlir/IR/Value.h"
42#include <list>
43#include <type_traits>
44
45namespace Fortran {
46namespace lower {
47
50template <typename... TerminatorOps>
52 fir::FirOpBuilder &builder,
53 std::list<Fortran::lower::pft::Evaluation> &evaluationList) {
54 mlir::Region *region = &builder.getRegion();
55 for (Fortran::lower::pft::Evaluation &eval : evaluationList) {
56 if (eval.block) {
57 if (eval.block->empty()) {
58 eval.block->erase();
59 eval.block = builder.createBlock(region);
60 } else {
61 [[maybe_unused]] mlir::Operation &terminatorOp = eval.block->back();
62 assert(mlir::isa<TerminatorOps...>(terminatorOp) &&
63 "expected terminator op");
64 }
65 }
66 if (!eval.isDirective() && eval.hasNestedEvaluations())
67 createEmptyRegionBlocks<TerminatorOps...>(builder,
68 eval.getNestedEvaluations());
69 }
70}
71
73getDataOperandBaseAddr(Fortran::lower::AbstractConverter &converter,
74 fir::FirOpBuilder &builder,
75 Fortran::lower::SymbolRef sym, mlir::Location loc,
76 bool unwrapFirBox = true) {
77 return fir::factory::getDataOperandBaseAddr(
78 builder, converter.getSymbolAddress(sym),
79 Fortran::semantics::IsOptional(sym), loc, unwrapFirBox);
80}
81
82namespace detail {
83template <typename T> //
84static T &&AsRvalueRef(T &&t) {
85 return std::move(t);
86}
87template <typename T> //
88static T AsRvalueRef(T &t) {
89 return t;
90}
91template <typename T> //
92static T AsRvalueRef(const T &t) {
93 return t;
94}
95
96// Helper class for stripping enclosing parentheses and a conversion that
97// preserves type category. This is used for triplet elements, which are
98// always of type integer(kind=8). The lower/upper bounds are converted to
99// an "index" type, which is 64-bit, so the explicit conversion to kind=8
100// (if present) is not needed. When it's present, though, it causes generated
101// names to contain "int(..., kind=8)".
103 template <Fortran::common::TypeCategory Category, int Kind>
104 static Fortran::semantics::MaybeExpr visit_with_category(
106 &expr) {
107 return Fortran::common::visit(
108 [](auto &&s) { return visit_with_category<Category, Kind>(s); },
109 expr.u);
110 }
111 template <Fortran::common::TypeCategory Category, int Kind>
112 static Fortran::semantics::MaybeExpr visit_with_category(
114 Category> &expr) {
115 return AsGenericExpr(AsRvalueRef(expr.left()));
116 }
117 template <Fortran::common::TypeCategory Category, int Kind, typename T>
118 static Fortran::semantics::MaybeExpr visit_with_category(const T &) {
119 return std::nullopt; //
120 }
121 template <Fortran::common::TypeCategory Category, typename T>
122 static Fortran::semantics::MaybeExpr visit_with_category(const T &) {
123 return std::nullopt; //
124 }
125
126 template <Fortran::common::TypeCategory Category>
127 static Fortran::semantics::MaybeExpr
129 &expr) {
130 return Fortran::common::visit(
131 [](auto &&s) { return visit_with_category<Category>(s); }, expr.u);
132 }
133 static Fortran::semantics::MaybeExpr
135 return Fortran::common::visit([](auto &&s) { return visit(s); }, expr.u);
136 }
137 template <typename T> //
138 static Fortran::semantics::MaybeExpr visit(const T &) {
139 return std::nullopt;
140 }
141};
142
143static inline Fortran::semantics::SomeExpr
144peelOuterConvert(Fortran::semantics::SomeExpr &expr) {
145 if (auto peeled = PeelConvert::visit(expr))
146 return *peeled;
147 return expr;
148}
149} // namespace detail
150
153template <typename BoundsOp, typename BoundsType>
155genBoundsOps(fir::FirOpBuilder &builder, mlir::Location loc,
158 const std::vector<Fortran::evaluate::Subscript> &subscripts,
159 std::stringstream &asFortran, fir::ExtendedValue &dataExv,
160 bool dataExvIsAssumedSize, fir::factory::AddrAndBoundsInfo &info,
161 bool treatIndexAsSection = false,
162 bool strideIncludeLowerExtent = false) {
163 int dimension = 0;
164 mlir::Type idxTy = builder.getIndexType();
165 mlir::Type boundTy = builder.getType<BoundsType>();
167
168 mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
169 mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
170 const int dataExvRank = static_cast<int>(dataExv.rank());
171 mlir::Value cumulativeExtent = one;
172 for (const auto &subscript : subscripts) {
173 const auto *triplet{std::get_if<Fortran::evaluate::Triplet>(&subscript.u)};
174 if (triplet || treatIndexAsSection) {
175 if (dimension != 0)
176 asFortran << ',';
177 mlir::Value lbound, ubound, extent;
178 std::optional<std::int64_t> lval, uval;
179 mlir::Value baseLb =
180 fir::factory::readLowerBound(builder, loc, dataExv, dimension, one);
181 bool defaultLb = baseLb == one;
182 mlir::Value stride = one;
183 bool strideInBytes = false;
184
185 if (mlir::isa<fir::BaseBoxType>(
186 fir::unwrapRefType(info.addr.getType()))) {
187 if (info.isPresent) {
188 stride =
189 builder
190 .genIfOp(loc, idxTy, info.isPresent, /*withElseRegion=*/true)
191 .genThen([&]() {
192 mlir::Value box =
193 !fir::isBoxAddress(info.addr.getType())
194 ? info.addr
195 : fir::LoadOp::create(builder, loc, info.addr);
196 mlir::Value d =
197 builder.createIntegerConstant(loc, idxTy, dimension);
198 auto dimInfo = fir::BoxDimsOp::create(builder, loc, idxTy,
199 idxTy, idxTy, box, d);
200 fir::ResultOp::create(builder, loc,
201 dimInfo.getByteStride());
202 })
203 .genElse([&] {
204 mlir::Value zero =
205 builder.createIntegerConstant(loc, idxTy, 0);
206 fir::ResultOp::create(builder, loc, zero);
207 })
208 .getResults()[0];
209 } else {
210 mlir::Value box = !fir::isBoxAddress(info.addr.getType())
211 ? info.addr
212 : fir::LoadOp::create(builder, loc, info.addr);
213 mlir::Value d = builder.createIntegerConstant(loc, idxTy, dimension);
214 auto dimInfo =
215 fir::BoxDimsOp::create(builder, loc, idxTy, idxTy, idxTy, box, d);
216 stride = dimInfo.getByteStride();
217 }
218 strideInBytes = true;
219 }
220
221 Fortran::semantics::MaybeExpr lower;
222 if (triplet) {
223 lower = Fortran::evaluate::AsGenericExpr(triplet->lower());
224 } else {
225 // Case of IndirectSubscriptIntegerExpr
226 using IndirectSubscriptIntegerExpr =
227 Fortran::evaluate::IndirectSubscriptIntegerExpr;
228 using SubscriptInteger = Fortran::evaluate::SubscriptInteger;
230 std::get<IndirectSubscriptIntegerExpr>(subscript.u).value();
231 lower = Fortran::evaluate::AsGenericExpr(std::move(oneInt));
232 if (lower->Rank() > 0) {
233 mlir::emitError(
234 loc, "vector subscript cannot be used for an array section");
235 break;
236 }
237 }
238 if (lower) {
239 lval = Fortran::evaluate::ToInt64(*lower);
240 if (lval) {
241 if (defaultLb) {
242 lbound = builder.createIntegerConstant(loc, idxTy, *lval - 1);
243 } else {
244 mlir::Value lb = builder.createIntegerConstant(loc, idxTy, *lval);
245 lbound = mlir::arith::SubIOp::create(builder, loc, lb, baseLb);
246 }
247 asFortran << *lval;
248 } else {
249 mlir::Value lb =
250 fir::getBase(converter.genExprValue(loc, *lower, stmtCtx));
251 lb = builder.createConvert(loc, baseLb.getType(), lb);
252 lbound = mlir::arith::SubIOp::create(builder, loc, lb, baseLb);
253 asFortran << detail::peelOuterConvert(*lower).AsFortran();
254 }
255 } else {
256 // If the lower bound is not specified, then the section
257 // starts from offset 0 of the dimension.
258 // Note that the lowerbound in the BoundsOp is always 0-based.
259 lbound = zero;
260 }
261
262 if (!triplet) {
263 // If it is a scalar subscript, then the upper bound
264 // is equal to the lower bound, and the extent is one.
265 ubound = lbound;
266 extent = one;
267 } else {
268 asFortran << ':';
269 Fortran::semantics::MaybeExpr upper =
270 Fortran::evaluate::AsGenericExpr(triplet->upper());
271
272 if (upper) {
273 uval = Fortran::evaluate::ToInt64(*upper);
274 if (uval) {
275 if (defaultLb) {
276 ubound = builder.createIntegerConstant(loc, idxTy, *uval - 1);
277 } else {
278 mlir::Value ub = builder.createIntegerConstant(loc, idxTy, *uval);
279 ubound = mlir::arith::SubIOp::create(builder, loc, ub, baseLb);
280 }
281 asFortran << *uval;
282 } else {
283 mlir::Value ub =
284 fir::getBase(converter.genExprValue(loc, *upper, stmtCtx));
285 ub = builder.createConvert(loc, baseLb.getType(), ub);
286 ubound = mlir::arith::SubIOp::create(builder, loc, ub, baseLb);
287 asFortran << detail::peelOuterConvert(*upper).AsFortran();
288 }
289 }
290 if (lower && upper) {
291 if (lval && uval && *uval < *lval) {
292 mlir::emitError(loc, "zero sized array section");
293 break;
294 } else {
295 // Stride is mandatory in evaluate::Triplet. Make sure it's 1.
296 auto val = Fortran::evaluate::ToInt64(triplet->GetStride());
297 if (!val || *val != 1) {
298 mlir::emitError(loc, "stride cannot be specified on "
299 "an array section");
300 break;
301 }
302 }
303 }
304
305 if (info.isPresent && mlir::isa<fir::BaseBoxType>(
306 fir::unwrapRefType(info.addr.getType()))) {
307 extent =
308 builder
309 .genIfOp(loc, idxTy, info.isPresent, /*withElseRegion=*/true)
310 .genThen([&]() {
311 mlir::Value ext = fir::factory::readExtent(
312 builder, loc, dataExv, dimension);
313 fir::ResultOp::create(builder, loc, ext);
314 })
315 .genElse([&] {
316 mlir::Value zero =
317 builder.createIntegerConstant(loc, idxTy, 0);
318 fir::ResultOp::create(builder, loc, zero);
319 })
320 .getResults()[0];
321 } else {
322 extent = fir::factory::readExtent(builder, loc, dataExv, dimension);
323 }
324
325 if (dataExvIsAssumedSize && dimension + 1 == dataExvRank) {
326 extent = zero;
327 if (ubound && lbound) {
328 mlir::Value diff =
329 mlir::arith::SubIOp::create(builder, loc, ubound, lbound);
330 extent = mlir::arith::AddIOp::create(builder, loc, diff, one);
331 }
332 if (!ubound)
333 ubound = lbound;
334 }
335
336 if (!ubound) {
337 // ub = extent - 1
338 ubound = mlir::arith::SubIOp::create(builder, loc, extent, one);
339 }
340 }
341
342 // When the strideInBytes is true, it means the stride is from descriptor
343 // and this already includes the lower extents.
344 if (strideIncludeLowerExtent && !strideInBytes) {
345 stride = cumulativeExtent;
346 cumulativeExtent = builder.createOrFold<mlir::arith::MulIOp>(
347 loc, cumulativeExtent, extent);
348 }
349
350 mlir::Value bound =
351 BoundsOp::create(builder, loc, boundTy, lbound, ubound, extent,
352 stride, strideInBytes, baseLb);
353 bounds.push_back(bound);
354 ++dimension;
355 }
356 }
357 return bounds;
358}
359
360namespace detail {
361template <typename Ref, typename Expr> //
362std::optional<Ref> getRef(Expr &&expr) {
363 if constexpr (std::is_same_v<llvm::remove_cvref_t<Expr>,
365 if (auto *ref = std::get_if<Ref>(&expr.u))
366 return *ref;
367 return std::nullopt;
368 } else {
369 auto maybeRef = Fortran::evaluate::ExtractDataRef(expr);
370 if (!maybeRef || !std::holds_alternative<Ref>(maybeRef->u))
371 return std::nullopt;
372 return std::get<Ref>(maybeRef->u);
373 }
374}
375} // namespace detail
376
377template <typename BoundsOp, typename BoundsType>
378fir::factory::AddrAndBoundsInfo gatherDataOperandAddrAndBounds(
379 Fortran::lower::AbstractConverter &converter, fir::FirOpBuilder &builder,
380 semantics::SemanticsContext &semaCtx,
381 Fortran::lower::StatementContext &stmtCtx,
382 Fortran::semantics::SymbolRef symbol,
383 const Fortran::semantics::MaybeExpr &maybeDesignator,
384 mlir::Location operandLocation, std::stringstream &asFortran,
385 llvm::SmallVector<mlir::Value> &bounds, bool treatIndexAsSection = false,
386 bool unwrapFirBox = true, bool genDefaultBounds = true,
387 bool strideIncludeLowerExtent = false) {
388 using namespace Fortran;
389
390 fir::factory::AddrAndBoundsInfo info;
391
392 if (!maybeDesignator) {
393 info = getDataOperandBaseAddr(converter, builder, symbol, operandLocation,
394 unwrapFirBox);
395 asFortran << symbol->name().ToString();
396 return info;
397 }
398
399 semantics::SomeExpr designator = *maybeDesignator;
400
401 if ((designator.Rank() > 0 || treatIndexAsSection) &&
402 IsArrayElement(designator)) {
403 auto arrayRef = detail::getRef<evaluate::ArrayRef>(designator);
404 // This shouldn't fail after IsArrayElement(designator).
405 assert(arrayRef && "Expecting ArrayRef");
406
407 fir::ExtendedValue dataExv;
408 bool dataExvIsAssumedSize = false;
409
410 auto toMaybeExpr = [&](auto &&base) {
411 using BaseType = llvm::remove_cvref_t<decltype(base)>;
412 evaluate::ExpressionAnalyzer ea{semaCtx};
413
414 if constexpr (std::is_same_v<evaluate::NamedEntity, BaseType>) {
415 if (auto *ref = base.UnwrapSymbolRef())
416 return ea.Designate(evaluate::DataRef{*ref});
417 if (auto *ref = base.UnwrapComponent())
418 return ea.Designate(evaluate::DataRef{*ref});
419 llvm_unreachable("Unexpected NamedEntity");
420 } else {
421 static_assert(std::is_same_v<semantics::SymbolRef, BaseType>);
422 return ea.Designate(evaluate::DataRef{base});
423 }
424 };
425
426 auto arrayBase = toMaybeExpr(arrayRef->base());
427 assert(arrayBase);
428
429 if (detail::getRef<evaluate::Component>(*arrayBase)) {
430 dataExv = converter.genExprAddr(operandLocation, *arrayBase, stmtCtx);
431 info.addr = fir::getBase(dataExv);
432 info.rawInput = info.addr;
433 asFortran << arrayBase->AsFortran();
434 } else {
435 const semantics::Symbol &sym = arrayRef->GetLastSymbol();
436 dataExvIsAssumedSize =
437 Fortran::semantics::IsAssumedSizeArray(sym.GetUltimate());
438 info = getDataOperandBaseAddr(converter, builder, sym, operandLocation,
439 unwrapFirBox);
440 dataExv = converter.getSymbolExtendedValue(sym);
441 asFortran << sym.name().ToString();
442 }
443
444 if (!arrayRef->subscript().empty()) {
445 asFortran << '(';
447 builder, operandLocation, converter, stmtCtx, arrayRef->subscript(),
448 asFortran, dataExv, dataExvIsAssumedSize, info, treatIndexAsSection,
449 strideIncludeLowerExtent);
450 }
451 asFortran << ')';
452 } else if (auto compRef = detail::getRef<evaluate::Component>(designator)) {
453 fir::ExtendedValue compExv =
454 converter.genExprAddr(operandLocation, designator, stmtCtx);
455 info.addr = fir::getBase(compExv);
456 info.rawInput = info.addr;
457 if (genDefaultBounds &&
458 mlir::isa<fir::SequenceType>(fir::unwrapRefType(info.addr.getType())))
460 builder, operandLocation, compExv,
461 /*isAssumedSize=*/false, strideIncludeLowerExtent);
462 asFortran << designator.AsFortran();
463
464 if (semantics::IsOptional(compRef->GetLastSymbol())) {
465 info.isPresent = fir::IsPresentOp::create(
466 builder, operandLocation, builder.getI1Type(), info.rawInput);
467 }
468
469 if (unwrapFirBox) {
470 if (auto loadOp =
471 mlir::dyn_cast_or_null<fir::LoadOp>(info.addr.getDefiningOp())) {
472 if (fir::isAllocatableType(loadOp.getType()) ||
473 fir::isPointerType(loadOp.getType())) {
474 info.boxType = info.addr.getType();
475 info.addr =
476 fir::BoxAddrOp::create(builder, operandLocation, info.addr);
477 }
478 info.rawInput = info.addr;
479 }
480 }
481
482 // If the component is an allocatable or pointer the result of
483 // genExprAddr will be the result of a fir.box_addr operation or
484 // a fir.box_addr has been inserted just before.
485 // Retrieve the box so we handle it like other descriptor.
486 if (auto boxAddrOp =
487 mlir::dyn_cast_or_null<fir::BoxAddrOp>(info.addr.getDefiningOp())) {
488 info.addr = boxAddrOp.getVal();
489 info.boxType = info.addr.getType();
490 info.rawInput = info.addr;
491 if (genDefaultBounds)
493 builder, operandLocation, compExv, info);
494 }
495 } else {
496 if (detail::getRef<evaluate::ArrayRef>(designator)) {
497 fir::ExtendedValue compExv =
498 converter.genExprAddr(operandLocation, designator, stmtCtx);
499 info.addr = fir::getBase(compExv);
500 info.rawInput = info.addr;
501 asFortran << designator.AsFortran();
502 } else if (auto symRef = detail::getRef<semantics::SymbolRef>(designator)) {
503 // Scalar or full array.
504 fir::ExtendedValue dataExv = converter.getSymbolExtendedValue(*symRef);
505 info = getDataOperandBaseAddr(converter, builder, *symRef,
506 operandLocation, unwrapFirBox);
507 if (genDefaultBounds && mlir::isa<fir::BaseBoxType>(
508 fir::unwrapRefType(info.addr.getType()))) {
509 info.boxType = fir::unwrapRefType(info.addr.getType());
511 builder, operandLocation, dataExv, info);
512 }
513 bool dataExvIsAssumedSize =
514 Fortran::semantics::IsAssumedSizeArray(symRef->get().GetUltimate());
515 if (genDefaultBounds &&
516 mlir::isa<fir::SequenceType>(fir::unwrapRefType(info.addr.getType())))
518 builder, operandLocation, dataExv, dataExvIsAssumedSize,
519 strideIncludeLowerExtent);
520 asFortran << symRef->get().name().ToString();
521 } else { // Unsupported
522 llvm::report_fatal_error("Unsupported type of OpenACC operand");
523 }
524 }
525
526 return info;
527}
528
529} // namespace lower
530} // namespace Fortran
531
532#endif // FORTRAN_LOWER_DIRECTIVES_COMMON_H
Definition common.h:214
Definition type.h:57
Definition AbstractConverter.h:85
virtual mlir::Value getSymbolAddress(SymbolRef sym)=0
Get the mlir instance of a symbol.
virtual fir::ExtendedValue genExprValue(const SomeExpr &expr, StatementContext &context, mlir::Location *locPtr=nullptr)=0
Generate the computations of the expression to produce a value.
virtual fir::ExtendedValue genExprAddr(const SomeExpr &expr, StatementContext &context, mlir::Location *locPtr=nullptr)=0
Definition StatementContext.h:46
Definition BoxValue.h:478
Definition FIRBuilder.h:55
mlir::Value createConvert(mlir::Location loc, mlir::Type toTy, mlir::Value val)
Lazy creation of fir.convert op.
Definition FIRBuilder.cpp:611
IfBuilder genIfOp(mlir::Location loc, mlir::TypeRange results, mlir::Value cdt, bool withElseRegion)
Definition FIRBuilder.h:538
mlir::Region & getRegion()
Get the current Region of the insertion point.
Definition FIRBuilder.h:109
mlir::Value createIntegerConstant(mlir::Location loc, mlir::Type integerType, std::int64_t i)
Definition FIRBuilder.cpp:144
Definition OpenACC.h:20
Definition ParserActions.h:24
llvm::SmallVector< mlir::Value > genBoundsOps(fir::FirOpBuilder &builder, mlir::Location loc, Fortran::lower::AbstractConverter &converter, Fortran::lower::StatementContext &stmtCtx, const std::vector< Fortran::evaluate::Subscript > &subscripts, std::stringstream &asFortran, fir::ExtendedValue &dataExv, bool dataExvIsAssumedSize, fir::factory::AddrAndBoundsInfo &info, bool treatIndexAsSection=false, bool strideIncludeLowerExtent=false)
Definition DirectivesCommon.h:155
void createEmptyRegionBlocks(fir::FirOpBuilder &builder, std::list< Fortran::lower::pft::Evaluation > &evaluationList)
Definition DirectivesCommon.h:51
Definition bit-population-count.h:20
llvm::SmallVector< mlir::Value > genBaseBoundsOps(fir::FirOpBuilder &builder, mlir::Location loc, fir::ExtendedValue dataExv, bool isAssumedSize, bool strideIncludeLowerExtent=false)
Definition DirectivesCommon.h:278
llvm::SmallVector< mlir::Value > genBoundsOpsFromBox(fir::FirOpBuilder &builder, mlir::Location loc, fir::ExtendedValue dataExv, AddrAndBoundsInfo &info)
Generate the bounds operation from the descriptor information.
Definition DirectivesCommon.h:211
mlir::Value readLowerBound(fir::FirOpBuilder &builder, mlir::Location loc, const fir::ExtendedValue &box, unsigned dim, mlir::Value defaultValue)
Definition FIRBuilder.cpp:1008
mlir::Value readExtent(fir::FirOpBuilder &builder, mlir::Location loc, const fir::ExtendedValue &box, unsigned dim)
Read or get the extent in dimension dim of the array described by box.
Definition FIRBuilder.cpp:977
bool isBoxAddress(mlir::Type t)
Is t an address to fir.box or class type?
Definition FIRType.h:512
mlir::Value getBase(const ExtendedValue &exv)
Definition BoxValue.cpp:21
bool isPointerType(mlir::Type ty)
Definition FIRType.cpp:305
bool isAllocatableType(mlir::Type ty)
Return true iff ty is the type of an ALLOCATABLE entity or value.
Definition FIRType.cpp:313
Definition expression.h:211
Definition variable.h:284
Definition type.h:399
Definition DirectivesCommon.h:102
Definition PFTBuilder.h:221
Definition DirectivesCommon.h:33