[flang-commits] [flang] [llvm] [flang][runtime] Formatted input optimizations (PR #134715)

Peter Klausler via flang-commits flang-commits at lists.llvm.org
Tue Apr 8 11:10:47 PDT 2025


https://github.com/klausler updated https://github.com/llvm/llvm-project/pull/134715

>From a49a1fa3b4b4d72944b9cb61dfc3f156ee307343 Mon Sep 17 00:00:00 2001
From: Peter Klausler <pklausler at nvidia.com>
Date: Fri, 4 Apr 2025 14:59:01 -0700
Subject: [PATCH] [flang][runtime] Formatted input optimizations

Make some minor tweaks (inlining, caching) to the formatting input
path to improve integer input in a SPEC code.  (None of the I/O
library has been tuned yet for performance, and there are some
easy optimizations for common cases.)  Input integer values are
now calculated with native C/C++ 128-bit integers.

A benchmark that only reads about 5M lines of three integer values each
speeds up from over 8 seconds to under 3 in my environment with these
changeds.

If this works out, the code here can be used to optimize the formatted
input paths for real and character data, too.

Fixes https://github.com/llvm/llvm-project/issues/134026.
---
 .../include/flang-rt/runtime/connection.h     |  33 +++--
 flang-rt/include/flang-rt/runtime/io-stmt.h   | 117 +++++++++++++++---
 flang-rt/lib/runtime/connection.cpp           |  27 ----
 flang-rt/lib/runtime/edit-input.cpp           |  39 ++++--
 flang-rt/lib/runtime/io-api.cpp               |  15 +--
 flang-rt/lib/runtime/io-stmt.cpp              |  70 +++++++----
 flang-rt/lib/runtime/unit.cpp                 |  10 +-
 flang/include/flang/Common/uint128.h          |   2 +
 8 files changed, 219 insertions(+), 94 deletions(-)

diff --git a/flang-rt/include/flang-rt/runtime/connection.h b/flang-rt/include/flang-rt/runtime/connection.h
index 03d9658e7067b..601669942cdd6 100644
--- a/flang-rt/include/flang-rt/runtime/connection.h
+++ b/flang-rt/include/flang-rt/runtime/connection.h
@@ -45,19 +45,36 @@ struct ConnectionAttributes {
 };
 
 struct ConnectionState : public ConnectionAttributes {
-  RT_API_ATTRS bool
-  IsAtEOF() const; // true when read has hit EOF or endfile record
-  RT_API_ATTRS bool
-  IsAfterEndfile() const; // true after ENDFILE until repositioned
+  RT_API_ATTRS bool IsAtEOF() const {
+    // true when read has hit EOF or endfile record
+    return endfileRecordNumber && currentRecordNumber >= *endfileRecordNumber;
+  }
+  RT_API_ATTRS bool IsAfterEndfile() const {
+    // true after ENDFILE until repositioned
+    return endfileRecordNumber && currentRecordNumber > *endfileRecordNumber;
+  }
 
   // All positions and measurements are always in units of bytes,
   // not characters.  Multi-byte character encodings are possible in
   // both internal I/O (when the character kind of the variable is 2 or 4)
   // and external formatted I/O (when the encoding is UTF-8).
-  RT_API_ATTRS std::size_t RemainingSpaceInRecord() const;
-  RT_API_ATTRS bool NeedAdvance(std::size_t) const;
-  RT_API_ATTRS void HandleAbsolutePosition(std::int64_t);
-  RT_API_ATTRS void HandleRelativePosition(std::int64_t);
+  RT_API_ATTRS std::size_t RemainingSpaceInRecord() const {
+    auto recl{recordLength.value_or(openRecl.value_or(
+        executionEnvironment.listDirectedOutputLineLengthLimit))};
+    return positionInRecord >= recl ? 0 : recl - positionInRecord;
+  }
+  RT_API_ATTRS bool NeedAdvance(std::size_t width) const {
+    return positionInRecord > 0 && width > RemainingSpaceInRecord();
+  }
+  RT_API_ATTRS void HandleAbsolutePosition(std::int64_t n) {
+    positionInRecord = (n < 0 ? 0 : n) + leftTabLimit.value_or(0);
+  }
+  RT_API_ATTRS void HandleRelativePosition(std::int64_t n) {
+    auto least{leftTabLimit.value_or(0)};
+    auto newPos{positionInRecord + n};
+    positionInRecord = newPos < least ? least : newPos;
+    ;
+  }
 
   RT_API_ATTRS void BeginRecord() {
     positionInRecord = 0;
diff --git a/flang-rt/include/flang-rt/runtime/io-stmt.h b/flang-rt/include/flang-rt/runtime/io-stmt.h
index 33cc91271ab12..c9c193335c940 100644
--- a/flang-rt/include/flang-rt/runtime/io-stmt.h
+++ b/flang-rt/include/flang-rt/runtime/io-stmt.h
@@ -130,20 +130,94 @@ class IoStatementState {
   }
 
   // Vacant after the end of the current record
-  RT_API_ATTRS Fortran::common::optional<char32_t> GetCurrentChar(
+  RT_API_ATTRS Fortran::common::optional<char32_t> GetCurrentCharSlow(
       std::size_t &byteCount);
 
+  // For faster formatted input editing, this structure can be built by
+  // GetUpcomingFastAsciiField() and used to save significant time in
+  // GetCurrentChar, NextInField() and other input utilities when the input
+  // is buffered, does not require UTF-8 conversion, and comprises only
+  // single byte characters.
+  class FastAsciiField {
+  public:
+    RT_API_ATTRS FastAsciiField(ConnectionState &connection)
+        : connection_{connection} {}
+    RT_API_ATTRS FastAsciiField(
+        ConnectionState &connection, const char *start, std::size_t bytes)
+        : connection_{connection}, at_{start}, limit_{start + bytes} {
+      CheckForAsterisk();
+    }
+    RT_API_ATTRS ConnectionState &connection() { return connection_; }
+    RT_API_ATTRS std::size_t got() const { return got_; }
+
+    RT_API_ATTRS bool MustUseSlowPath() const { return at_ == nullptr; }
+
+    RT_API_ATTRS Fortran::common::optional<char32_t> Next() const {
+      if (at_ && at_ < limit_) {
+        return *at_;
+      } else {
+        return std::nullopt;
+      }
+    }
+    RT_API_ATTRS void NextRecord(IoStatementState &io) {
+      if (at_) {
+        if (std::size_t bytes{io.GetNextInputBytes(at_)}) {
+          limit_ = at_ + bytes;
+          CheckForAsterisk();
+        } else {
+          at_ = limit_ = nullptr;
+        }
+      }
+    }
+    RT_API_ATTRS void Advance(int gotten, std::size_t bytes) {
+      if (at_ && at_ < limit_) {
+        ++at_;
+        got_ += gotten;
+      }
+      connection_.HandleRelativePosition(bytes);
+    }
+    RT_API_ATTRS bool MightHaveAsterisk() const { return !at_ || hasAsterisk_; }
+
+  private:
+    RT_API_ATTRS void CheckForAsterisk() {
+      hasAsterisk_ =
+          at_ && at_ < limit_ && std::memchr(at_, '*', limit_ - at_) != nullptr;
+    }
+
+    ConnectionState &connection_;
+    const char *at_{nullptr};
+    const char *limit_{nullptr};
+    std::size_t got_{0}; // for READ(..., SIZE=)
+    bool hasAsterisk_{false};
+  };
+
+  RT_API_ATTRS FastAsciiField GetUpcomingFastAsciiField();
+
+  RT_API_ATTRS Fortran::common::optional<char32_t> GetCurrentChar(
+      std::size_t &byteCount, FastAsciiField *field = nullptr) {
+    if (field) {
+      if (auto ch{field->Next()}) {
+        byteCount = ch ? 1 : 0;
+        return ch;
+      } else if (!field->MustUseSlowPath()) {
+        return std::nullopt;
+      }
+    }
+    return GetCurrentCharSlow(byteCount);
+  }
+
   // The result of CueUpInput() and the "remaining" arguments to SkipSpaces()
   // and NextInField() are always in units of bytes, not characters; the
   // distinction matters for internal input from CHARACTER(KIND=2 and 4).
 
   // For fixed-width fields, return the number of remaining bytes.
   // Skip over leading blanks.
-  RT_API_ATTRS Fortran::common::optional<int> CueUpInput(const DataEdit &edit) {
+  RT_API_ATTRS Fortran::common::optional<int> CueUpInput(
+      const DataEdit &edit, FastAsciiField *fastField = nullptr) {
     Fortran::common::optional<int> remaining;
     if (edit.IsListDirected()) {
       std::size_t byteCount{0};
-      GetNextNonBlank(byteCount);
+      GetNextNonBlank(byteCount, fastField);
     } else {
       if (edit.width.value_or(0) > 0) {
         remaining = *edit.width;
@@ -152,16 +226,17 @@ class IoStatementState {
           *remaining *= bytesPerChar;
         }
       }
-      SkipSpaces(remaining);
+      SkipSpaces(remaining, fastField);
     }
     return remaining;
   }
 
   RT_API_ATTRS Fortran::common::optional<char32_t> SkipSpaces(
-      Fortran::common::optional<int> &remaining) {
+      Fortran::common::optional<int> &remaining,
+      FastAsciiField *fastField = nullptr) {
     while (!remaining || *remaining > 0) {
       std::size_t byteCount{0};
-      if (auto ch{GetCurrentChar(byteCount)}) {
+      if (auto ch{GetCurrentChar(byteCount, fastField)}) {
         if (*ch != ' ' && *ch != '\t') {
           return ch;
         }
@@ -172,7 +247,11 @@ class IoStatementState {
           GotChar(byteCount);
           *remaining -= byteCount;
         }
-        HandleRelativePosition(byteCount);
+        if (fastField) {
+          fastField->Advance(0, byteCount);
+        } else {
+          HandleRelativePosition(byteCount);
+        }
       } else {
         break;
       }
@@ -183,25 +262,35 @@ class IoStatementState {
   // Acquires the next input character, respecting any applicable field width
   // or separator character.
   RT_API_ATTRS Fortran::common::optional<char32_t> NextInField(
-      Fortran::common::optional<int> &remaining, const DataEdit &);
+      Fortran::common::optional<int> &remaining, const DataEdit &,
+      FastAsciiField *field = nullptr);
 
   // Detect and signal any end-of-record condition after input.
   // Returns true if at EOR and remaining input should be padded with blanks.
-  RT_API_ATTRS bool CheckForEndOfRecord(std::size_t afterReading);
+  RT_API_ATTRS bool CheckForEndOfRecord(
+      std::size_t afterReading, const ConnectionState &);
 
   // Skips spaces, advances records, and ignores NAMELIST comments
   RT_API_ATTRS Fortran::common::optional<char32_t> GetNextNonBlank(
-      std::size_t &byteCount) {
-    auto ch{GetCurrentChar(byteCount)};
+      std::size_t &byteCount, FastAsciiField *fastField = nullptr) {
+    auto ch{GetCurrentChar(byteCount, fastField)};
     bool inNamelist{mutableModes().inNamelist};
     while (!ch || *ch == ' ' || *ch == '\t' || *ch == '\n' ||
         (inNamelist && *ch == '!')) {
       if (ch && (*ch == ' ' || *ch == '\t' || *ch == '\n')) {
-        HandleRelativePosition(byteCount);
-      } else if (!AdvanceRecord()) {
+        if (fastField) {
+          fastField->Advance(0, byteCount);
+        } else {
+          HandleRelativePosition(byteCount);
+        }
+      } else if (AdvanceRecord()) {
+        if (fastField) {
+          fastField->NextRecord(*this);
+        }
+      } else {
         return Fortran::common::nullopt;
       }
-      ch = GetCurrentChar(byteCount);
+      ch = GetCurrentChar(byteCount, fastField);
     }
     return ch;
   }
diff --git a/flang-rt/lib/runtime/connection.cpp b/flang-rt/lib/runtime/connection.cpp
index 2f01dbbb95920..2e02717f5425b 100644
--- a/flang-rt/lib/runtime/connection.cpp
+++ b/flang-rt/lib/runtime/connection.cpp
@@ -9,37 +9,10 @@
 #include "flang-rt/runtime/connection.h"
 #include "flang-rt/runtime/environment.h"
 #include "flang-rt/runtime/io-stmt.h"
-#include <algorithm>
 
 namespace Fortran::runtime::io {
 RT_OFFLOAD_API_GROUP_BEGIN
 
-RT_API_ATTRS std::size_t ConnectionState::RemainingSpaceInRecord() const {
-  auto recl{recordLength.value_or(openRecl.value_or(
-      executionEnvironment.listDirectedOutputLineLengthLimit))};
-  return positionInRecord >= recl ? 0 : recl - positionInRecord;
-}
-
-RT_API_ATTRS bool ConnectionState::NeedAdvance(std::size_t width) const {
-  return positionInRecord > 0 && width > RemainingSpaceInRecord();
-}
-
-RT_API_ATTRS bool ConnectionState::IsAtEOF() const {
-  return endfileRecordNumber && currentRecordNumber >= *endfileRecordNumber;
-}
-
-RT_API_ATTRS bool ConnectionState::IsAfterEndfile() const {
-  return endfileRecordNumber && currentRecordNumber > *endfileRecordNumber;
-}
-
-RT_API_ATTRS void ConnectionState::HandleAbsolutePosition(std::int64_t n) {
-  positionInRecord = std::max(n, std::int64_t{0}) + leftTabLimit.value_or(0);
-}
-
-RT_API_ATTRS void ConnectionState::HandleRelativePosition(std::int64_t n) {
-  positionInRecord = std::max(leftTabLimit.value_or(0), positionInRecord + n);
-}
-
 SavedPosition::SavedPosition(IoStatementState &io) : io_{io} {
   ConnectionState &conn{io_.GetConnectionState()};
   saved_ = conn;
diff --git a/flang-rt/lib/runtime/edit-input.cpp b/flang-rt/lib/runtime/edit-input.cpp
index 99a266648f95c..0a7b09f836c7d 100644
--- a/flang-rt/lib/runtime/edit-input.cpp
+++ b/flang-rt/lib/runtime/edit-input.cpp
@@ -169,17 +169,18 @@ static inline RT_API_ATTRS char32_t GetRadixPointChar(const DataEdit &edit) {
 // Prepares input from a field, and returns the sign, if any, else '\0'.
 static RT_API_ATTRS char ScanNumericPrefix(IoStatementState &io,
     const DataEdit &edit, Fortran::common::optional<char32_t> &next,
-    Fortran::common::optional<int> &remaining) {
-  remaining = io.CueUpInput(edit);
-  next = io.NextInField(remaining, edit);
+    Fortran::common::optional<int> &remaining,
+    IoStatementState::FastAsciiField *fastField = nullptr) {
+  remaining = io.CueUpInput(edit, fastField);
+  next = io.NextInField(remaining, edit, fastField);
   char sign{'\0'};
   if (next) {
     if (*next == '-' || *next == '+') {
       sign = *next;
       if (!edit.IsListDirected()) {
-        io.SkipSpaces(remaining);
+        io.SkipSpaces(remaining, fastField);
       }
-      next = io.NextInField(remaining, edit);
+      next = io.NextInField(remaining, edit, fastField);
     }
   }
   return sign;
@@ -213,17 +214,18 @@ RT_API_ATTRS bool EditIntegerInput(IoStatementState &io, const DataEdit &edit,
   }
   Fortran::common::optional<int> remaining;
   Fortran::common::optional<char32_t> next;
-  char sign{ScanNumericPrefix(io, edit, next, remaining)};
+  auto fastField{io.GetUpcomingFastAsciiField()};
+  char sign{ScanNumericPrefix(io, edit, next, remaining, &fastField)};
   if (sign == '-' && !isSigned) {
     io.GetIoErrorHandler().SignalError("Negative sign in UNSIGNED input field");
     return false;
   }
-  common::UnsignedInt128 value{0};
+  common::uint128_t value{0};
   bool any{!!sign};
   bool overflow{false};
   const char32_t comma{GetSeparatorChar(edit)};
-  static constexpr auto maxu128{~common::UnsignedInt128{0}};
-  for (; next; next = io.NextInField(remaining, edit)) {
+  static constexpr auto maxu128{~common::uint128_t{0}};
+  for (; next; next = io.NextInField(remaining, edit, &fastField)) {
     char32_t ch{*next};
     if (ch == ' ' || ch == '\t') {
       if (edit.modes.editingFlags & blankZero) {
@@ -243,7 +245,7 @@ RT_API_ATTRS bool EditIntegerInput(IoStatementState &io, const DataEdit &edit,
         // input, like a few other Fortran compilers do.
         // TODO: also process exponents?  Some compilers do, but they obviously
         // can't just be ignored.
-        while ((next = io.NextInField(remaining, edit))) {
+        while ((next = io.NextInField(remaining, edit, &fastField))) {
           if (*next < '0' || *next > '9') {
             break;
           }
@@ -271,7 +273,7 @@ RT_API_ATTRS bool EditIntegerInput(IoStatementState &io, const DataEdit &edit,
     return false;
   }
   if (isSigned) {
-    auto maxForKind{common::UnsignedInt128{1} << ((8 * kind) - 1)};
+    auto maxForKind{common::uint128_t{1} << ((8 * kind) - 1)};
     overflow |= value >= maxForKind && (value > maxForKind || sign != '-');
   } else {
     auto maxForKind{maxu128 >> (((16 - kind) * 8) + (isSigned ? 1 : 0))};
@@ -287,7 +289,16 @@ RT_API_ATTRS bool EditIntegerInput(IoStatementState &io, const DataEdit &edit,
   }
   if (any || !io.GetIoErrorHandler().InError()) {
     // The value is stored in the lower order bits on big endian platform.
-    // When memcpy, shift the value to the higher order bit.
+    // For memcpy, shift the value to the highest order bits.
+#if USING_NATIVE_INT128_T
+    auto shft{static_cast<int>(sizeof value - kind)};
+    if (!isHostLittleEndian && shft >= 0) {
+      auto l{value << shft};
+      std::memcpy(n, &l, kind);
+    } else {
+      std::memcpy(n, &value, kind); // a blank field means zero
+    }
+#else
     auto shft{static_cast<int>(sizeof(value.low())) - kind};
     // For kind==8 (i.e. shft==0), the value is stored in low_ in big endian.
     if (!isHostLittleEndian && shft >= 0) {
@@ -296,6 +307,8 @@ RT_API_ATTRS bool EditIntegerInput(IoStatementState &io, const DataEdit &edit,
     } else {
       std::memcpy(n, &value, kind); // a blank field means zero
     }
+#endif
+    io.GotChar(fastField.got());
     return true;
   } else {
     return false;
@@ -1070,7 +1083,7 @@ RT_API_ATTRS bool EditCharacterInput(IoStatementState &io, const DataEdit &edit,
       readyBytes = io.GetNextInputBytes(input);
       if (readyBytes == 0 ||
           (readyBytes < remainingChars && edit.modes.nonAdvancing)) {
-        if (io.CheckForEndOfRecord(readyBytes)) {
+        if (io.CheckForEndOfRecord(readyBytes, connection)) {
           if (readyBytes == 0) {
             // PAD='YES' and no more data
             Fortran::runtime::fill_n(x, lengthChars, ' ');
diff --git a/flang-rt/lib/runtime/io-api.cpp b/flang-rt/lib/runtime/io-api.cpp
index 2b7ec9134689b..6af0121437cd5 100644
--- a/flang-rt/lib/runtime/io-api.cpp
+++ b/flang-rt/lib/runtime/io-api.cpp
@@ -1057,14 +1057,15 @@ bool IODEF(InputDescriptor)(Cookie cookie, const Descriptor &descriptor) {
 }
 
 bool IODEF(InputInteger)(Cookie cookie, std::int64_t &n, int kind) {
-  if (!cookie->CheckFormattedStmtType<Direction::Input>("InputInteger")) {
-    return false;
+  IoStatementState &io{*cookie};
+  if (io.BeginReadingRecord()) {
+    if (auto edit{io.GetNextDataEdit()}) {
+      return edit->descriptor == DataEdit::ListDirectedNullValue ||
+          EditIntegerInput(io, *edit, reinterpret_cast<void *>(&n), kind,
+              /*isSigned=*/true);
+    }
   }
-  StaticDescriptor<0> staticDescriptor;
-  Descriptor &descriptor{staticDescriptor.descriptor()};
-  descriptor.Establish(
-      TypeCategory::Integer, kind, reinterpret_cast<void *>(&n), 0);
-  return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
+  return false;
 }
 
 bool IODEF(InputReal32)(Cookie cookie, float &x) {
diff --git a/flang-rt/lib/runtime/io-stmt.cpp b/flang-rt/lib/runtime/io-stmt.cpp
index 636351f560b0a..dcfd35d0de37c 100644
--- a/flang-rt/lib/runtime/io-stmt.cpp
+++ b/flang-rt/lib/runtime/io-stmt.cpp
@@ -596,7 +596,7 @@ ExternalFileUnit *IoStatementState::GetExternalFileUnit() const {
       [](auto &x) { return x.get().GetExternalFileUnit(); }, u_);
 }
 
-Fortran::common::optional<char32_t> IoStatementState::GetCurrentChar(
+Fortran::common::optional<char32_t> IoStatementState::GetCurrentCharSlow(
     std::size_t &byteCount) {
   const char *p{nullptr};
   std::size_t bytes{GetNextInputBytes(p)};
@@ -628,12 +628,24 @@ Fortran::common::optional<char32_t> IoStatementState::GetCurrentChar(
   }
 }
 
+IoStatementState::FastAsciiField IoStatementState::GetUpcomingFastAsciiField() {
+  ConnectionState &connection{GetConnectionState()};
+  if (!connection.isUTF8 && connection.internalIoCharKind <= 1) {
+    const char *p{nullptr};
+    if (std::size_t bytes{GetNextInputBytes(p)}) {
+      return FastAsciiField(connection, p, bytes);
+    }
+  }
+  return FastAsciiField(connection);
+}
+
 Fortran::common::optional<char32_t> IoStatementState::NextInField(
-    Fortran::common::optional<int> &remaining, const DataEdit &edit) {
+    Fortran::common::optional<int> &remaining, const DataEdit &edit,
+    FastAsciiField *field) {
   std::size_t byteCount{0};
   if (!remaining) { // Stream, list-directed, or NAMELIST
-    if (auto next{GetCurrentChar(byteCount)}) {
-      if (edit.IsListDirected()) {
+    if (auto next{GetCurrentChar(byteCount, field)}) {
+      if ((*next < '0' || *next == ';') && edit.IsListDirected()) {
         // list-directed or NAMELIST: check for separators
         switch (*next) {
         case ' ':
@@ -666,21 +678,30 @@ Fortran::common::optional<char32_t> IoStatementState::NextInField(
           break;
         }
       }
-      HandleRelativePosition(byteCount);
-      GotChar(byteCount);
+      if (field) {
+        field->Advance(1, byteCount);
+      } else {
+        HandleRelativePosition(byteCount);
+        GotChar(byteCount);
+      }
       return next;
     }
   } else if (*remaining > 0) {
-    if (auto next{GetCurrentChar(byteCount)}) {
+    if (auto next{GetCurrentChar(byteCount, field)}) {
       if (byteCount > static_cast<std::size_t>(*remaining)) {
         return Fortran::common::nullopt;
       }
       *remaining -= byteCount;
-      HandleRelativePosition(byteCount);
-      GotChar(byteCount);
+      if (field) {
+        field->Advance(1, byteCount);
+      } else {
+        HandleRelativePosition(byteCount);
+        GotChar(byteCount);
+      }
       return next;
     }
-    if (CheckForEndOfRecord(0)) { // do padding
+    if (CheckForEndOfRecord(0,
+            field ? field->connection() : GetConnectionState())) { // do padding
       --*remaining;
       return Fortran::common::optional<char32_t>{' '};
     }
@@ -688,8 +709,8 @@ Fortran::common::optional<char32_t> IoStatementState::NextInField(
   return Fortran::common::nullopt;
 }
 
-bool IoStatementState::CheckForEndOfRecord(std::size_t afterReading) {
-  const ConnectionState &connection{GetConnectionState()};
+bool IoStatementState::CheckForEndOfRecord(
+    std::size_t afterReading, const ConnectionState &connection) {
   if (!connection.IsAtEOF()) {
     if (auto length{connection.EffectiveRecordLength()}) {
       if (connection.positionInRecord +
@@ -798,7 +819,6 @@ Fortran::common::optional<DataEdit>
 ListDirectedStatementState<Direction::Input>::GetNextDataEdit(
     IoStatementState &io, int maxRepeat) {
   // N.B. list-directed transfers cannot be nonadvancing (C1221)
-  ConnectionState &connection{io.GetConnectionState()};
   DataEdit edit;
   edit.descriptor = DataEdit::ListDirected;
   edit.repeat = 1; // may be overridden below
@@ -839,14 +859,15 @@ ListDirectedStatementState<Direction::Input>::GetNextDataEdit(
     imaginaryPart_ = true;
     edit.descriptor = DataEdit::ListDirectedImaginaryPart;
   }
-  auto ch{io.GetNextNonBlank(byteCount)};
+  auto fastField{io.GetUpcomingFastAsciiField()};
+  auto ch{io.GetNextNonBlank(byteCount, &fastField)};
   if (ch && *ch == comma && eatComma_) {
     // Consume comma & whitespace after previous item.
     // This includes the comma between real and imaginary components
     // in list-directed/NAMELIST complex input.
     // (When DECIMAL='COMMA', the comma is actually a semicolon.)
-    io.HandleRelativePosition(byteCount);
-    ch = io.GetNextNonBlank(byteCount);
+    fastField.Advance(0, byteCount);
+    ch = io.GetNextNonBlank(byteCount, &fastField);
   }
   eatComma_ = true;
   if (!ch) {
@@ -864,8 +885,9 @@ ListDirectedStatementState<Direction::Input>::GetNextDataEdit(
   if (imaginaryPart_) { // can't repeat components
     return edit;
   }
-  if (*ch >= '0' && *ch <= '9') { // look for "r*" repetition count
-    auto start{connection.positionInRecord};
+  if (*ch >= '0' && *ch <= '9' && fastField.MightHaveAsterisk()) {
+    // look for "r*" repetition count
+    auto start{fastField.connection().positionInRecord};
     int r{0};
     do {
       static auto constexpr clamp{(std::numeric_limits<int>::max() - '9') / 10};
@@ -874,12 +896,12 @@ ListDirectedStatementState<Direction::Input>::GetNextDataEdit(
         break;
       }
       r = 10 * r + (*ch - '0');
-      io.HandleRelativePosition(byteCount);
-      ch = io.GetCurrentChar(byteCount);
+      fastField.Advance(0, byteCount);
+      ch = io.GetCurrentChar(byteCount, &fastField);
     } while (ch && *ch >= '0' && *ch <= '9');
     if (r > 0 && ch && *ch == '*') { // subtle: r must be nonzero
-      io.HandleRelativePosition(byteCount);
-      ch = io.GetCurrentChar(byteCount);
+      fastField.Advance(0, byteCount);
+      ch = io.GetCurrentChar(byteCount, &fastField);
       if (ch && *ch == '/') { // r*/
         hitSlash_ = true;
         edit.descriptor = DataEdit::ListDirectedNullValue;
@@ -894,12 +916,12 @@ ListDirectedStatementState<Direction::Input>::GetNextDataEdit(
         repeatPosition_.emplace(io);
       }
     } else { // not a repetition count, just an integer value; rewind
-      connection.positionInRecord = start;
+      fastField.connection().positionInRecord = start;
     }
   }
   if (!imaginaryPart_ && ch && *ch == '(') {
     realPart_ = true;
-    io.HandleRelativePosition(byteCount);
+    fastField.connection().HandleRelativePosition(byteCount);
     edit.descriptor = DataEdit::ListDirectedRealPart;
   }
   return edit;
diff --git a/flang-rt/lib/runtime/unit.cpp b/flang-rt/lib/runtime/unit.cpp
index 199287d7237fd..5f52fa2781db5 100644
--- a/flang-rt/lib/runtime/unit.cpp
+++ b/flang-rt/lib/runtime/unit.cpp
@@ -135,6 +135,13 @@ bool ExternalFileUnit::Receive(char *data, std::size_t bytes,
 std::size_t ExternalFileUnit::GetNextInputBytes(
     const char *&p, IoErrorHandler &handler) {
   RUNTIME_CHECK(handler, direction_ == Direction::Input);
+  if (access == Access::Sequential &&
+      positionInRecord < recordLength.value_or(positionInRecord)) {
+    // Fast path for variable-length formatted input: the whole record
+    // must be in frame as a result of newline detection for record length.
+    p = Frame() + recordOffsetInFrame_ + positionInRecord;
+    return *recordLength - positionInRecord;
+  }
   std::size_t length{1};
   if (auto recl{EffectiveRecordLength()}) {
     if (positionInRecord < *recl) {
@@ -443,7 +450,6 @@ void ExternalFileUnit::Rewind(IoErrorHandler &handler) {
     DoImpliedEndfile(handler);
     SetPosition(0);
     currentRecordNumber = 1;
-    leftTabLimit.reset();
     anyWriteSinceLastPositioning_ = false;
   }
 }
@@ -455,6 +461,8 @@ void ExternalFileUnit::SetPosition(std::int64_t pos) {
     directAccessRecWasSet_ = true;
   }
   BeginRecord();
+  beganReadingRecord_ = false; // for positioning after nonadvancing input
+  leftTabLimit.reset();
 }
 
 void ExternalFileUnit::Sought(std::int64_t zeroBasedPos) {
diff --git a/flang/include/flang/Common/uint128.h b/flang/include/flang/Common/uint128.h
index faecb88737a0b..c4bc4689a1eaa 100644
--- a/flang/include/flang/Common/uint128.h
+++ b/flang/include/flang/Common/uint128.h
@@ -278,9 +278,11 @@ using SignedInt128 = Int128<true>;
 
 #if !AVOID_NATIVE_UINT128_T && (defined __GNUC__ || defined __clang__) && \
     defined __SIZEOF_INT128__
+#define USING_NATIVE_INT128_T 1
 using uint128_t = __uint128_t;
 using int128_t = __int128_t;
 #else
+#undef USING_NATIVE_INT128_T
 using uint128_t = UnsignedInt128;
 using int128_t = SignedInt128;
 #endif



More information about the flang-commits mailing list