diff --git a/flang/include/flang/Runtime/io-api.h b/flang/include/flang/Runtime/io-api.h --- a/flang/include/flang/Runtime/io-api.h +++ b/flang/include/flang/Runtime/io-api.h @@ -144,9 +144,11 @@ const char *sourceFile = nullptr, int sourceLine = 0); // WAIT(ID=) -Cookie IONAME(BeginWait)(ExternalUnit, AsynchronousId); +Cookie IONAME(BeginWait)(ExternalUnit, AsynchronousId, + const char *sourceFile = nullptr, int sourceLine = 0); // WAIT(no ID=) -Cookie IONAME(BeginWaitAll)(ExternalUnit); +Cookie IONAME(BeginWaitAll)( + ExternalUnit, const char *sourceFile = nullptr, int sourceLine = 0); // Other I/O statements Cookie IONAME(BeginClose)( @@ -194,7 +196,8 @@ void IONAME(EnableHandlers)(Cookie, bool hasIoStat = false, bool hasErr = false, bool hasEnd = false, bool hasEor = false, bool hasIoMsg = false); -// ASYNCHRONOUS='YES' or 'NO' with no ID= on READ/WRITE/OPEN +// ASYNCHRONOUS='YES' or 'NO' on READ/WRITE/OPEN +// Use GetAsynchronousId() to handle ID=. bool IONAME(SetAsynchronous)(Cookie, const char *, std::size_t); // Control list options. These return false on a error that the @@ -306,8 +309,8 @@ // end-of-record/file condition is present. void IONAME(GetIoMsg)(Cookie, char *, std::size_t); // IOMSG= -// TODO: for ID= on READ/WRITE(ASYNCHRONOUS='YES') -// int IONAME(GetAsynchronousId)(Cookie); +// Defines ID= on READ/WRITE(ASYNCHRONOUS='YES') +int IONAME(GetAsynchronousId)(Cookie); // INQUIRE() specifiers are mostly identified by their NUL-terminated // case-insensitive names. diff --git a/flang/include/flang/Runtime/iostat.h b/flang/include/flang/Runtime/iostat.h --- a/flang/include/flang/Runtime/iostat.h +++ b/flang/include/flang/Runtime/iostat.h @@ -77,6 +77,8 @@ IostatRealInputOverflow, IostatOpenAlreadyConnected, IostatCannotReposition, + IostatBadWaitId, + IostatTooManyAsyncOps, }; const char *IostatErrorString(int); diff --git a/flang/runtime/io-api.cpp b/flang/runtime/io-api.cpp --- a/flang/runtime/io-api.cpp +++ b/flang/runtime/io-api.cpp @@ -337,18 +337,30 @@ unit, false /*was an existing file*/, sourceFile, sourceLine); } -Cookie IONAME(BeginWait)(ExternalUnit unitNumber, AsynchronousId id) { - // TODO: add and use sourceFile & sourceLine here - Terminator oom; - // TODO: add and use sourceFile & sourceLine here - auto &io{ - New{oom}(nullptr, 0).release()->ioStatementState()}; - if (id != 0 && !ExternalFileUnit::LookUp(unitNumber)) { - io.GetIoErrorHandler().SetPendingError(IostatBadWaitUnit); - } - return &io; -} -Cookie IONAME(BeginWaitAll)(ExternalUnit unitNumber) { +Cookie IONAME(BeginWait)(ExternalUnit unitNumber, AsynchronousId id, + const char *sourceFile, int sourceLine) { + Terminator terminator{sourceFile, sourceLine}; + if (ExternalFileUnit * unit{ExternalFileUnit::LookUp(unitNumber)}) { + if (unit->Wait(id)) { + return &unit->BeginIoStatement( + *unit, ExternalMiscIoStatementState::Wait, sourceFile, sourceLine); + } else { + return &unit->BeginIoStatement( + IostatBadWaitId, unit, sourceFile, sourceLine); + } + } else { + auto &io{ + New{terminator}(sourceFile, sourceLine, unitNumber) + .release() + ->ioStatementState()}; + if (id != 0) { + io.GetIoErrorHandler().SetPendingError(IostatBadWaitUnit); + } + return &io; + } +} +Cookie IONAME(BeginWaitAll)( + ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { return IONAME(BeginWait)(unitNumber, 0 /*no ID=*/); } @@ -737,9 +749,13 @@ "SetAsynchronous() called after GetNewUnit() for an OPEN statement"); } open->unit().set_mayAsynchronous(isYes); - } else if (ExternalFileUnit * unit{io.GetExternalFileUnit()}) { - if (isYes && !unit->mayAsynchronous()) { - handler.SignalError(IostatBadAsynchronous); + } else if (auto *ext{io.get_if()}) { + if (isYes) { + if (ext->unit().mayAsynchronous()) { + ext->SetAsynchronous(); + } else { + handler.SignalError(IostatBadAsynchronous); + } } } else { handler.Crash("SetAsynchronous() called when not in an OPEN or external " diff --git a/flang/runtime/io-stmt.h b/flang/runtime/io-stmt.h --- a/flang/runtime/io-stmt.h +++ b/flang/runtime/io-stmt.h @@ -407,11 +407,14 @@ ExternalFileUnit &unit() { return unit_; } MutableModes &mutableModes(); ConnectionState &GetConnectionState(); + int asynchronousID() const { return asynchronousID_; } int EndIoStatement(); ExternalFileUnit *GetExternalFileUnit() const { return &unit_; } + void SetAsynchronous(); private: ExternalFileUnit &unit_; + int asynchronousID_{-1}; }; template @@ -698,7 +701,7 @@ class ExternalMiscIoStatementState : public ExternalIoStatementBase { public: - enum Which { Flush, Backspace, Endfile, Rewind }; + enum Which { Flush, Backspace, Endfile, Rewind, Wait }; ExternalMiscIoStatementState(ExternalFileUnit &unit, Which which, const char *sourceFile = nullptr, int sourceLine = 0) : ExternalIoStatementBase{unit, sourceFile, sourceLine}, which_{which} {} diff --git a/flang/runtime/io-stmt.cpp b/flang/runtime/io-stmt.cpp --- a/flang/runtime/io-stmt.cpp +++ b/flang/runtime/io-stmt.cpp @@ -206,6 +206,10 @@ return result; } +void ExternalIoStatementBase::SetAsynchronous() { + asynchronousID_ = unit().GetAsynchronousId(*this); +} + void OpenStatementState::set_path(const char *path, std::size_t length) { pathLength_ = TrimTrailingSpaces(path, length); path_ = SaveDefaultCharacter(path, pathLength_, *this); @@ -1023,6 +1027,8 @@ case Rewind: ext.Rewind(*this); break; + case Wait: + break; // handled in io-api.cpp BeginWait } return IoStatementBase::CompleteOperation(); } diff --git a/flang/runtime/iostat.cpp b/flang/runtime/iostat.cpp --- a/flang/runtime/iostat.cpp +++ b/flang/runtime/iostat.cpp @@ -87,7 +87,7 @@ return "READ/WRITE(ASYNCHRONOUS='YES') on unit without " "OPEN(ASYNCHRONOUS='YES')"; case IostatBadWaitUnit: - return "WAIT(ID=nonzero) for a bad unit number"; + return "WAIT(UNIT=) for a bad unit number"; case IostatBOZInputOverflow: return "B/O/Z input value overflows variable"; case IostatIntegerInputOverflow: @@ -99,6 +99,10 @@ "only be processed sequentially"; case IostatOpenAlreadyConnected: return "OPEN of file already connected to another unit"; + case IostatBadWaitId: + return "WAIT(ID=nonzero) for an ID value that is not a pending operation"; + case IostatTooManyAsyncOps: + return "Too many asynchronous operations pending on unit"; default: return nullptr; } diff --git a/flang/runtime/unit.h b/flang/runtime/unit.h --- a/flang/runtime/unit.h +++ b/flang/runtime/unit.h @@ -20,6 +20,7 @@ #include "io-stmt.h" #include "lock.h" #include "terminator.h" +#include "flang/Common/constexpr-bitset.h" #include "flang/Runtime/memory.h" #include #include @@ -37,6 +38,7 @@ public: explicit ExternalFileUnit(int unitNumber) : unitNumber_{unitNumber} { isUTF8 = executionEnvironment.defaultUTF8; + asyncIdAvailable_.set(); } ~ExternalFileUnit() {} @@ -102,6 +104,9 @@ ChildIo &PushChildIo(IoStatementState &); void PopChildIo(ChildIo &); + int GetAsynchronousId(IoErrorHandler &); + bool Wait(int); + private: static UnitMap &GetUnitMap(); const char *FrameNextInput(IoErrorHandler &, std::size_t); @@ -117,13 +122,22 @@ bool CheckDirectAccess(IoErrorHandler &); void HitEndOnRead(IoErrorHandler &); + Lock lock_; + int unitNumber_{-1}; Direction direction_{Direction::Output}; bool impliedEndfile_{false}; // sequential/stream output has taken place bool beganReadingRecord_{false}; bool directAccessRecWasSet_{false}; // REC= appeared - - Lock lock_; + // Subtle: The beginning of the frame can't be allowed to advance + // during a single list-directed READ due to the possibility of a + // multi-record CHARACTER value with a "r*" repeat count. So we + // manage the frame and the current record therein separately. + std::int64_t frameOffsetInFile_{0}; + std::size_t recordOffsetInFrame_{0}; // of currentRecordNumber + bool swapEndianness_{false}; + bool createdForInternalChildIo_{false}; + common::BitSet<64> asyncIdAvailable_; // When a synchronous I/O statement is in progress on this unit, holds its // state. @@ -140,17 +154,6 @@ // Points to the active alternative (if any) in u_ for use as a Cookie std::optional io_; - // Subtle: The beginning of the frame can't be allowed to advance - // during a single list-directed READ due to the possibility of a - // multi-record CHARACTER value with a "r*" repeat count. So we - // manage the frame and the current record therein separately. - std::int64_t frameOffsetInFile_{0}; - std::size_t recordOffsetInFrame_{0}; // of currentRecordNumber - - bool swapEndianness_{false}; - - bool createdForInternalChildIo_{false}; - // A stack of child I/O pseudo-units for user-defined derived type // I/O that have this unit number. OwningPtr child_; diff --git a/flang/runtime/unit.cpp b/flang/runtime/unit.cpp --- a/flang/runtime/unit.cpp +++ b/flang/runtime/unit.cpp @@ -911,6 +911,32 @@ child_.reset(child.AcquirePrevious().release()); // deletes top child } +int ExternalFileUnit::GetAsynchronousId(IoErrorHandler &handler) { + if (!mayAsynchronous()) { + handler.SignalError(IostatBadAsynchronous); + return -1; + } else if (auto least{asyncIdAvailable_.LeastElement()}) { + asyncIdAvailable_.reset(*least); + return static_cast(*least); + } else { + handler.SignalError(IostatTooManyAsyncOps); + return -1; + } +} + +bool ExternalFileUnit::Wait(int id) { + if (id < 0 || asyncIdAvailable_.test(id)) { + return false; + } else { + if (id == 0) { + asyncIdAvailable_.set(); + } else { + asyncIdAvailable_.set(id); + } + return true; + } +} + void ChildIo::EndIoStatement() { io_.reset(); u_.emplace();