[flang] Extend & fix per-I/O-statement state (ext. I/O work part 7)

The per-I/O-statement state structures need to support missing
external I/O statements, and some bugs found in testing with
formatted input and record advancement are fixed.  The effects
of these changes will not be visible until further patches to
the I/O API handlers are pushed.

Reviewed By: tskeith

Differential Revision: https://reviews.llvm.org/D83151
diff --git a/flang/runtime/io-stmt.cpp b/flang/runtime/io-stmt.cpp
index 1e2e5f5..a4d8af4 100644
--- a/flang/runtime/io-stmt.cpp
+++ b/flang/runtime/io-stmt.cpp
@@ -13,6 +13,7 @@
 #include "tools.h"
 #include "unit.h"
 #include <algorithm>
+#include <cstdio>
 #include <cstring>
 #include <limits>
 
@@ -182,10 +183,10 @@
 }
 
 template <Direction DIR> int ExternalIoStatementState<DIR>::EndIoStatement() {
+  if (!unit().nonAdvancing) {
+    unit().AdvanceRecord(*this);
+  }
   if constexpr (DIR == Direction::Output) {
-    if (!unit().nonAdvancing) {
-      unit().AdvanceRecord(*this);
-    }
     unit().FlushIfTerminal(*this);
   }
   return ExternalIoStatementBase::EndIoStatement();
@@ -291,7 +292,7 @@
 }
 
 void IoStatementState::HandleRelativePosition(std::int64_t n) {
-  return std::visit([=](auto &x) { x.get().HandleRelativePosition(n); }, u_);
+  std::visit([=](auto &x) { x.get().HandleRelativePosition(n); }, u_);
 }
 
 int IoStatementState::EndIoStatement() {
@@ -347,15 +348,22 @@
   }
 }
 
