Skip to content

Commit 09b00ab

Browse files
committed
[flang] Handle dynamic and remotely scoped non-type-bound UDDTIO subroutines
The present I/O infrastructure for user-defined derived type I/O subroutines works fine for type-bound I/O generic bindings. It also works for explicit INTERFACE blocks and GENERIC statements that define UDDIO subroutines in the same scope as the definition of the derived type, so long as the specific procedures in those bindings are module procedures or external procedures. For non-type-bound UDDTIO specific procedures that are dummy procedures, thunks of inner procedures, or procedure pointers, or that are defined with interfaces or GENERIC outside the scope of the definition of the derived type, a new runtime I/O API is needed so that lowering can generate a call that supplies the appropriate procedure as well as the defined type instance. This patch specifies and implements this new runtime API and provides utility routines for lowering to use to determine whether it should be called for any particular OutputItem or InputItem in the parse tree. Differential Revision: https://reviews.llvm.org/D146571
1 parent 9972c9a commit 09b00ab

File tree

8 files changed

+231
-39
lines changed

8 files changed

+231
-39
lines changed

flang/include/flang/Runtime/io-api.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,22 @@ bool IONAME(InputLogical)(Cookie, bool &);
275275
bool IONAME(OutputNamelist)(Cookie, const NamelistGroup &);
276276
bool IONAME(InputNamelist)(Cookie, const NamelistGroup &);
277277

278+
// When an I/O list item has a derived type with a specific user-defined
279+
// I/O subroutine of the appropriate generic kind for the active
280+
// I/O data transfer statement (read/write, formatted/unformatted)
281+
// and that I/O subroutine is a specific procedure for an explicit
282+
// generic INTERFACE or GENERIC statement that is *not* type-bound,
283+
// this data item transfer API enables the use of that procedure
284+
// for the item. Pass 'true' for 'isPolymorphic' when the first ("dtv")
285+
// dummy argument of the specific procedure is CLASS(t), not TYPE(t).
286+
// If the procedure pointer is null, or when the next edit descriptor for
287+
// formatted I/O is not DT, the procedure will not be called and the
288+
// behavior will be as if (Output/Input)Descriptor had been called.
289+
bool IONAME(OutputDerivedType)(
290+
Cookie, const Descriptor &, void (*)(), bool isPolymorphic);
291+
bool IONAME(InputDerivedType)(
292+
Cookie, const Descriptor &, void (*)(), bool isPolymorphic);
293+
278294
// Additional specifier interfaces for the connection-list of
279295
// on OPEN statement (only). SetBlank(), SetDecimal(),
280296
// SetDelim(), GetIoMsg(), SetPad(), SetRound(), SetSign(),

flang/include/flang/Semantics/tools.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,5 +631,20 @@ bool HasDefinedIo(
631631
std::forward_list<std::string> GetAllNames(
632632
const SemanticsContext &, const SourceName &);
633633

634+
// Determines the derived type of a procedure's initial "dtv" dummy argument,
635+
// assuming that the procedure is a specific procedure of a user-defined
636+
// derived type I/O generic interface,
637+
const DerivedTypeSpec *GetDtvArgDerivedType(const Symbol &);
638+
639+
// Locates a non-type-bound generic interface in the enclosing scopes for a
640+
// given user-defined derived type I/O operation, given a specific derived type
641+
// spec. Intended for use when lowering I/O data list items to identify a remote
642+
// or dynamic non-type-bound UDDTIO subroutine so that it can be passed to the
643+
// I/O runtime's NonTypeBoundDefinedIo() API.
644+
std::pair<const Symbol *, bool /*isPolymorphic*/> FindNonTypeBoundDefinedIo(
645+
const SemanticsContext, const parser::OutputItem &, bool isFormatted);
646+
std::pair<const Symbol *, bool /*isPolymorphic*/> FindNonTypeBoundDefinedIo(
647+
const SemanticsContext, const parser::InputItem &, bool isFormatted);
648+
634649
} // namespace Fortran::semantics
635650
#endif // FORTRAN_SEMANTICS_TOOLS_H_

