1 //===-- runtime/descriptor-io.h ---------------------------------*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
9 #ifndef FORTRAN_RUNTIME_DESCRIPTOR_IO_H_
10 #define FORTRAN_RUNTIME_DESCRIPTOR_IO_H_
12 // Implementation of I/O data list item transfers based on descriptors.
13 // (All I/O items come through here so that the code is exercised for test;
14 // some scalar I/O data transfer APIs could be changed to bypass their use
15 // of descriptors in the future for better efficiency.)
17 #include "edit-input.h"
18 #include "edit-output.h"
21 #include "terminator.h"
22 #include "type-info.h"
24 #include "flang/Common/optional.h"
25 #include "flang/Common/uint128.h"
26 #include "flang/Runtime/cpp-type.h"
27 #include "flang/Runtime/descriptor.h"
29 namespace Fortran::runtime::io::descr
{
31 inline RT_API_ATTRS A
&ExtractElement(IoStatementState
&io
,
32 const Descriptor
&descriptor
, const SubscriptValue subscripts
[]) {
33 A
*p
{descriptor
.Element
<A
>(subscripts
)};
35 io
.GetIoErrorHandler().Crash("Bad address for I/O item -- null base "
36 "address or subscripts out of range");
41 // Per-category descriptor-based I/O templates
43 // TODO (perhaps as a nontrivial but small starter project): implement
44 // automatic repetition counts, like "10*3.14159", for list-directed and
45 // NAMELIST array output.
47 template <int KIND
, Direction
DIR>
48 inline RT_API_ATTRS
bool FormattedIntegerIO(
49 IoStatementState
&io
, const Descriptor
&descriptor
) {
50 std::size_t numElements
{descriptor
.Elements()};
51 SubscriptValue subscripts
[maxRank
];
52 descriptor
.GetLowerBounds(subscripts
);
53 using IntType
= CppTypeFor
<TypeCategory::Integer
, KIND
>;
55 for (std::size_t j
{0}; j
< numElements
; ++j
) {
56 if (auto edit
{io
.GetNextDataEdit()}) {
57 IntType
&x
{ExtractElement
<IntType
>(io
, descriptor
, subscripts
)};
58 if constexpr (DIR == Direction::Output
) {
59 if (!EditIntegerOutput
<KIND
>(io
, *edit
, x
)) {
62 } else if (edit
->descriptor
!= DataEdit::ListDirectedNullValue
) {
63 if (EditIntegerInput(io
, *edit
, reinterpret_cast<void *>(&x
), KIND
)) {
66 return anyInput
&& edit
->IsNamelist();
69 if (!descriptor
.IncrementSubscripts(subscripts
) && j
+ 1 < numElements
) {
70 io
.GetIoErrorHandler().Crash(
71 "FormattedIntegerIO: subscripts out of bounds");
80 template <int KIND
, Direction
DIR>
81 inline RT_API_ATTRS
bool FormattedRealIO(
82 IoStatementState
&io
, const Descriptor
&descriptor
) {
83 std::size_t numElements
{descriptor
.Elements()};
84 SubscriptValue subscripts
[maxRank
];
85 descriptor
.GetLowerBounds(subscripts
);
86 using RawType
= typename RealOutputEditing
<KIND
>::BinaryFloatingPoint
;
88 for (std::size_t j
{0}; j
< numElements
; ++j
) {
89 if (auto edit
{io
.GetNextDataEdit()}) {
90 RawType
&x
{ExtractElement
<RawType
>(io
, descriptor
, subscripts
)};
91 if constexpr (DIR == Direction::Output
) {
92 if (!RealOutputEditing
<KIND
>{io
, x
}.Edit(*edit
)) {
95 } else if (edit
->descriptor
!= DataEdit::ListDirectedNullValue
) {
96 if (EditRealInput
<KIND
>(io
, *edit
, reinterpret_cast<void *>(&x
))) {
99 return anyInput
&& edit
->IsNamelist();
102 if (!descriptor
.IncrementSubscripts(subscripts
) && j
+ 1 < numElements
) {
103 io
.GetIoErrorHandler().Crash(
104 "FormattedRealIO: subscripts out of bounds");
113 template <int KIND
, Direction
DIR>
114 inline RT_API_ATTRS
bool FormattedComplexIO(
115 IoStatementState
&io
, const Descriptor
&descriptor
) {
116 std::size_t numElements
{descriptor
.Elements()};
117 SubscriptValue subscripts
[maxRank
];
118 descriptor
.GetLowerBounds(subscripts
);
120 io
.get_if
<ListDirectedStatementState
<Direction::Output
>>() != nullptr};
121 using RawType
= typename RealOutputEditing
<KIND
>::BinaryFloatingPoint
;
122 bool anyInput
{false};
123 for (std::size_t j
{0}; j
< numElements
; ++j
) {
124 RawType
*x
{&ExtractElement
<RawType
>(io
, descriptor
, subscripts
)};
126 DataEdit rEdit
, iEdit
;
127 rEdit
.descriptor
= DataEdit::ListDirectedRealPart
;
128 iEdit
.descriptor
= DataEdit::ListDirectedImaginaryPart
;
129 rEdit
.modes
= iEdit
.modes
= io
.mutableModes();
130 if (!RealOutputEditing
<KIND
>{io
, x
[0]}.Edit(rEdit
) ||
131 !RealOutputEditing
<KIND
>{io
, x
[1]}.Edit(iEdit
)) {
135 for (int k
{0}; k
< 2; ++k
, ++x
) {
136 auto edit
{io
.GetNextDataEdit()};
139 } else if constexpr (DIR == Direction::Output
) {
140 if (!RealOutputEditing
<KIND
>{io
, *x
}.Edit(*edit
)) {
143 } else if (edit
->descriptor
== DataEdit::ListDirectedNullValue
) {
145 } else if (EditRealInput
<KIND
>(
146 io
, *edit
, reinterpret_cast<void *>(x
))) {
149 return anyInput
&& edit
->IsNamelist();
153 if (!descriptor
.IncrementSubscripts(subscripts
) && j
+ 1 < numElements
) {
154 io
.GetIoErrorHandler().Crash(
155 "FormattedComplexIO: subscripts out of bounds");
161 template <typename A
, Direction
DIR>
162 inline RT_API_ATTRS
bool FormattedCharacterIO(
163 IoStatementState
&io
, const Descriptor
&descriptor
) {
164 std::size_t numElements
{descriptor
.Elements()};
165 SubscriptValue subscripts
[maxRank
];
166 descriptor
.GetLowerBounds(subscripts
);
167 std::size_t length
{descriptor
.ElementBytes() / sizeof(A
)};
168 auto *listOutput
{io
.get_if
<ListDirectedStatementState
<Direction::Output
>>()};
169 bool anyInput
{false};
170 for (std::size_t j
{0}; j
< numElements
; ++j
) {
171 A
*x
{&ExtractElement
<A
>(io
, descriptor
, subscripts
)};
173 if (!ListDirectedCharacterOutput(io
, *listOutput
, x
, length
)) {
176 } else if (auto edit
{io
.GetNextDataEdit()}) {
177 if constexpr (DIR == Direction::Output
) {
178 if (!EditCharacterOutput(io
, *edit
, x
, length
)) {
182 if (edit
->descriptor
!= DataEdit::ListDirectedNullValue
) {
183 if (EditCharacterInput(io
, *edit
, x
, length
)) {
186 return anyInput
&& edit
->IsNamelist();
193 if (!descriptor
.IncrementSubscripts(subscripts
) && j
+ 1 < numElements
) {
194 io
.GetIoErrorHandler().Crash(
195 "FormattedCharacterIO: subscripts out of bounds");
201 template <int KIND
, Direction
DIR>
202 inline RT_API_ATTRS
bool FormattedLogicalIO(
203 IoStatementState
&io
, const Descriptor
&descriptor
) {
204 std::size_t numElements
{descriptor
.Elements()};
205 SubscriptValue subscripts
[maxRank
];
206 descriptor
.GetLowerBounds(subscripts
);
207 auto *listOutput
{io
.get_if
<ListDirectedStatementState
<Direction::Output
>>()};
208 using IntType
= CppTypeFor
<TypeCategory::Integer
, KIND
>;
209 bool anyInput
{false};
210 for (std::size_t j
{0}; j
< numElements
; ++j
) {
211 IntType
&x
{ExtractElement
<IntType
>(io
, descriptor
, subscripts
)};
213 if (!ListDirectedLogicalOutput(io
, *listOutput
, x
!= 0)) {
216 } else if (auto edit
{io
.GetNextDataEdit()}) {
217 if constexpr (DIR == Direction::Output
) {
218 if (!EditLogicalOutput(io
, *edit
, x
!= 0)) {
222 if (edit
->descriptor
!= DataEdit::ListDirectedNullValue
) {
224 if (EditLogicalInput(io
, *edit
, truth
)) {
228 return anyInput
&& edit
->IsNamelist();
235 if (!descriptor
.IncrementSubscripts(subscripts
) && j
+ 1 < numElements
) {
236 io
.GetIoErrorHandler().Crash(
237 "FormattedLogicalIO: subscripts out of bounds");
243 template <Direction
DIR>
244 static RT_API_ATTRS
bool DescriptorIO(IoStatementState
&, const Descriptor
&,
245 const NonTbpDefinedIoTable
* = nullptr);
247 // For intrinsic (not defined) derived type I/O, formatted & unformatted
248 template <Direction
DIR>
249 static RT_API_ATTRS
bool DefaultComponentIO(IoStatementState
&io
,
250 const typeInfo::Component
&component
, const Descriptor
&origDescriptor
,
251 const SubscriptValue origSubscripts
[], Terminator
&terminator
,
252 const NonTbpDefinedIoTable
*table
) {
253 #if !defined(RT_DEVICE_AVOID_RECURSION)
254 if (component
.genre() == typeInfo::Component::Genre::Data
) {
255 // Create a descriptor for the component
256 StaticDescriptor
<maxRank
, true, 16 /*?*/> statDesc
;
257 Descriptor
&desc
{statDesc
.descriptor()};
258 component
.CreatePointerDescriptor(
259 desc
, origDescriptor
, terminator
, origSubscripts
);
260 return DescriptorIO
<DIR>(io
, desc
, table
);
262 // Component is itself a descriptor
264 origDescriptor
.Element
<char>(origSubscripts
) + component
.offset()};
266 terminator
, component
.genre() == typeInfo::Component::Genre::Automatic
);
267 const Descriptor
&compDesc
{*reinterpret_cast<const Descriptor
*>(pointer
)};
268 return DescriptorIO
<DIR>(io
, compDesc
, table
);
271 terminator
.Crash("not yet implemented: component IO");
275 template <Direction
DIR>
276 static RT_API_ATTRS
bool DefaultComponentwiseFormattedIO(IoStatementState
&io
,
277 const Descriptor
&descriptor
, const typeInfo::DerivedType
&type
,
278 const NonTbpDefinedIoTable
*table
, const SubscriptValue subscripts
[]) {
279 IoErrorHandler
&handler
{io
.GetIoErrorHandler()};
280 const Descriptor
&compArray
{type
.component()};
281 RUNTIME_CHECK(handler
, compArray
.rank() == 1);
282 std::size_t numComponents
{compArray
.Elements()};
283 SubscriptValue at
[maxRank
];
284 compArray
.GetLowerBounds(at
);
285 for (std::size_t k
{0}; k
< numComponents
;
286 ++k
, compArray
.IncrementSubscripts(at
)) {
287 const typeInfo::Component
&component
{
288 *compArray
.Element
<typeInfo::Component
>(at
)};
289 if (!DefaultComponentIO
<DIR>(
290 io
, component
, descriptor
, subscripts
, handler
, table
)) {
291 // Return true for NAMELIST input if any component appeared.
293 io
.get_if
<ListDirectedStatementState
<Direction::Input
>>()};
294 return DIR == Direction::Input
&& k
> 0 && listInput
&&
295 listInput
->inNamelistSequence();
301 template <Direction
DIR>
302 static RT_API_ATTRS
bool DefaultComponentwiseUnformattedIO(IoStatementState
&io
,
303 const Descriptor
&descriptor
, const typeInfo::DerivedType
&type
,
304 const NonTbpDefinedIoTable
*table
) {
305 IoErrorHandler
&handler
{io
.GetIoErrorHandler()};
306 const Descriptor
&compArray
{type
.component()};
307 RUNTIME_CHECK(handler
, compArray
.rank() == 1);
308 std::size_t numComponents
{compArray
.Elements()};
309 std::size_t numElements
{descriptor
.Elements()};
310 SubscriptValue subscripts
[maxRank
];
311 descriptor
.GetLowerBounds(subscripts
);
312 for (std::size_t j
{0}; j
< numElements
;
313 ++j
, descriptor
.IncrementSubscripts(subscripts
)) {
314 SubscriptValue at
[maxRank
];
315 compArray
.GetLowerBounds(at
);
316 for (std::size_t k
{0}; k
< numComponents
;
317 ++k
, compArray
.IncrementSubscripts(at
)) {
318 const typeInfo::Component
&component
{
319 *compArray
.Element
<typeInfo::Component
>(at
)};
320 if (!DefaultComponentIO
<DIR>(
321 io
, component
, descriptor
, subscripts
, handler
, table
)) {
329 RT_API_ATTRS
Fortran::common::optional
<bool> DefinedFormattedIo(
330 IoStatementState
&, const Descriptor
&, const typeInfo::DerivedType
&,
331 const typeInfo::SpecialBinding
&, const SubscriptValue
[]);
333 template <Direction
DIR>
334 static RT_API_ATTRS
bool FormattedDerivedTypeIO(IoStatementState
&io
,
335 const Descriptor
&descriptor
, const NonTbpDefinedIoTable
*table
) {
336 IoErrorHandler
&handler
{io
.GetIoErrorHandler()};
337 // Derived type information must be present for formatted I/O.
338 const DescriptorAddendum
*addendum
{descriptor
.Addendum()};
339 RUNTIME_CHECK(handler
, addendum
!= nullptr);
340 const typeInfo::DerivedType
*type
{addendum
->derivedType()};
341 RUNTIME_CHECK(handler
, type
!= nullptr);
342 Fortran::common::optional
<typeInfo::SpecialBinding
> nonTbpSpecial
;
343 const typeInfo::SpecialBinding
*special
{nullptr};
345 if (const auto *definedIo
{table
->Find(*type
,
346 DIR == Direction::Input
? common::DefinedIo::ReadFormatted
347 : common::DefinedIo::WriteFormatted
)}) {
348 if (definedIo
->subroutine
) {
349 nonTbpSpecial
.emplace(DIR == Direction::Input
350 ? typeInfo::SpecialBinding::Which::ReadFormatted
351 : typeInfo::SpecialBinding::Which::WriteFormatted
,
352 definedIo
->subroutine
, definedIo
->isDtvArgPolymorphic
, false,
354 special
= &*nonTbpSpecial
;
359 if (const typeInfo::SpecialBinding
*
360 binding
{type
->FindSpecialBinding(DIR == Direction::Input
361 ? typeInfo::SpecialBinding::Which::ReadFormatted
362 : typeInfo::SpecialBinding::Which::WriteFormatted
)}) {
363 if (!table
|| !table
->ignoreNonTbpEntries
|| binding
->isTypeBound()) {
368 SubscriptValue subscripts
[maxRank
];
369 descriptor
.GetLowerBounds(subscripts
);
370 std::size_t numElements
{descriptor
.Elements()};
371 for (std::size_t j
{0}; j
< numElements
;
372 ++j
, descriptor
.IncrementSubscripts(subscripts
)) {
373 Fortran::common::optional
<bool> result
;
375 result
= DefinedFormattedIo(io
, descriptor
, *type
, *special
, subscripts
);
378 result
= DefaultComponentwiseFormattedIO
<DIR>(
379 io
, descriptor
, *type
, table
, subscripts
);
381 if (!result
.value()) {
382 // Return true for NAMELIST input if we got anything.
384 io
.get_if
<ListDirectedStatementState
<Direction::Input
>>()};
385 return DIR == Direction::Input
&& j
> 0 && listInput
&&
386 listInput
->inNamelistSequence();
392 RT_API_ATTRS
bool DefinedUnformattedIo(IoStatementState
&, const Descriptor
&,
393 const typeInfo::DerivedType
&, const typeInfo::SpecialBinding
&);
396 template <Direction
DIR>
397 static RT_API_ATTRS
bool UnformattedDescriptorIO(IoStatementState
&io
,
398 const Descriptor
&descriptor
, const NonTbpDefinedIoTable
*table
= nullptr) {
399 IoErrorHandler
&handler
{io
.GetIoErrorHandler()};
400 const DescriptorAddendum
*addendum
{descriptor
.Addendum()};
401 if (const typeInfo::DerivedType
*
402 type
{addendum
? addendum
->derivedType() : nullptr}) {
403 // derived type unformatted I/O
405 if (const auto *definedIo
{table
->Find(*type
,
406 DIR == Direction::Input
? common::DefinedIo::ReadUnformatted
407 : common::DefinedIo::WriteUnformatted
)}) {
408 if (definedIo
->subroutine
) {
409 typeInfo::SpecialBinding special
{DIR == Direction::Input
410 ? typeInfo::SpecialBinding::Which::ReadUnformatted
411 : typeInfo::SpecialBinding::Which::WriteUnformatted
,
412 definedIo
->subroutine
, definedIo
->isDtvArgPolymorphic
, false,
414 if (Fortran::common::optional
<bool> wasDefined
{
415 DefinedUnformattedIo(io
, descriptor
, *type
, special
)}) {
419 return DefaultComponentwiseUnformattedIO
<DIR>(
420 io
, descriptor
, *type
, table
);
424 if (const typeInfo::SpecialBinding
*
425 special
{type
->FindSpecialBinding(DIR == Direction::Input
426 ? typeInfo::SpecialBinding::Which::ReadUnformatted
427 : typeInfo::SpecialBinding::Which::WriteUnformatted
)}) {
428 if (!table
|| !table
->ignoreNonTbpEntries
|| special
->isTypeBound()) {
429 // defined derived type unformatted I/O
430 return DefinedUnformattedIo(io
, descriptor
, *type
, *special
);
433 // Default derived type unformatted I/O
434 // TODO: If no component at any level has defined READ or WRITE
435 // (as appropriate), the elements are contiguous, and no byte swapping
436 // is active, do a block transfer via the code below.
437 return DefaultComponentwiseUnformattedIO
<DIR>(io
, descriptor
, *type
, table
);
439 // intrinsic type unformatted I/O
440 auto *externalUnf
{io
.get_if
<ExternalUnformattedIoStatementState
<DIR>>()};
441 auto *childUnf
{io
.get_if
<ChildUnformattedIoStatementState
<DIR>>()};
443 DIR == Direction::Output
? io
.get_if
<InquireIOLengthState
>() : nullptr};
444 RUNTIME_CHECK(handler
, externalUnf
|| childUnf
|| inq
);
445 std::size_t elementBytes
{descriptor
.ElementBytes()};
446 std::size_t numElements
{descriptor
.Elements()};
447 std::size_t swappingBytes
{elementBytes
};
448 if (auto maybeCatAndKind
{descriptor
.type().GetCategoryAndKind()}) {
449 // Byte swapping units can be smaller than elements, namely
450 // for COMPLEX and CHARACTER.
451 if (maybeCatAndKind
->first
== TypeCategory::Character
) {
452 // swap each character position independently
453 swappingBytes
= maybeCatAndKind
->second
; // kind
454 } else if (maybeCatAndKind
->first
== TypeCategory::Complex
) {
455 // swap real and imaginary components independently
459 SubscriptValue subscripts
[maxRank
];
460 descriptor
.GetLowerBounds(subscripts
);
462 std::conditional_t
<DIR == Direction::Output
, const char, char>;
463 auto Transfer
{[=](CharType
&x
, std::size_t totalBytes
) -> bool {
464 if constexpr (DIR == Direction::Output
) {
465 return externalUnf
? externalUnf
->Emit(&x
, totalBytes
, swappingBytes
)
466 : childUnf
? childUnf
->Emit(&x
, totalBytes
, swappingBytes
)
467 : inq
->Emit(&x
, totalBytes
, swappingBytes
);
469 return externalUnf
? externalUnf
->Receive(&x
, totalBytes
, swappingBytes
)
470 : childUnf
->Receive(&x
, totalBytes
, swappingBytes
);
473 bool swapEndianness
{externalUnf
&& externalUnf
->unit().swapEndianness()};
474 if (!swapEndianness
&&
475 descriptor
.IsContiguous()) { // contiguous unformatted I/O
476 char &x
{ExtractElement
<char>(io
, descriptor
, subscripts
)};
477 return Transfer(x
, numElements
* elementBytes
);
478 } else { // non-contiguous or byte-swapped intrinsic type unformatted I/O
479 for (std::size_t j
{0}; j
< numElements
; ++j
) {
480 char &x
{ExtractElement
<char>(io
, descriptor
, subscripts
)};
481 if (!Transfer(x
, elementBytes
)) {
484 if (!descriptor
.IncrementSubscripts(subscripts
) &&
485 j
+ 1 < numElements
) {
486 handler
.Crash("DescriptorIO: subscripts out of bounds");
494 template <Direction
DIR>
495 static RT_API_ATTRS
bool DescriptorIO(IoStatementState
&io
,
496 const Descriptor
&descriptor
, const NonTbpDefinedIoTable
*table
) {
497 IoErrorHandler
&handler
{io
.GetIoErrorHandler()};
498 if (handler
.InError()) {
501 if (!io
.get_if
<IoDirectionState
<DIR>>()) {
502 handler
.Crash("DescriptorIO() called for wrong I/O direction");
505 if constexpr (DIR == Direction::Input
) {
506 if (!io
.BeginReadingRecord()) {
510 if (!io
.get_if
<FormattedIoStatementState
<DIR>>()) {
511 return UnformattedDescriptorIO
<DIR>(io
, descriptor
, table
);
513 if (auto catAndKind
{descriptor
.type().GetCategoryAndKind()}) {
514 TypeCategory cat
{catAndKind
->first
};
515 int kind
{catAndKind
->second
};
517 case TypeCategory::Integer
:
520 return FormattedIntegerIO
<1, DIR>(io
, descriptor
);
522 return FormattedIntegerIO
<2, DIR>(io
, descriptor
);
524 return FormattedIntegerIO
<4, DIR>(io
, descriptor
);
526 return FormattedIntegerIO
<8, DIR>(io
, descriptor
);
528 return FormattedIntegerIO
<16, DIR>(io
, descriptor
);
531 "not yet implemented: INTEGER(KIND=%d) in formatted IO", kind
);
534 case TypeCategory::Real
:
537 return FormattedRealIO
<2, DIR>(io
, descriptor
);
539 return FormattedRealIO
<3, DIR>(io
, descriptor
);
541 return FormattedRealIO
<4, DIR>(io
, descriptor
);
543 return FormattedRealIO
<8, DIR>(io
, descriptor
);
545 return FormattedRealIO
<10, DIR>(io
, descriptor
);
546 // TODO: case double/double
548 return FormattedRealIO
<16, DIR>(io
, descriptor
);
551 "not yet implemented: REAL(KIND=%d) in formatted IO", kind
);
554 case TypeCategory::Complex
:
557 return FormattedComplexIO
<2, DIR>(io
, descriptor
);
559 return FormattedComplexIO
<3, DIR>(io
, descriptor
);
561 return FormattedComplexIO
<4, DIR>(io
, descriptor
);
563 return FormattedComplexIO
<8, DIR>(io
, descriptor
);
565 return FormattedComplexIO
<10, DIR>(io
, descriptor
);
566 // TODO: case double/double
568 return FormattedComplexIO
<16, DIR>(io
, descriptor
);
571 "not yet implemented: COMPLEX(KIND=%d) in formatted IO", kind
);
574 case TypeCategory::Character
:
577 return FormattedCharacterIO
<char, DIR>(io
, descriptor
);
579 return FormattedCharacterIO
<char16_t
, DIR>(io
, descriptor
);
581 return FormattedCharacterIO
<char32_t
, DIR>(io
, descriptor
);
584 "not yet implemented: CHARACTER(KIND=%d) in formatted IO", kind
);
587 case TypeCategory::Logical
:
590 return FormattedLogicalIO
<1, DIR>(io
, descriptor
);
592 return FormattedLogicalIO
<2, DIR>(io
, descriptor
);
594 return FormattedLogicalIO
<4, DIR>(io
, descriptor
);
596 return FormattedLogicalIO
<8, DIR>(io
, descriptor
);
599 "not yet implemented: LOGICAL(KIND=%d) in formatted IO", kind
);
602 case TypeCategory::Derived
:
603 return FormattedDerivedTypeIO
<DIR>(io
, descriptor
, table
);
606 handler
.Crash("DescriptorIO: bad type code (%d) in descriptor",
607 static_cast<int>(descriptor
.type().raw()));
610 } // namespace Fortran::runtime::io::descr
611 #endif // FORTRAN_RUNTIME_DESCRIPTOR_IO_H_