[flang][runtime] Better (but still synchronous) support for asynchronous I/O
authorPeter Klausler <pklausler@nvidia.com>
Mon, 6 Jun 2022 18:44:19 +0000 (11:44 -0700)
committerPeter Klausler <pklausler@nvidia.com>
Mon, 13 Jun 2022 17:43:14 +0000 (10:43 -0700)
Track pending "asynchronous" I/O operation IDs so that WAIT statements can
report errors about bad ID numbers.

Lowering will need to extended to call GetAsynchronousId() for a READ or
WRITE statement with ID=n.

Differential Revision: https://reviews.llvm.org/D127421

flang/include/flang/Runtime/io-api.h
flang/include/flang/Runtime/iostat.h
flang/runtime/io-api.cpp
flang/runtime/io-stmt.cpp
flang/runtime/io-stmt.h
flang/runtime/iostat.cpp
flang/runtime/unit.cpp
flang/runtime/unit.h

index 66dd1a7..27cffda 100644 (file)
@@ -144,9 +144,11 @@ Cookie IONAME(BeginUnformattedInput)(ExternalUnit = DefaultUnit,
     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 @@ Cookie IONAME(BeginInquireIoLength)(
 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 @@ std::size_t IONAME(GetIoLength)(Cookie);
 // 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.
index 9453c12..cfde044 100644 (file)
@@ -77,6 +77,8 @@ enum Iostat {
   IostatRealInputOverflow,
   IostatOpenAlreadyConnected,
   IostatCannotReposition,
+  IostatBadWaitId,
+  IostatTooManyAsyncOps,
 };
 
 const char *IostatErrorString(int);
index 99ff2f0..4373dcd 100644 (file)
@@ -337,18 +337,30 @@ Cookie IONAME(BeginOpenNewUnit)( // OPEN(NEWUNIT=j)
       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<NoopStatementState>{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<ExternalMiscIoStatementState>(
+          *unit, ExternalMiscIoStatementState::Wait, sourceFile, sourceLine);
+    } else {
+      return &unit->BeginIoStatement<ErroneousIoStatementState>(
+          IostatBadWaitId, unit, sourceFile, sourceLine);
+    }
+  } else {
+    auto &io{
+        New<NoopStatementState>{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 @@ bool IONAME(SetAsynchronous)(
           "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<ExternalIoStatementBase>()}) {
+    if (isYes) {
+      if (ext->unit().mayAsynchronous()) {
+        ext->SetAsynchronous();
+      } else {
+        handler.SignalError(IostatBadAsynchronous);
+      }
     }
   } else {
     handler.Crash("SetAsynchronous() called when not in an OPEN or external "
index 9845676..a73012d 100644 (file)
@@ -206,6 +206,10 @@ int ExternalIoStatementBase::EndIoStatement() {
   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 @@ void ExternalMiscIoStatementState::CompleteOperation() {
   case Rewind:
     ext.Rewind(*this);
     break;
+  case Wait:
+    break; // handled in io-api.cpp BeginWait
   }
   return IoStatementBase::CompleteOperation();
 }
index 5a5bef5..5b5b60e 100644 (file)
@@ -407,11 +407,14 @@ public:
   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 <Direction DIR>
@@ -698,7 +701,7 @@ private:
 
 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} {}
index 0e6c162..3008c6e 100644 (file)
@@ -87,7 +87,7 @@ const char *IostatErrorString(int iostat) {
     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 @@ const char *IostatErrorString(int iostat) {
            "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;
   }
index 1a50b79..c7df78a 100644 (file)
@@ -911,6 +911,32 @@ void ExternalFileUnit::PopChildIo(ChildIo &child) {
   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<int>(*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<std::monostate>();
index e47fd5d..3d024ba 100644 (file)
@@ -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 <cstdlib>
 #include <cstring>
@@ -37,6 +38,7 @@ class ExternalFileUnit : public ConnectionState,
 public:
   explicit ExternalFileUnit(int unitNumber) : unitNumber_{unitNumber} {
     isUTF8 = executionEnvironment.defaultUTF8;
+    asyncIdAvailable_.set();
   }
   ~ExternalFileUnit() {}
 
@@ -102,6 +104,9 @@ public:
   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 @@ private:
   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 @@ private:
   // Points to the active alternative (if any) in u_ for use as a Cookie
   std::optional<IoStatementState> 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<ChildIo> child_;