flang/lib/Semantics/check-declarations.cpp

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,6 @@ class CheckHelper {
161161
std::map<std::pair<SourceName, const Symbol *>, SymbolRef> moduleProcs_;
162162
// Collection of symbols with global names, BIND(C) or otherwise
163163
std::map<std::string, SymbolRef> globalNames_;
164-
// Derived types that have defined input/output procedures
165-
std::vector<TypeWithDefinedIo> seenDefinedIoTypes_;
166164
};
167165

168166
class DistinguishabilityHelper {
@@ -2428,24 +2426,32 @@ bool CheckHelper::CheckDioDummyIsData(
24282426

24292427
void CheckHelper::CheckAlreadySeenDefinedIo(const DerivedTypeSpec &derivedType,
24302428
GenericKind::DefinedIo ioKind, const Symbol &proc, const Symbol &generic) {
2431-
for (TypeWithDefinedIo definedIoType : seenDefinedIoTypes_) {
2432-
// It's okay to have two or more distinct derived type I/O procedures
2433-
// for the same type if they're coming from distinct non-type-bound
2434-
// interfaces. (The non-type-bound interfaces would have been merged into
2435-
// a single generic if both were visible in the same scope.)
2436-
if (derivedType == definedIoType.type && ioKind == definedIoType.ioKind &&
2437-
proc != definedIoType.proc &&
2438-
(generic.owner().IsDerivedType() ||
2439-
definedIoType.generic.owner().IsDerivedType())) {
2440-
SayWithDeclaration(proc, definedIoType.proc.name(),
2441-
"Derived type '%s' already has defined input/output procedure"
2442-
" '%s'"_err_en_US,
2443-
derivedType.name(), GenericKind::AsFortran(ioKind));
2444-
return;
2429+
// Check for conflict between non-type-bound UDDTIO and type-bound generics.
2430+
// It's okay to have two or more distinct derived type I/O procedures
2431+
// for the same type if they're coming from distinct non-type-bound
2432+
// interfaces. (The non-type-bound interfaces would have been merged into
2433+
// a single generic -- with errors where indistinguishable -- if both were
2434+
// visible in the same scope.)
2435+
if (generic.owner().IsDerivedType()) {
2436+
return;
2437+
}
2438+
if (const Scope * dtScope{derivedType.scope()}) {
2439+
if (auto iter{dtScope->find(generic.name())}; iter != dtScope->end()) {
2440+
for (auto specRef : iter->second->get<GenericDetails>().specificProcs()) {
2441+
const Symbol &specific{specRef->get<ProcBindingDetails>().symbol()};
2442+
if (specific == proc) { // unambiguous, accept
2443+
continue;
2444+
}
2445+
if (const auto *specDT{GetDtvArgDerivedType(specific)};
2446+
specDT && evaluate::AreSameDerivedType(derivedType, *specDT)) {
2447+
SayWithDeclaration(*specRef, proc.name(),
2448+
"Derived type '%s' has conflicting type-bound input/output procedure '%s'"_err_en_US,
2449+
derivedType.name(), GenericKind::AsFortran(ioKind));
2450+
return;
2451+
}
2452+
}
24452453
}
24462454
}
2447-
seenDefinedIoTypes_.emplace_back(
2448-
TypeWithDefinedIo{derivedType, ioKind, proc, generic});
24492455
}
24502456

24512457
void CheckHelper::CheckDioDummyIsDerived(const Symbol &subp, const Symbol &arg,

flang/lib/Semantics/tools.cpp

Lines changed: 73 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1475,6 +1475,24 @@ std::optional<ArraySpec> ToArraySpec(evaluate::FoldingContext &context,
14751475
return shape ? ToArraySpec(context, *shape) : std::nullopt;
14761476
}
14771477

1478+
static const DeclTypeSpec *GetDtvArgTypeSpec(const Symbol &proc) {
1479+
if (const auto *subp{proc.detailsIf<SubprogramDetails>()};
1480+
subp && !subp->dummyArgs().empty()) {
1481+
if (const auto *arg{subp->dummyArgs()[0]}) {
1482+
return arg->GetType();
1483+
}
1484+
}
1485+
return nullptr;
1486+
}
1487+
1488+
const DerivedTypeSpec *GetDtvArgDerivedType(const Symbol &proc) {
1489+
if (const auto *type{GetDtvArgTypeSpec(proc)}) {
1490+
return type->AsDerived();
1491+
} else {
1492+
return nullptr;
1493+
}
1494+
}
1495+
14781496
bool HasDefinedIo(GenericKind::DefinedIo which, const DerivedTypeSpec &derived,
14791497
const Scope *scope) {
14801498
if (const Scope * dtScope{derived.scope()}) {
@@ -1499,16 +1517,10 @@ bool HasDefinedIo(GenericKind::DefinedIo which, const DerivedTypeSpec &derived,
14991517
const auto &generic{iter->second->GetUltimate().get<GenericDetails>()};
15001518
for (auto ref : generic.specificProcs()) {
15011519
const Symbol &procSym{ref->GetUltimate()};
1502-
if (const auto *subp{procSym.detailsIf<SubprogramDetails>()}) {
1503-
if (!subp->dummyArgs().empty()) {
1504-
if (const Symbol * first{subp->dummyArgs().at(0)}) {
1505-
if (const DeclTypeSpec * dtSpec{first->GetType()}) {
1506-
if (auto dyDummy{evaluate::DynamicType::From(*dtSpec)}) {
1507-
if (dyDummy->IsTkCompatibleWith(dyDerived)) {
1508-
return true; // GENERIC or INTERFACE not in type
1509-
}
1510-
}
1511-
}
1520+
if (const DeclTypeSpec * dtSpec{GetDtvArgTypeSpec(procSym)}) {
1521+
if (auto dyDummy{evaluate::DynamicType::From(*dtSpec)}) {
1522+
if (dyDummy->IsTkCompatibleWith(dyDerived)) {
1523+
return true; // GENERIC or INTERFACE not in type
15121524
}
15131525
}
15141526
}
@@ -1519,4 +1531,55 @@ bool HasDefinedIo(GenericKind::DefinedIo which, const DerivedTypeSpec &derived,
15191531
return false;
15201532
}
15211533

1534+
static std::pair<const Symbol *, bool /*isPolymorphic*/>
1535+
FindNonTypeBoundDefinedIo(const Scope &scope, const evaluate::DynamicType &type,
1536+
GenericKind::DefinedIo io) {
1537+
if (const DerivedTypeSpec * derived{evaluate::GetDerivedTypeSpec(type)}) {
1538+
if (const Symbol * symbol{scope.FindSymbol(GenericKind::AsFortran(io))}) {
1539+
if (const auto *generic{symbol->detailsIf<GenericDetails>()}) {
1540+
for (const auto &ref : generic->specificProcs()) {
1541+
const Symbol &specific{ref->GetUltimate()};
1542+
if (const DeclTypeSpec * dtvTypeSpec{GetDtvArgTypeSpec(specific)}) {
1543+
if (const DerivedTypeSpec * dtvDerived{dtvTypeSpec->AsDerived()}) {
1544+
if (evaluate::AreSameDerivedType(*derived, *dtvDerived)) {
1545+
return {&specific, dtvTypeSpec->IsPolymorphic()};
1546+
}
1547+
}
1548+
}
1549+
}
1550+
}
1551+
}
1552+
}
1553+
return {nullptr, false};
1554+
}
1555+
1556+
std::pair<const Symbol *, bool /*isPolymorphic*/> FindNonTypeBoundDefinedIo(
1557+
const SemanticsContext &context, const parser::OutputItem &item,
1558+
bool isFormatted) {
1559+
if (const auto *expr{std::get_if<parser::Expr>(&item.u)};
1560+
expr && expr->typedExpr && expr->typedExpr->v) {
1561+
if (auto type{expr->typedExpr->v->GetType()}) {
1562+
return FindNonTypeBoundDefinedIo(context.FindScope(expr->source), *type,
1563+
isFormatted ? GenericKind::DefinedIo::WriteFormatted
1564+
: GenericKind::DefinedIo::WriteUnformatted);
1565+
}
1566+
}
1567+
return {nullptr, false};
1568+
}
1569+
1570+
std::pair<const Symbol *, bool /*isPolymorphic*/> FindNonTypeBoundDefinedIo(
1571+
const SemanticsContext &context, const parser::InputItem &item,
1572+
bool isFormatted) {
1573+
if (const auto *var{std::get_if<parser::Variable>(&item.u)};
1574+
var && var->typedExpr && var->typedExpr->v) {
1575+
if (auto type{var->typedExpr->v->GetType()}) {
1576+
return FindNonTypeBoundDefinedIo(context.FindScope(var->GetSource()),
1577+
*type,
1578+
isFormatted ? GenericKind::DefinedIo::ReadFormatted
1579+
: GenericKind::DefinedIo::ReadUnformatted);
1580+
}
1581+
}
1582+
return {nullptr, false};
1583+
}
1584+
15221585
} // namespace Fortran::semantics

flang/runtime/io-api.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1379,6 +1379,61 @@ bool IONAME(InputLogical)(Cookie cookie, bool &truth) {
13791379
return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
13801380
}
13811381

1382+
template <Direction DIR>
1383+
static bool DoDerivedTypeIo(Cookie cookie, const Descriptor &descriptor,
1384+
void (*procedure)(), bool isPolymorphic, const char *which) {
1385+
IoStatementState &io{*cookie};
1386+
IoErrorHandler &handler{io.GetIoErrorHandler()};
1387+
if (handler.InError()) {
1388+
return false;
1389+
}
1390+
const DescriptorAddendum *addendum{descriptor.Addendum()};
1391+
const typeInfo::DerivedType *type{
1392+
addendum ? addendum->derivedType() : nullptr};
1393+
RUNTIME_CHECK(handler, type != nullptr);
1394+
if (!procedure) {
1395+
if constexpr (DIR == Direction::Output) {
1396+
return IONAME(OutputDescriptor)(cookie, descriptor);
1397+
} else {
1398+
return IONAME(InputDescriptor)(cookie, descriptor);
1399+
}
1400+
}
1401+
if (!io.get_if<IoDirectionState<DIR>>()) {
1402+
handler.Crash("%s called for I/O statement that is not %s", which,
1403+
DIR == Direction::Output ? "output" : "input");
1404+
}
1405+
std::uint8_t isArgDesc{isPolymorphic};
1406+
if (io.get_if<FormattedIoStatementState<DIR>>()) {
1407+
if (std::optional<bool> wasDefined{
1408+
descr::DefinedFormattedIo(io, descriptor, *type,
1409+
typeInfo::SpecialBinding{DIR == Direction::Output
1410+
? typeInfo::SpecialBinding::Which::WriteFormatted
1411+
: typeInfo::SpecialBinding::Which::ReadFormatted,
1412+
procedure, isArgDesc})}) {
1413+
return *wasDefined;
1414+
}
1415+
return descr::DefaultComponentwiseIO<DIR>(io, descriptor, *type);
1416+
} else { // unformatted
1417+
return descr::DefinedUnformattedIo(io, descriptor, *type,
1418+
typeInfo::SpecialBinding{DIR == Direction::Output
1419+
? typeInfo::SpecialBinding::Which::WriteUnformatted
1420+
: typeInfo::SpecialBinding::Which::ReadUnformatted,
1421+
procedure, isArgDesc});
1422+
}
1423+
}
1424+
1425+
bool IONAME(OutputDerivedType)(Cookie cookie, const Descriptor &descriptor,
1426+
void (*procedure)(), bool isPolymorphic) {
1427+
return DoDerivedTypeIo<Direction::Output>(
1428+
cookie, descriptor, procedure, isPolymorphic, "OutputDerivedType");
1429+
}
1430+
1431+
bool IONAME(InputDerivedType)(Cookie cookie, const Descriptor &descriptor,
1432+
void (*procedure)(), bool isPolymorphic) {
1433+
return DoDerivedTypeIo<Direction::Output>(
1434+
cookie, descriptor, procedure, isPolymorphic, "InputDerivedType");
1435+
}
1436+
13821437
std::size_t IONAME(GetSize)(Cookie cookie) {
13831438
IoStatementState &io{*cookie};
13841439
IoErrorHandler &handler{io.GetIoErrorHandler()};

flang/runtime/type-info.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,11 @@ class SpecialBinding {
133133
// higher-ranked final procedures follow
134134
};
135135

136+
// Special bindings can be created during execution to handle user-defined
137+
// derived type I/O procedures that are not type-bound.
138+
SpecialBinding(Which which, ProcedurePointer proc, std::uint8_t isArgDescSet)
139+
: which_{which}, isArgDescriptorSet_{isArgDescSet}, proc_{proc} {}
140+
136141
static constexpr Which RankFinal(int rank) {
137142
return static_cast<Which>(static_cast<int>(Which::ScalarFinal) + rank);
138143
}

flang/test/Semantics/generic05.F90

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ subroutine name(dtv, unit, iostat, iomsg); \
2828
character(*), intent(in out) :: iomsg; \
2929
read(unit, iostat=iostat, iomsg=iomsg) dtv%n; \
3030
end subroutine name
31-
!ERROR: Derived type 't1' already has defined input/output procedure 'read(unformatted)'
31+
!ERROR: Derived type 't1' has conflicting type-bound input/output procedure 'read(unformatted)'
3232
DEFINE_READU(readt1a, t1)
3333
DEFINE_READU(readt1b, t1)
3434
DEFINE_READU(readt2a, t2)

flang/test/Semantics/io11.f90

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ subroutine formattedReadProc(dtv,unit,iotype,v_list,iostat,iomsg)
391391
end module
392392

393393
module m18
394-
! Test the same defined input/output procedure specified as a type-bound
394+
! Test the same defined input/output procedure specified as a type-bound
395395
! procedure and as a generic
396396
type t
397397
integer c
@@ -435,7 +435,7 @@ subroutine unformattedReadProc1(dtv,unit,iostat,iomsg)
435435
character(*),intent(inout) :: iomsg
436436
read(unit,iotype,iostat=iostat,iomsg=iomsg) dtv%c
437437
end subroutine
438-
!ERROR: Derived type 't' already has defined input/output procedure 'read(unformatted)'
438+
!ERROR: Derived type 't' has conflicting type-bound input/output procedure 'read(unformatted)'
439439
subroutine unformattedReadProc(dtv,unit,iostat,iomsg)
440440
class(t),intent(inout) :: dtv
441441
integer,intent(in) :: unit
@@ -499,7 +499,7 @@ subroutine unformattedReadProc(dtv,unit,iostat,iomsg)
499499
character(*),intent(inout) :: iomsg
500500
read(unit,iotype,iostat=iostat,iomsg=iomsg) dtv%c
501501
end subroutine
502-
!ERROR: Derived type 't' already has defined input/output procedure 'read(unformatted)'
502+
!ERROR: Derived type 't' has conflicting type-bound input/output procedure 'read(unformatted)'
503503
subroutine unformattedReadProc1(dtv,unit,iostat,iomsg)
504504
class(t(4)),intent(inout) :: dtv
505505
integer,intent(in) :: unit
@@ -510,7 +510,7 @@ subroutine unformattedReadProc1(dtv,unit,iostat,iomsg)
510510
end module
511511

512512
module m22
513-
! Test read and write defined input/output procedures specified as a
513+
! Test read and write defined input/output procedures specified as a
514514
! type-bound procedure and as a generic for the same derived type with a
515515
! KIND type parameter where they have different values
516516
type t(typeParam)
@@ -542,10 +542,10 @@ subroutine unformattedReadProc1(dtv,unit,iostat,iomsg)
542542

543543
module m23
544544
type t(typeParam)
545-
! Test read and write defined input/output procedures specified as a
545+
! Test read and write defined input/output procedures specified as a
546546
! type-bound procedure and as a generic for the same derived type with a
547-
! LEN type parameter where they have different values
548-
integer, len :: typeParam = 4
547+
! KIND type parameter where they have different values
548+
integer, kind :: typeParam = 4
549549
integer c
550550
contains
551551
procedure :: unformattedReadProc
@@ -556,7 +556,7 @@ module m23
556556
end interface
557557
contains
558558
subroutine unformattedReadProc(dtv,unit,iostat,iomsg)
559-
class(t(*)),intent(inout) :: dtv
559+
class(t(2)),intent(inout) :: dtv
560560
integer,intent(in) :: unit
561561
integer,intent(out) :: iostat
562562
character(*),intent(inout) :: iomsg
@@ -571,10 +571,42 @@ subroutine unformattedReadProc1(dtv,unit,iostat,iomsg)
571571
end subroutine
572572
end module
573573

574+
module m23a
575+
type t(typeParam)
576+
! Test read and write defined input/output procedures specified as a
577+
! type-bound procedure and as a generic for the same derived type with a
578+
! KIND type parameter where they have the same value
579+
integer, kind :: typeParam = 4
580+
integer c
581+
contains
582+
procedure :: unformattedReadProc
583+
generic :: read(unformatted) => unformattedReadProc
584+
end type
585+
interface read(unformatted)
586+
module procedure unformattedReadProc1
587+
end interface
588+
contains
589+
subroutine unformattedReadProc(dtv,unit,iostat,iomsg)
590+
class(t),intent(inout) :: dtv
591+
integer,intent(in) :: unit
592+
integer,intent(out) :: iostat
593+
character(*),intent(inout) :: iomsg
594+
read(unit,iotype,iostat=iostat,iomsg=iomsg) dtv%c
595+
end subroutine
596+
!ERROR: Derived type 't' has conflicting type-bound input/output procedure 'read(unformatted)'
597+
subroutine unformattedReadProc1(dtv,unit,iostat,iomsg)
598+
class(t(4)),intent(inout) :: dtv
599+
integer,intent(in) :: unit
600+
integer,intent(out) :: iostat
601+
character(*),intent(inout) :: iomsg
602+
read(unit,iotype,iostat=iostat,iomsg=iomsg) dtv%c
603+
end subroutine
604+
end module
605+
574606
module m24
575607
! Test read and write defined input/output procedures specified as a
576608
! type-bound procedure and as a generic for the same derived type with a
577-
! LEN type parameter where they have the same value
609+
! LEN type parameter where they are both assumed
578610
type t(typeParam)
579611
integer, len :: typeParam = 4
580612
integer c
@@ -593,7 +625,7 @@ subroutine unformattedReadProc(dtv,unit,iostat,iomsg)
593625
character(*),intent(inout) :: iomsg
594626
read(unit,iotype,iostat=iostat,iomsg=iomsg) dtv%c
595627
end subroutine
596-
!ERROR: Derived type 't' already has defined input/output procedure 'read(unformatted)'
628+
!ERROR: Derived type 't' has conflicting type-bound input/output procedure 'read(unformatted)'
597629
subroutine unformattedReadProc1(dtv,unit,iostat,iomsg)
598630
class(t(*)),intent(inout) :: dtv
599631
integer,intent(in) :: unit

0 commit comments

Comments
 (0)