Skip to content

Commit 43fadef

Browse files
committed
[flang] Implement user-defined derived type runtime I/O
With derived type description tables now available to the runtime library, it is possible to implement the concept of "child" I/O statements in the runtime and use them to convert instances of derived type I/O data transfers into calls to user-defined subroutines when they have been specified for a type. (See Fortran 2018, subclauses 12.6.4.8 & 13.7.6). - Support formatted, list-directed, and NAMELIST transfers to internal parent units; support these, and unformatted transfers, for external parent units. - Support nested child defined derived type I/O. - Parse DT'foo'(v-list) FORMAT data edit descriptors and passes their strings &/or v-list values as arguments to the defined formatted I/O routines. - Fix problems with this feature encountered in semantics and FORMAT valiation during development and end-to-end testing. - Convert typeInfo::SpecialBinding from a struct to a class after adding a member function. Differential Revision: https://reviews.llvm.org/D104930
1 parent ad6bee8 commit 43fadef

21 files changed

+984
-343
lines changed

flang/include/flang/Common/format.h

+6-4
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,11 @@ template <typename CHAR = char> class FormatValidator {
136136
const CHAR *cursor_{}; // current location in format_
137137
const CHAR *laCursor_{}; // lookahead cursor
138138
Token token_{}; // current token
139+
TokenKind previousTokenKind_{TokenKind::None};
139140
int64_t integerValue_{-1}; // value of UnsignedInteger token
140141
Token knrToken_{}; // k, n, or r UnsignedInteger token
141142
int64_t knrValue_{-1}; // -1 ==> not present
142143
int64_t wValue_{-1};
143-
bool previousTokenWasInt_{false};
144144
char argString_[3]{}; // 1-2 character msg arg; usually edit descriptor name
145145
bool formatHasErrors_{false};
146146
bool unterminatedFormatError_{false};
@@ -179,7 +179,7 @@ template <typename CHAR> void FormatValidator<CHAR>::NextToken() {
179179
// At entry, cursor_ points before the start of the next token.
180180
// At exit, cursor_ points to last CHAR of token_.
181181

182-
previousTokenWasInt_ = token_.kind() == TokenKind::UnsignedInteger;
182+
previousTokenKind_ = token_.kind();
183183
CHAR c{NextChar()};
184184
token_.set_kind(TokenKind::None);
185185
token_.set_offset(cursor_ - format_);
@@ -416,7 +416,8 @@ template <typename CHAR> void FormatValidator<CHAR>::NextToken() {
416416
}
417417
}
418418
SetLength();
419-
if (stmt_ == IoStmtKind::Read) { // 13.3.2p6
419+
if (stmt_ == IoStmtKind::Read &&
420+
previousTokenKind_ != TokenKind::DT) { // 13.3.2p6
420421
ReportError("String edit descriptor in READ format expression");
421422
} else if (token_.kind() != TokenKind::String) {
422423
ReportError("Unterminated string");
@@ -829,7 +830,8 @@ template <typename CHAR> bool FormatValidator<CHAR>::Check() {
829830
// Possible first token of the next format item; token not yet processed.
830831
if (commaRequired) {
831832
const char *s{"Expected ',' or ')' in format expression"}; // C1302
832-
if (previousTokenWasInt_ && itemsWithLeadingInts_.test(token_.kind())) {
833+
if (previousTokenKind_ == TokenKind::UnsignedInteger &&
834+
itemsWithLeadingInts_.test(token_.kind())) {
833835
ReportError(s);
834836
} else {
835837
ReportWarning(s);

flang/lib/Semantics/check-declarations.cpp

+8-2
Original file line numberDiff line numberDiff line change
@@ -1797,9 +1797,15 @@ void CheckHelper::CheckAlreadySeenDefinedIo(const DerivedTypeSpec *derivedType,
17971797
void CheckHelper::CheckDioDummyIsDerived(
17981798
const Symbol &subp, const Symbol &arg, GenericKind::DefinedIo ioKind) {
17991799
if (const DeclTypeSpec * type{arg.GetType()}) {
1800-
const DerivedTypeSpec *derivedType{type->AsDerived()};
1801-
if (derivedType) {
1800+
if (const DerivedTypeSpec * derivedType{type->AsDerived()}) {
18021801
CheckAlreadySeenDefinedIo(derivedType, ioKind, subp);
1802+
bool isPolymorphic{type->IsPolymorphic()};
1803+
if (isPolymorphic != IsExtensibleType(derivedType)) {
1804+
messages_.Say(arg.name(),
1805+
"Dummy argument '%s' of a defined input/output procedure must be %s when the derived type is %s"_err_en_US,
1806+
arg.name(), isPolymorphic ? "TYPE()" : "CLASS()",
1807+
isPolymorphic ? "not extensible" : "extensible");
1808+
}
18031809
} else {
18041810
messages_.Say(arg.name(),
18051811
"Dummy argument '%s' of a defined input/output procedure must have a"

flang/runtime/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ add_flang_library(FortranRuntime
4040
connection.cpp
4141
derived.cpp
4242
descriptor.cpp
43+
descriptor-io.cpp
4344
dot-product.cpp
4445
edit-input.cpp
4546
edit-output.cpp

flang/runtime/derived.cpp

+7-7
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ static const typeInfo::SpecialBinding *FindFinal(
2020
for (std::size_t j{0}; j < totalSpecialBindings; ++j) {
2121
const auto &special{
2222
*specialDesc.ZeroBasedIndexedElement<typeInfo::SpecialBinding>(j)};
23-
switch (special.which) {
23+
switch (special.which()) {
2424
case typeInfo::SpecialBinding::Which::Final:
25-
if (special.rank == rank) {
25+
if (special.rank() == rank) {
2626
return &special;
2727
}
2828
break;
@@ -40,20 +40,20 @@ static const typeInfo::SpecialBinding *FindFinal(
4040
static void CallFinalSubroutine(
4141
const Descriptor &descriptor, const typeInfo::DerivedType &derived) {
4242
if (const auto *special{FindFinal(derived, descriptor.rank())}) {
43-
if (special->which == typeInfo::SpecialBinding::Which::ElementalFinal) {
43+
if (special->which() == typeInfo::SpecialBinding::Which::ElementalFinal) {
4444
std::size_t byteStride{descriptor.ElementBytes()};
45-
auto p{reinterpret_cast<void (*)(char *)>(special->proc)};
45+
auto *p{special->GetProc<void (*)(char *)>()};
4646
// Finalizable objects must be contiguous.
4747
std::size_t elements{descriptor.Elements()};
4848
for (std::size_t j{0}; j < elements; ++j) {
4949
p(descriptor.OffsetElement<char>(j * byteStride));
5050
}
51-
} else if (special->isArgDescriptorSet & 1) {
52-
auto p{reinterpret_cast<void (*)(const Descriptor &)>(special->proc)};
51+
} else if (special->IsArgDescriptor(0)) {
52+
auto *p{special->GetProc<void (*)(const Descriptor &)>()};
5353
p(descriptor);
5454
} else {
5555
// Finalizable objects must be contiguous.
56-
auto p{reinterpret_cast<void (*)(char *)>(special->proc)};
56+
auto *p{special->GetProc<void (*)(char *)>()};
5757
p(descriptor.OffsetElement<char>());
5858
}
5959
}

flang/runtime/descriptor-io.cpp

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
//===-- runtime/descriptor-io.cpp -----------------------------------------===//
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+
#include "descriptor-io.h"
10+
11+
namespace Fortran::runtime::io::descr {
12+
13+
// User-defined derived type formatted I/O (maybe)
14+
std::optional<bool> DefinedFormattedIo(IoStatementState &io,
15+
const Descriptor &descriptor, const typeInfo::SpecialBinding &special) {
16+
std::optional<DataEdit> peek{io.GetNextDataEdit(0 /*to peek at it*/)};
17+
if (peek &&
18+
(peek->descriptor == DataEdit::DefinedDerivedType ||
19+
peek->descriptor == DataEdit::ListDirected)) {
20+
// User-defined derived type formatting
21+
IoErrorHandler &handler{io.GetIoErrorHandler()};
22+
DataEdit edit{*io.GetNextDataEdit()}; // consume it this time
23+
RUNTIME_CHECK(handler, edit.descriptor == peek->descriptor);
24+
char ioType[2 + edit.maxIoTypeChars];
25+
auto ioTypeLen{std::size_t{2} /*"DT"*/ + edit.ioTypeChars};
26+
if (edit.descriptor == DataEdit::DefinedDerivedType) {
27+
ioType[0] = 'D';
28+
ioType[1] = 'T';
29+
std::memcpy(ioType + 2, edit.ioType, edit.ioTypeChars);
30+
} else {
31+
std::strcpy(
32+
ioType, io.mutableModes().inNamelist ? "NAMELIST" : "LISTDIRECTED");
33+
ioTypeLen = std::strlen(ioType);
34+
}
35+
StaticDescriptor<0, true> statDesc;
36+
Descriptor &vListDesc{statDesc.descriptor()};
37+
vListDesc.Establish(TypeCategory::Integer, sizeof(int), nullptr, 1);
38+
vListDesc.set_base_addr(edit.vList);
39+
vListDesc.GetDimension(0).SetBounds(1, edit.vListEntries);
40+
vListDesc.GetDimension(0).SetByteStride(
41+
static_cast<SubscriptValue>(sizeof(int)));
42+
ExternalFileUnit *actualExternal{io.GetExternalFileUnit()};
43+
ExternalFileUnit *external{actualExternal};
44+
if (!external) {
45+
// Create a new unit to service defined I/O for an
46+
// internal I/O parent.
47+
external = &ExternalFileUnit::NewUnit(handler, true);
48+
}
49+
ChildIo &child{external->PushChildIo(io)};
50+
int unit{external->unitNumber()};
51+
int ioStat{IostatOk};
52+
char ioMsg[100];
53+
if (special.IsArgDescriptor(0)) {
54+
auto *p{special.GetProc<void (*)(const Descriptor &, int &, char *,
55+
const Descriptor &, int &, char *, std::size_t, std::size_t)>()};
56+
p(descriptor, unit, ioType, vListDesc, ioStat, ioMsg, ioTypeLen,
57+
sizeof ioMsg);
58+
} else {
59+
auto *p{special.GetProc<void (*)(const void *, int &, char *,
60+
const Descriptor &, int &, char *, std::size_t, std::size_t)>()};
61+
p(descriptor.raw().base_addr, unit, ioType, vListDesc, ioStat, ioMsg,
62+
ioTypeLen, sizeof ioMsg);
63+
}
64+
handler.Forward(ioStat, ioMsg, sizeof ioMsg);
65+
external->PopChildIo(child);
66+
if (!actualExternal) {
67+
// Close unit created for internal I/O above.
68+
auto *closing{external->LookUpForClose(external->unitNumber())};
69+
RUNTIME_CHECK(handler, external == closing);
70+
external->DestroyClosed();
71+
}
72+
return handler.GetIoStat() == IostatOk;
73+
} else {
74+
// There's a user-defined I/O subroutine, but there's a FORMAT present and
75+
// it does not have a DT data edit descriptor, so apply default formatting
76+
// to the components of the derived type as usual.
77+
return std::nullopt;
78+
}
79+
}
80+
81+
// User-defined derived type unformatted I/O
82+
bool DefinedUnformattedIo(IoStatementState &io, const Descriptor &descriptor,
83+
const typeInfo::SpecialBinding &special) {
84+
// Unformatted I/O must have an external unit (or child thereof).
85+
IoErrorHandler &handler{io.GetIoErrorHandler()};
86+
ExternalFileUnit *external{io.GetExternalFileUnit()};
87+
RUNTIME_CHECK(handler, external != nullptr);
88+
ChildIo &child{external->PushChildIo(io)};
89+
int unit{external->unitNumber()};
90+
int ioStat{IostatOk};
91+
char ioMsg[100];
92+
if (special.IsArgDescriptor(0)) {
93+
auto *p{special.GetProc<void (*)(
94+
const Descriptor &, int &, int &, char *, std::size_t)>()};
95+
p(descriptor, unit, ioStat, ioMsg, sizeof ioMsg);
96+
} else {
97+
auto *p{special.GetProc<void (*)(
98+
const void *, int &, int &, char *, std::size_t)>()};
99+
p(descriptor.raw().base_addr, unit, ioStat, ioMsg, sizeof ioMsg);
100+
}
101+
handler.Forward(ioStat, ioMsg, sizeof ioMsg);
102+
external->PopChildIo(child);
103+
return handler.GetIoStat() == IostatOk;
104+
}
105+
106+
} // namespace Fortran::runtime::io::descr

0 commit comments

Comments
 (0)