-void IoStatementState::SkipSpaces(std::optional<int> &remaining) {
-  if (!remaining || *remaining > 0) {
-    for (auto ch{GetCurrentChar()}; ch && ch == ' '; ch = GetCurrentChar()) {
-      HandleRelativePosition(1);
-      if (remaining && !--*remaining) {
-        break;
+std::optional<char32_t> IoStatementState::SkipSpaces(
+    std::optional<int> &remaining) {
+  while (!remaining || *remaining > 0) {
+    if (auto ch{GetCurrentChar()}) {
+      if (*ch != ' ') {
+        return ch;
       }
+      HandleRelativePosition(1);
+      if (remaining) {
+        --*remaining;
+      }
+    } else {
+      break;
     }
   }
+  return std::nullopt;
 }
 
 std::optional<char32_t> IoStatementState::NextInField(
@@ -372,6 +380,7 @@
       case '\'':
       case '"':
       case '*':
+      case '\n': // for stream access
         break;
       default:
         HandleRelativePosition(1);
@@ -385,7 +394,8 @@
       return next;
     }
     const ConnectionState &connection{GetConnectionState()};
-    if (!connection.IsAtEOF() && connection.recordLength &&
+    if (!connection.IsAtEOF() && connection.isFixedRecordLength &&
+        connection.recordLength &&
         connection.positionInRecord >= *connection.recordLength) {
       if (connection.modes.pad) { // PAD='YES'
         --*remaining;
@@ -554,28 +564,38 @@
 }
 
 template <Direction DIR>
+bool UnformattedIoStatementState<DIR>::Receive(char *data, std::size_t bytes) {
+  if constexpr (DIR == Direction::Output) {
+    this->Crash(
+        "UnformattedIoStatementState::Receive() called for output statement");
+  }
+  return this->unit().Receive(data, bytes, *this);
+}
+
+template <Direction DIR>
 int UnformattedIoStatementState<DIR>::EndIoStatement() {
-  auto &ext{static_cast<ExternalIoStatementState<DIR> &>(*this)};
-  ExternalFileUnit &unit{ext.unit()};
-  if (unit.access == Access::Sequential && !unit.recordLength.has_value()) {
-    // Overwrite the first four bytes of the record with its length,
-    // and also append the length.  These four bytes were skipped over
-    // in BeginUnformattedOutput().
-    // TODO: Break very large records up into subrecords with negative
-    // headers &/or footers
-    union {
-      std::uint32_t u;
-      char c[sizeof u];
-    } u;
-    u.u = unit.furthestPositionInRecord - sizeof u.c;
-    // TODO: Convert record length to little-endian on big-endian host?
-    if (!(ext.Emit(u.c, sizeof u.c) &&
-            (ext.HandleAbsolutePosition(0), ext.Emit(u.c, sizeof u.c)) &&
-            ext.AdvanceRecord())) {
-      return false;
+  ExternalFileUnit &unit{this->unit()};
+  if constexpr (DIR == Direction::Output) {
+    if (unit.access == Access::Sequential && !unit.isFixedRecordLength) {
+      // Append the length of a sequential unformatted variable-length record
+      // as its footer, then overwrite the reserved first four bytes of the
+      // record with its length as its header.  These four bytes were skipped
+      // over in BeginUnformattedOutput().
+      // TODO: Break very large records up into subrecords with negative
+      // headers &/or footers
+      union {
+        std::uint32_t u;
+        char c[sizeof u];
+      } u;
+      u.u = unit.furthestPositionInRecord - sizeof u;
+      // TODO: Convert record length to little-endian on big-endian host?
+      if (!(this->Emit(u.c, sizeof u) &&
+              (this->HandleAbsolutePosition(0), this->Emit(u.c, sizeof u)))) {
+        return false;
+      }
     }
   }
-  return ext.EndIoStatement();
+  return ExternalIoStatementState<DIR>::EndIoStatement();
 }
 
 template class InternalIoStatementState<Direction::Output>;
@@ -592,4 +612,25 @@
 template class ExternalListIoStatementState<Direction::Input>;
 template class UnformattedIoStatementState<Direction::Output>;
 template class UnformattedIoStatementState<Direction::Input>;
+
+int ExternalMiscIoStatementState::EndIoStatement() {
+  ExternalFileUnit &ext{unit()};
+  switch (which_) {
+  case Flush:
+    ext.Flush(*this);
+    std::fflush(nullptr); // flushes C stdio output streams (12.9(2))
+    break;
+  case Backspace:
+    ext.BackspaceRecord(*this);
+    break;
+  case Endfile:
+    ext.Endfile(*this);
+    break;
+  case Rewind:
+    ext.Rewind(*this);
+    break;
+  }
+  return ExternalIoStatementBase::EndIoStatement();
+}
+
 } // namespace Fortran::runtime::io
diff --git a/flang/runtime/io-stmt.h b/flang/runtime/io-stmt.h
index b8145d7..066391b 100644
--- a/flang/runtime/io-stmt.h
+++ b/flang/runtime/io-stmt.h
@@ -36,6 +36,7 @@
 class ExternalFormattedIoStatementState;
 template <Direction> class ExternalListIoStatementState;
 template <Direction> class UnformattedIoStatementState;
+class ExternalMiscIoStatementState;
 
 // The Cookie type in the I/O API is a pointer (for C) to this class.
 class IoStatementState {
@@ -73,7 +74,8 @@
 
   bool EmitRepeated(char, std::size_t);
   bool EmitField(const char *, std::size_t length, std::size_t width);
-  void SkipSpaces(std::optional<int> &remaining);
+
+  std::optional<char32_t> SkipSpaces(std::optional<int> &remaining);
   std::optional<char32_t> NextInField(std::optional<int> &remaining);
   std::optional<char32_t> GetNextNonBlank(); // can advance record
 
@@ -94,7 +96,8 @@
       std::reference_wrapper<ExternalListIoStatementState<Direction::Output>>,
       std::reference_wrapper<ExternalListIoStatementState<Direction::Input>>,
       std::reference_wrapper<UnformattedIoStatementState<Direction::Output>>,
-      std::reference_wrapper<UnformattedIoStatementState<Direction::Input>>>
+      std::reference_wrapper<UnformattedIoStatementState<Direction::Input>>,
+      std::reference_wrapper<ExternalMiscIoStatementState>>
       u_;
 };
 
@@ -235,7 +238,7 @@
 public:
   using ExternalIoStatementBase::ExternalIoStatementBase;
   int EndIoStatement();
-  bool Emit(const char *, std::size_t chars /* not bytes */);
+  bool Emit(const char *, std::size_t);
   bool Emit(const char16_t *, std::size_t chars /* not bytes */);
   bool Emit(const char32_t *, std::size_t chars /* not bytes */);
   std::optional<char32_t> GetCurrentChar();
@@ -280,6 +283,7 @@
 class UnformattedIoStatementState : public ExternalIoStatementState<DIR> {
 public:
   using ExternalIoStatementState<DIR>::ExternalIoStatementState;
+  bool Receive(char *, std::size_t);
   int EndIoStatement();
 };
 
@@ -353,5 +357,17 @@
 extern template class FormatControl<
     ExternalFormattedIoStatementState<Direction::Input>>;
 
+class ExternalMiscIoStatementState : public ExternalIoStatementBase {
+public:
+  enum Which { Flush, Backspace, Endfile, Rewind };
+  ExternalMiscIoStatementState(ExternalFileUnit &unit, Which which,
+      const char *sourceFile = nullptr, int sourceLine = 0)
+      : ExternalIoStatementBase{unit, sourceFile, sourceLine}, which_{which} {}
+  int EndIoStatement();
+
+private:
+  Which which_;
+};
+
 } // namespace Fortran::runtime::io
 #endif // FORTRAN_RUNTIME_IO_STMT_H_
diff --git a/flang/runtime/unit.h b/flang/runtime/unit.h
index bdc7a94..c546254 100644
--- a/flang/runtime/unit.h
+++ b/flang/runtime/unit.h
@@ -106,7 +106,8 @@
       ExternalListIoStatementState<Direction::Output>,
       ExternalListIoStatementState<Direction::Input>,
       UnformattedIoStatementState<Direction::Output>,
-      UnformattedIoStatementState<Direction::Input>>
+      UnformattedIoStatementState<Direction::Input>,
+      ExternalMiscIoStatementState>
       u_;
 
   // Points to the active alternative (if any) in u_ for use as a Cookie