[flang-commits] [flang] bad5205 - [flang][runtime] Support internal I/O to CHARACTER(KIND/=1)

Peter Klausler via flang-commits flang-commits at lists.llvm.org
Tue Aug 9 08:51:36 PDT 2022


Author: Peter Klausler
Date: 2022-08-09T08:46:21-07:00
New Revision: bad52055954e05a6b4cc86e9e950cac0b42f44a9

URL: https://github.com/llvm/llvm-project/commit/bad52055954e05a6b4cc86e9e950cac0b42f44a9
DIFF: https://github.com/llvm/llvm-project/commit/bad52055954e05a6b4cc86e9e950cac0b42f44a9.diff

LOG: [flang][runtime] Support internal I/O to CHARACTER(KIND/=1)

Allow internal I/O to support non-default kinds of CHARACTER.

The I/O runtime design anticipated this standard feature, but
this patch is somewhat larger than I thought it would be because
many code sites had to have assumptions about units (characters
vs. bytes) brought into harmony, and some encoding utilities
had to be pulled out of IoStatementState and templatized into
their own new header file so that they are available to formatted
output code without having to "thread" an IoStatementState reference
through many call chains.

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

Added: 
    flang/runtime/emit-encoded.h

Modified: 
    flang/lib/Semantics/check-io.cpp
    flang/runtime/connection.h
    flang/runtime/edit-input.cpp
    flang/runtime/edit-output.cpp
    flang/runtime/format-implementation.h
    flang/runtime/format.h
    flang/runtime/internal-unit.cpp
    flang/runtime/internal-unit.h
    flang/runtime/io-stmt.cpp
    flang/runtime/io-stmt.h
    flang/runtime/namelist.cpp
    flang/test/Semantics/io03.f90
    flang/unittests/Runtime/Format.cpp

Removed: 
    


################################################################################
diff  --git a/flang/lib/Semantics/check-io.cpp b/flang/lib/Semantics/check-io.cpp
index 7e9f9414ac8e1..fa044b2615c6f 100644
--- a/flang/lib/Semantics/check-io.cpp
+++ b/flang/lib/Semantics/check-io.cpp
@@ -556,10 +556,6 @@ void IoChecker::Enter(const parser::IoUnit &spec) {
       if (HasVectorSubscript(*expr)) {
         context_.Say(parser::FindSourceLocation(*var), // C1201
             "Internal file must not have a vector subscript"_err_en_US);
-      } else if (!ExprTypeKindIsDefault(*expr, context_)) {
-        // This may be too restrictive; other kinds may be valid.
-        context_.Say(parser::FindSourceLocation(*var), // C1202
-            "Invalid character kind for an internal file variable"_err_en_US);
       }
     }
     SetSpecifier(IoSpecKind::Unit);

diff  --git a/flang/runtime/connection.h b/flang/runtime/connection.h
index ba880b22c975e..acda51124e0d4 100644
--- a/flang/runtime/connection.h
+++ b/flang/runtime/connection.h
@@ -28,6 +28,7 @@ struct ConnectionAttributes {
   Access access{Access::Sequential}; // ACCESS='SEQUENTIAL', 'DIRECT', 'STREAM'
   std::optional<bool> isUnformatted; // FORM='UNFORMATTED' if true
   bool isUTF8{false}; // ENCODING='UTF-8'
+  unsigned char internalIoCharKind{0}; // 0->external, 1/2/4->internal
   std::optional<std::int64_t> openRecl; // RECL= on OPEN
 
   bool IsRecordFile() const {
@@ -39,13 +40,18 @@ struct ConnectionAttributes {
     // For wide CHARACTER kinds, always use UTF-8 for formatted I/O.
     // For single-byte CHARACTER, encode characters >= 0x80 with
     // UTF-8 iff the mode is set.
-    return sizeof(CHAR) > 1 || isUTF8;
+    return internalIoCharKind == 0 && (sizeof(CHAR) > 1 || isUTF8);
   }
 };
 
 struct ConnectionState : public ConnectionAttributes {
   bool IsAtEOF() const; // true when read has hit EOF or endfile record
   bool IsAfterEndfile() const; // true after ENDFILE until repositioned
+
+  // 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).
   std::size_t RemainingSpaceInRecord() const;
   bool NeedAdvance(std::size_t) const;
   void HandleAbsolutePosition(std::int64_t);
@@ -68,13 +74,13 @@ struct ConnectionState : public ConnectionAttributes {
 
   std::int64_t currentRecordNumber{1}; // 1 is first
 
-  // positionInRecord is the 0-based offset in the current recurd to/from
-  // which the next data transfer will occur.  It can be past
+  // positionInRecord is the 0-based bytes offset in the current recurd
+  // to/from which the next data transfer will occur.  It can be past
   // furthestPositionInRecord if moved by an X or T or TR control edit
   // descriptor.
   std::int64_t positionInRecord{0};
 
-  // furthestPositionInRecord is the 0-based offset of the greatest
+  // furthestPositionInRecord is the 0-based byte offset of the greatest
   // position in the current record to/from which any data transfer has
   // occurred, plus one.  It can be viewed as a count of bytes processed.
   std::int64_t furthestPositionInRecord{0}; // max(position+bytes)

diff  --git a/flang/runtime/edit-input.cpp b/flang/runtime/edit-input.cpp
index f2ee328c3b478..64175095b5522 100644
--- a/flang/runtime/edit-input.cpp
+++ b/flang/runtime/edit-input.cpp
@@ -19,18 +19,19 @@ namespace Fortran::runtime::io {
 template <int LOG2_BASE>
 static bool EditBOZInput(
     IoStatementState &io, const DataEdit &edit, void *n, std::size_t bytes) {
-  std::optional<int> remaining;
-  std::optional<char32_t> next{io.PrepareInput(edit, remaining)};
+  // Skip leading white space & zeroes
+  std::optional<int> remaining{io.CueUpInput(edit)};
+  auto start{io.GetConnectionState().positionInRecord};
+  std::optional<char32_t> next{io.NextInField(remaining, edit)};
   if (next.value_or('?') == '0') {
     do {
+      start = io.GetConnectionState().positionInRecord;
       next = io.NextInField(remaining, edit);
     } while (next && *next == '0');
   }
   // Count significant digits after any leading white space & zeroes
   int digits{0};
-  int chars{0};
   for (; next; next = io.NextInField(remaining, edit)) {
-    ++chars;
     char32_t ch{*next};
     if (ch == ' ' || ch == '\t') {
       continue;
@@ -54,7 +55,7 @@ static bool EditBOZInput(
     return false;
   }
   // Reset to start of significant digits
-  io.HandleRelativePosition(-chars);
+  io.HandleAbsolutePosition(start);
   remaining.reset();
   // Make a second pass now that the digit count is known
   std::memset(n, 0, bytes);
@@ -99,7 +100,8 @@ static inline char32_t GetDecimalPoint(const DataEdit &edit) {
 // Returns true if there's a '-' sign.
 static bool ScanNumericPrefix(IoStatementState &io, const DataEdit &edit,
     std::optional<char32_t> &next, std::optional<int> &remaining) {
-  next = io.PrepareInput(edit, remaining);
+  remaining = io.CueUpInput(edit);
+  next = io.NextInField(remaining, edit);
   bool negative{false};
   if (next) {
     negative = *next == '-';
@@ -384,10 +386,13 @@ static bool TryFastPathRealInput(
   if (edit.modes.scale != 0) {
     return false;
   }
+  const ConnectionState &connection{io.GetConnectionState()};
+  if (connection.internalIoCharKind > 1) {
+    return false; // reading non-default character
+  }
   const char *str{nullptr};
   std::size_t got{io.GetNextInputBytes(str)};
-  if (got == 0 || str == nullptr ||
-      !io.GetConnectionState().recordLength.has_value()) {
+  if (got == 0 || str == nullptr || !connection.recordLength.has_value()) {
     return false; // could not access reliably-terminated input stream
   }
   const char *p{str};
@@ -569,8 +574,8 @@ bool EditLogicalInput(IoStatementState &io, const DataEdit &edit, bool &x) {
         edit.descriptor);
     return false;
   }
-  std::optional<int> remaining;
-  std::optional<char32_t> next{io.PrepareInput(edit, remaining)};
+  std::optional<int> remaining{io.CueUpInput(edit)};
+  std::optional<char32_t> next{io.NextInField(remaining, edit)};
   if (next && *next == '.') { // skip optional period
     next = io.NextInField(remaining, edit);
   }
@@ -740,6 +745,18 @@ bool EditCharacterInput(
         chunk = 1;
       }
       --remaining;
+    } else if (connection.internalIoCharKind > 1) {
+      // Reading from non-default character internal unit
+      chunk = connection.internalIoCharKind;
+      if (skipping) {
+        --skip;
+      } else {
+        char32_t buffer{0};
+        std::memcpy(&buffer, input, chunk);
+        *x++ = buffer;
+        --length;
+      }
+      --remaining;
     } else if constexpr (sizeof *x > 1) {
       // Read single byte with expansion into multi-byte CHARACTER
       chunk = 1;

diff  --git a/flang/runtime/edit-output.cpp b/flang/runtime/edit-output.cpp
index b49e222383040..965c7ceb7de4a 100644
--- a/flang/runtime/edit-output.cpp
+++ b/flang/runtime/edit-output.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "edit-output.h"
+#include "emit-encoded.h"
 #include "utf.h"
 #include "flang/Common/uint128.h"
 #include <algorithm>
@@ -69,17 +70,17 @@ static bool EditBOZOutput(IoStatementState &io, const DataEdit &edit,
   int subTotal{leadingZeroes + significant};
   int leadingSpaces{std::max(0, editWidth - subTotal)};
   if (editWidth > 0 && leadingSpaces + subTotal > editWidth) {
-    return io.EmitRepeated('*', editWidth);
+    return EmitRepeated(io, '*', editWidth);
   }
-  if (!(io.EmitRepeated(' ', leadingSpaces) &&
-          io.EmitRepeated('0', leadingZeroes))) {
+  if (!(EmitRepeated(io, ' ', leadingSpaces) &&
+          EmitRepeated(io, '0', leadingZeroes))) {
     return false;
   }
   // Emit remaining digits
   while (bytes > 0) {
     if (get == 0) {
       char ch{static_cast<char>(digit >= 10 ? 'A' + digit - 10 : '0' + digit)};
-      if (!io.Emit(&ch, 1)) {
+      if (!EmitAscii(io, &ch, 1)) {
         return false;
       }
       get = LOG2_BASE;
@@ -157,7 +158,7 @@ bool EditIntegerOutput(IoStatementState &io, const DataEdit &edit,
   int subTotal{signChars + leadingZeroes + digits};
   int leadingSpaces{std::max(0, editWidth - subTotal)};
   if (editWidth > 0 && leadingSpaces + subTotal > editWidth) {
-    return io.EmitRepeated('*', editWidth);
+    return EmitRepeated(io, '*', editWidth);
   }
   if (edit.IsListDirected()) {
     int total{std::max(leadingSpaces, 1) + subTotal};
@@ -167,9 +168,9 @@ bool EditIntegerOutput(IoStatementState &io, const DataEdit &edit,
     }
     leadingSpaces = 1;
   }
-  return io.EmitRepeated(' ', leadingSpaces) &&
-      io.Emit(n < 0 ? "-" : "+", signChars) &&
-      io.EmitRepeated('0', leadingZeroes) && io.Emit(p, digits);
+  return EmitRepeated(io, ' ', leadingSpaces) &&
+      EmitAscii(io, n < 0 ? "-" : "+", signChars) &&
+      EmitRepeated(io, '0', leadingZeroes) && EmitAscii(io, p, digits);
 }
 
 // Formats the exponent (see table 13.1 for all the cases)
@@ -218,9 +219,9 @@ bool RealOutputEditingBase::EmitPrefix(
     length += prefixLength + suffixLength;
     ConnectionState &connection{io_.GetConnectionState()};
     return (!connection.NeedAdvance(length) || io_.AdvanceRecord()) &&
-        io_.Emit(" (", prefixLength);
+        EmitAscii(io_, " (", prefixLength);
   } else if (width > length) {
-    return io_.EmitRepeated(' ', width - length);
+    return EmitRepeated(io_, ' ', width - length);
   } else {
     return true;
   }
@@ -228,9 +229,10 @@ bool RealOutputEditingBase::EmitPrefix(
 
 bool RealOutputEditingBase::EmitSuffix(const DataEdit &edit) {
   if (edit.descriptor == DataEdit::ListDirectedRealPart) {
-    return io_.Emit(edit.modes.editingFlags & decimalComma ? ";" : ",", 1);
+    return EmitAscii(
+        io_, edit.modes.editingFlags & decimalComma ? ";" : ",", 1);
   } else if (edit.descriptor == DataEdit::ListDirectedImaginaryPart) {
-    return io_.Emit(")", 1);
+    return EmitAscii(io_, ")", 1);
   } else {
     return true;
   }
@@ -307,7 +309,7 @@ bool RealOutputEditing<binaryPrecision>::EditEorDOutput(const DataEdit &edit) {
         Convert(significantDigits, edit.modes.round, flags)};
     if (IsInfOrNaN(converted)) {
       return EmitPrefix(edit, converted.length, editWidth) &&
-          io_.Emit(converted.str, converted.length) && EmitSuffix(edit);
+          EmitAscii(io_, converted.str, converted.length) && EmitSuffix(edit);
     }
     if (!IsZero()) {
       converted.decimalExponent -= scale;
@@ -354,7 +356,7 @@ bool RealOutputEditing<binaryPrecision>::EditEorDOutput(const DataEdit &edit) {
         expoLength};
     int width{editWidth > 0 ? editWidth : totalLength};
     if (totalLength > width || !exponent) {
-      return io_.EmitRepeated('*', width);
+      return EmitRepeated(io_, '*', width);
     }
     if (totalLength < width && digitsBeforePoint == 0 &&
         zeroesBeforePoint == 0) {
@@ -365,14 +367,14 @@ bool RealOutputEditing<binaryPrecision>::EditEorDOutput(const DataEdit &edit) {
       width = totalLength;
     }
     return EmitPrefix(edit, totalLength, width) &&
-        io_.Emit(converted.str, signLength + digitsBeforePoint) &&
-        io_.EmitRepeated('0', zeroesBeforePoint) &&
-        io_.Emit(edit.modes.editingFlags & decimalComma ? "," : ".", 1) &&
-        io_.EmitRepeated('0', zeroesAfterPoint) &&
-        io_.Emit(
-            converted.str + signLength + digitsBeforePoint, digitsAfterPoint) &&
-        io_.EmitRepeated('0', trailingZeroes) &&
-        io_.Emit(exponent, expoLength) && EmitSuffix(edit);
+        EmitAscii(io_, converted.str, signLength + digitsBeforePoint) &&
+        EmitRepeated(io_, '0', zeroesBeforePoint) &&
+        EmitAscii(io_, edit.modes.editingFlags & decimalComma ? "," : ".", 1) &&
+        EmitRepeated(io_, '0', zeroesAfterPoint) &&
+        EmitAscii(io_, converted.str + signLength + digitsBeforePoint,
+            digitsAfterPoint) &&
+        EmitRepeated(io_, '0', trailingZeroes) &&
+        EmitAscii(io_, exponent, expoLength) && EmitSuffix(edit);
   }
 }
 
@@ -401,7 +403,7 @@ bool RealOutputEditing<binaryPrecision>::EditFOutput(const DataEdit &edit) {
         Convert(extraDigits + fracDigits, rounding, flags)};
     if (IsInfOrNaN(converted)) {
       return EmitPrefix(edit, converted.length, editWidth) &&
-          io_.Emit(converted.str, converted.length) && EmitSuffix(edit);
+          EmitAscii(io_, converted.str, converted.length) && EmitSuffix(edit);
     }
     int expo{converted.decimalExponent + edit.modes.scale /*kP*/};
     int signLength{*converted.str == '-' || *converted.str == '+' ? 1 : 0};
@@ -463,22 +465,22 @@ bool RealOutputEditing<binaryPrecision>::EditFOutput(const DataEdit &edit) {
         trailingZeroes};
     int width{editWidth > 0 ? editWidth : totalLength};
     if (totalLength > width) {
-      return io_.EmitRepeated('*', width);
+      return EmitRepeated(io_, '*', width);
     }
     if (totalLength < width && digitsBeforePoint + zeroesBeforePoint == 0) {
       zeroesBeforePoint = 1;
       ++totalLength;
     }
     return EmitPrefix(edit, totalLength, width) &&
-        io_.Emit(converted.str, signLength + digitsBeforePoint) &&
-        io_.EmitRepeated('0', zeroesBeforePoint) &&
-        io_.Emit(edit.modes.editingFlags & decimalComma ? "," : ".", 1) &&
-        io_.EmitRepeated('0', zeroesAfterPoint) &&
-        io_.Emit(
-            converted.str + signLength + digitsBeforePoint, digitsAfterPoint) &&
-        io_.EmitRepeated('1', trailingOnes) &&
-        io_.EmitRepeated('0', trailingZeroes) &&
-        io_.EmitRepeated(' ', trailingBlanks_) && EmitSuffix(edit);
+        EmitAscii(io_, converted.str, signLength + digitsBeforePoint) &&
+        EmitRepeated(io_, '0', zeroesBeforePoint) &&
+        EmitAscii(io_, edit.modes.editingFlags & decimalComma ? "," : ".", 1) &&
+        EmitRepeated(io_, '0', zeroesAfterPoint) &&
+        EmitAscii(io_, converted.str + signLength + digitsBeforePoint,
+            digitsAfterPoint) &&
+        EmitRepeated(io_, '1', trailingOnes) &&
+        EmitRepeated(io_, '0', trailingZeroes) &&
+        EmitRepeated(io_, ' ', trailingBlanks_) && EmitSuffix(edit);
   }
 }
 
@@ -594,15 +596,16 @@ template <int KIND> bool RealOutputEditing<KIND>::Edit(const DataEdit &edit) {
 
 bool ListDirectedLogicalOutput(IoStatementState &io,
     ListDirectedStatementState<Direction::Output> &list, bool truth) {
-  return list.EmitLeadingSpaceOrAdvance(io) && io.Emit(truth ? "T" : "F", 1);
+  return list.EmitLeadingSpaceOrAdvance(io) &&
+      EmitAscii(io, truth ? "T" : "F", 1);
 }
 
 bool EditLogicalOutput(IoStatementState &io, const DataEdit &edit, bool truth) {
   switch (edit.descriptor) {
   case 'L':
   case 'G':
-    return io.EmitRepeated(' ', std::max(0, edit.width.value_or(1) - 1)) &&
-        io.Emit(truth ? "T" : "F", 1);
+    return EmitRepeated(io, ' ', std::max(0, edit.width.value_or(1) - 1)) &&
+        EmitAscii(io, truth ? "T" : "F", 1);
   case 'B':
     return EditBOZOutput<1>(io, edit,
         reinterpret_cast<const unsigned char *>(&truth), sizeof truth);
@@ -635,7 +638,7 @@ bool ListDirectedCharacterOutput(IoStatementState &io,
       if (connection.NeedAdvance(1)) {
         ok = ok && io.AdvanceRecord();
       }
-      ok = ok && io.EmitEncoded(&ch, 1);
+      ok = ok && EmitEncoded(io, &ch, 1);
     }};
     EmitOne(modes.delim);
     for (std::size_t j{0}; j < length; ++j) {
@@ -658,15 +661,18 @@ bool ListDirectedCharacterOutput(IoStatementState &io,
     // Undelimited list-directed output
     ok = ok && list.EmitLeadingSpaceOrAdvance(io, length > 0 ? 1 : 0, true);
     std::size_t put{0};
-    std::size_t oneIfUTF8{connection.useUTF8<CHAR>() ? 1 : length};
+    std::size_t oneAtATime{
+        connection.useUTF8<CHAR>() || connection.internalIoCharKind > 1
+            ? 1
+            : length};
     while (ok && put < length) {
       if (std::size_t chunk{std::min<std::size_t>(
-              std::min<std::size_t>(length - put, oneIfUTF8),
+              std::min<std::size_t>(length - put, oneAtATime),
               connection.RemainingSpaceInRecord())}) {
-        ok = io.EmitEncoded(x + put, chunk);
+        ok = EmitEncoded(io, x + put, chunk);
         put += chunk;
       } else {
-        ok = io.AdvanceRecord() && io.Emit(" ", 1);
+        ok = io.AdvanceRecord() && EmitAscii(io, " ", 1);
       }
     }
     list.set_lastWasUndelimitedCharacter(true);
@@ -702,8 +708,8 @@ bool EditCharacterOutput(IoStatementState &io, const DataEdit &edit,
         edit.descriptor);
     return false;
   }
-  return io.EmitRepeated(' ', std::max(0, width - len)) &&
-      io.EmitEncoded(x, std::min(width, len));
+  return EmitRepeated(io, ' ', std::max(0, width - len)) &&
+      EmitEncoded(io, x, std::min(width, len));
 }
 
 template bool EditIntegerOutput<1>(

diff  --git a/flang/runtime/emit-encoded.h b/flang/runtime/emit-encoded.h
new file mode 100644
index 0000000000000..192c7ec068a4e
--- /dev/null
+++ b/flang/runtime/emit-encoded.h
@@ -0,0 +1,94 @@
+//===-- runtime/emit-encoded.h ----------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// Templates for emitting CHARACTER values with conversion
+
+#ifndef FORTRAN_RUNTIME_EMIT_ENCODED_H_
+#define FORTRAN_RUNTIME_EMIT_ENCODED_H_
+
+#include "connection.h"
+#include "environment.h"
+#include "utf.h"
+
+namespace Fortran::runtime::io {
+
+template <typename CONTEXT, typename CHAR>
+bool EmitEncoded(CONTEXT &to, const CHAR *data, std::size_t chars) {
+  ConnectionState &connection{to.GetConnectionState()};
+  if (connection.useUTF8<CHAR>()) {
+    using UnsignedChar = std::make_unsigned_t<CHAR>;
+    const UnsignedChar *uData{reinterpret_cast<const UnsignedChar *>(data)};
+    char buffer[256];
+    std::size_t at{0};
+    while (chars-- > 0) {
+      auto len{EncodeUTF8(buffer + at, *uData++)};
+      at += len;
+      if (at + maxUTF8Bytes > sizeof buffer) {
+        if (!to.Emit(buffer, at)) {
+          return false;
+        }
+        at = 0;
+      }
+    }
+    return at == 0 || to.Emit(buffer, at);
+  } else {
+    std::size_t internalKind = connection.internalIoCharKind;
+    if (internalKind == 0 || internalKind == sizeof(CHAR)) {
+      const char *rawData{reinterpret_cast<const char *>(data)};
+      return to.Emit(rawData, chars * sizeof(CHAR), sizeof(CHAR));
+    } else {
+      // CHARACTER kind conversion for internal output
+      while (chars-- > 0) {
+        char32_t buffer = *data++;
+        char *p{reinterpret_cast<char *>(&buffer)};
+        if constexpr (!isHostLittleEndian) {
+          p += sizeof(buffer) - internalKind;
+        }
+        if (!to.Emit(p, internalKind)) {
+          return false;
+        }
+      }
+      return true;
+    }
+  }
+}
+
+template <typename CONTEXT>
+bool EmitAscii(CONTEXT &to, const char *data, std::size_t chars) {
+  ConnectionState &connection{to.GetConnectionState()};
+  if (connection.internalIoCharKind <= 1) {
+    return to.Emit(data, chars);
+  } else {
+    return EmitEncoded(to, data, chars);
+  }
+}
+
+template <typename CONTEXT>
+bool EmitRepeated(CONTEXT &to, char ch, std::size_t n) {
+  if (n <= 0) {
+    return true;
+  }
+  ConnectionState &connection{to.GetConnectionState()};
+  if (connection.internalIoCharKind <= 1) {
+    while (n-- > 0) {
+      if (!to.Emit(&ch, 1)) {
+        return false;
+      }
+    }
+  } else {
+    while (n-- > 0) {
+      if (!EmitEncoded(to, &ch, 1)) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+} // namespace Fortran::runtime::io
+#endif // FORTRAN_RUNTIME_EMIT_ENCODED_H_

diff  --git a/flang/runtime/format-implementation.h b/flang/runtime/format-implementation.h
index 4ebd84aacb158..f29b59276f81e 100644
--- a/flang/runtime/format-implementation.h
+++ b/flang/runtime/format-implementation.h
@@ -11,6 +11,7 @@
 #ifndef FORTRAN_RUNTIME_FORMAT_IMPLEMENTATION_H_
 #define FORTRAN_RUNTIME_FORMAT_IMPLEMENTATION_H_
 
+#include "emit-encoded.h"
 #include "format.h"
 #include "io-stmt.h"
 #include "flang/Common/format.h"
@@ -130,6 +131,10 @@ static void HandleControl(CONTEXT &context, char ch, char next, int n) {
     break;
   case 'X':
     if (!next) {
+      ConnectionState &connection{context.GetConnectionState()};
+      if (connection.internalIoCharKind > 1) {
+        n *= connection.internalIoCharKind;
+      }
       context.HandleRelativePosition(n);
       return;
     }
@@ -146,7 +151,14 @@ static void HandleControl(CONTEXT &context, char ch, char next, int n) {
     break;
   case 'T': {
     if (!next) { // Tn
-      context.HandleAbsolutePosition(n - 1); // convert 1-based to 0-based
+      --n; // convert 1-based to 0-based
+    }
+    ConnectionState &connection{context.GetConnectionState()};
+    if (connection.internalIoCharKind > 1) {
+      n *= connection.internalIoCharKind;
+    }
+    if (!next) { // Tn
+      context.HandleAbsolutePosition(n);
       return;
     }
     if (next == 'L' || next == 'R') { // TLn & TRn
@@ -300,7 +312,7 @@ int FormatControl<CONTEXT>::CueUpNextDataEdit(Context &context, bool stop) {
       } else {
         --chars;
       }
-      context.Emit(format_ + start, chars);
+      EmitAscii(context, format_ + start, chars);
     } else if (ch == 'H') {
       // 9HHOLLERITH
       if (!repeat || *repeat < 1 || offset_ + *repeat > formatLength_) {
@@ -308,7 +320,7 @@ int FormatControl<CONTEXT>::CueUpNextDataEdit(Context &context, bool stop) {
             maybeReversionPoint);
         return 0;
       }
-      context.Emit(format_ + offset_, static_cast<std::size_t>(*repeat));
+      EmitAscii(context, format_ + offset_, static_cast<std::size_t>(*repeat));
       offset_ += *repeat;
     } else if (ch >= 'A' && ch <= 'Z') {
       int start{offset_ - 1};
@@ -350,7 +362,7 @@ int FormatControl<CONTEXT>::CueUpNextDataEdit(Context &context, bool stop) {
     } else if (ch == '\t' || ch == '\v') {
       // Tabs (extension)
       // TODO: any other raw characters?
-      context.Emit(format_ + offset_ - 1, 1);
+      EmitAscii(context, format_ + offset_ - 1, 1);
     } else {
       ReportBadFormat(
           context, "Invalid character in FORMAT", maybeReversionPoint);

diff  --git a/flang/runtime/format.h b/flang/runtime/format.h
index d0b16bb04f720..2db7a788f4fe3 100644
--- a/flang/runtime/format.h
+++ b/flang/runtime/format.h
@@ -20,6 +20,8 @@
 
 namespace Fortran::runtime::io {
 
+class IoStatementState;
+
 enum EditingFlags {
   blankZero = 1, // BLANK=ZERO or BZ edit
   decimalComma = 2, // DECIMAL=COMMA or DC edit
@@ -80,7 +82,7 @@ struct DataEdit {
 template <typename CONTEXT> class FormatControl {
 public:
   using Context = CONTEXT;
-  using CharType = typename Context::CharType;
+  using CharType = char; // formats are always default kind CHARACTER
 
   FormatControl() {}
   FormatControl(const Terminator &, const CharType *format,

diff  --git a/flang/runtime/internal-unit.cpp b/flang/runtime/internal-unit.cpp
index 9f1d6c572c327..a38db49a83ed6 100644
--- a/flang/runtime/internal-unit.cpp
+++ b/flang/runtime/internal-unit.cpp
@@ -16,23 +16,27 @@ namespace Fortran::runtime::io {
 
 template <Direction DIR>
 InternalDescriptorUnit<DIR>::InternalDescriptorUnit(
-    Scalar scalar, std::size_t length) {
+    Scalar scalar, std::size_t length, int kind) {
+  internalIoCharKind = kind;
   recordLength = length;
   endfileRecordNumber = 2;
   void *pointer{reinterpret_cast<void *>(const_cast<char *>(scalar))};
-  descriptor().Establish(TypeCode{CFI_type_char}, length, pointer, 0, nullptr,
-      CFI_attribute_pointer);
+  descriptor().Establish(TypeCode{TypeCategory::Character, kind}, length * kind,
+      pointer, 0, nullptr, CFI_attribute_pointer);
 }
 
 template <Direction DIR>
 InternalDescriptorUnit<DIR>::InternalDescriptorUnit(
     const Descriptor &that, const Terminator &terminator) {
-  RUNTIME_CHECK(terminator, that.type().IsCharacter());
+  auto thatType{that.type().GetCategoryAndKind()};
+  RUNTIME_CHECK(terminator, thatType.has_value());
+  RUNTIME_CHECK(terminator, thatType->first == TypeCategory::Character);
   Descriptor &d{descriptor()};
   RUNTIME_CHECK(
       terminator, that.SizeInBytes() <= d.SizeInBytes(maxRank, true, 0));
   new (&d) Descriptor{that};
   d.Check();
+  internalIoCharKind = thatType->second;
   recordLength = d.ElementBytes();
   endfileRecordNumber = d.Elements() + 1;
 }
@@ -73,8 +77,8 @@ bool InternalDescriptorUnit<DIR>::Emit(
       bytes = std::max(std::int64_t{0}, furthestAfter - positionInRecord);
       ok = false;
     } else if (positionInRecord > furthestPositionInRecord) {
-      std::fill_n(record + furthestPositionInRecord,
-          positionInRecord - furthestPositionInRecord, ' ');
+      BlankFill(record + furthestPositionInRecord,
+          positionInRecord - furthestPositionInRecord);
     }
     std::memcpy(record + positionInRecord, data, bytes);
     positionInRecord += bytes;
@@ -118,14 +122,30 @@ bool InternalDescriptorUnit<DIR>::AdvanceRecord(IoErrorHandler &handler) {
   return true;
 }
 
+template <Direction DIR>
+void InternalDescriptorUnit<DIR>::BlankFill(char *at, std::size_t bytes) {
+  switch (internalIoCharKind) {
+  case 2:
+    std::fill_n(reinterpret_cast<char16_t *>(at), bytes / 2,
+        static_cast<char16_t>(' '));
+    break;
+  case 4:
+    std::fill_n(reinterpret_cast<char32_t *>(at), bytes / 4,
+        static_cast<char32_t>(' '));
+    break;
+  default:
+    std::fill_n(at, bytes, ' ');
+    break;
+  }
+}
+
 template <Direction DIR>
 void InternalDescriptorUnit<DIR>::BlankFillOutputRecord() {
   if constexpr (DIR == Direction::Output) {
     if (furthestPositionInRecord <
         recordLength.value_or(furthestPositionInRecord)) {
-      char *record{CurrentRecord()};
-      std::fill_n(record + furthestPositionInRecord,
-          *recordLength - furthestPositionInRecord, ' ');
+      BlankFill(CurrentRecord() + furthestPositionInRecord,
+          *recordLength - furthestPositionInRecord);
     }
   }
 }

diff  --git a/flang/runtime/internal-unit.h b/flang/runtime/internal-unit.h
index e59866013188c..06e54dee10f6b 100644
--- a/flang/runtime/internal-unit.h
+++ b/flang/runtime/internal-unit.h
@@ -26,7 +26,7 @@ template <Direction DIR> class InternalDescriptorUnit : public ConnectionState {
 public:
   using Scalar =
       std::conditional_t<DIR == Direction::Input, const char *, char *>;
-  InternalDescriptorUnit(Scalar, std::size_t);
+  InternalDescriptorUnit(Scalar, std::size_t chars, int kind);
   InternalDescriptorUnit(const Descriptor &, const Terminator &);
   void EndIoStatement();
 
@@ -44,6 +44,7 @@ template <Direction DIR> class InternalDescriptorUnit : public ConnectionState {
     return descriptor().template ZeroBasedIndexedElement<char>(
         currentRecordNumber - 1);
   }
+  void BlankFill(char *, std::size_t);
   void BlankFillOutputRecord();
 
   StaticDescriptor<maxRank, true /*addendum*/> staticDescriptor_;

diff  --git a/flang/runtime/io-stmt.cpp b/flang/runtime/io-stmt.cpp
index 66b5e9c30d627..65d3eb9c75905 100644
--- a/flang/runtime/io-stmt.cpp
+++ b/flang/runtime/io-stmt.cpp
@@ -8,6 +8,7 @@
 
 #include "io-stmt.h"
 #include "connection.h"
+#include "emit-encoded.h"
 #include "format.h"
 #include "tools.h"
 #include "unit.h"
@@ -25,12 +26,6 @@ bool IoStatementBase::Emit(const char *, std::size_t, std::size_t) {
   return false;
 }
 
-bool IoStatementBase::Emit(const char *, std::size_t) { return false; }
-
-bool IoStatementBase::Emit(const char16_t *, std::size_t) { return false; }
-
-bool IoStatementBase::Emit(const char32_t *, std::size_t) { return false; }
-
 std::size_t IoStatementBase::GetNextInputBytes(const char *&p) {
   p = nullptr;
   return 0;
@@ -82,34 +77,33 @@ void IoStatementBase::BadInquiryKeywordHashCrash(InquiryKeywordHash inquiry) {
       decode ? decode : "(cannot decode)");
 }
 
-template <Direction DIR, typename CHAR>
-InternalIoStatementState<DIR, CHAR>::InternalIoStatementState(
+template <Direction DIR>
+InternalIoStatementState<DIR>::InternalIoStatementState(
     Buffer scalar, std::size_t length, const char *sourceFile, int sourceLine)
-    : IoStatementBase{sourceFile, sourceLine}, unit_{scalar, length} {}
+    : IoStatementBase{sourceFile, sourceLine}, unit_{scalar, length, 1} {}
 
-template <Direction DIR, typename CHAR>
-InternalIoStatementState<DIR, CHAR>::InternalIoStatementState(
+template <Direction DIR>
+InternalIoStatementState<DIR>::InternalIoStatementState(
     const Descriptor &d, const char *sourceFile, int sourceLine)
     : IoStatementBase{sourceFile, sourceLine}, unit_{d, *this} {}
 
-template <Direction DIR, typename CHAR>
-bool InternalIoStatementState<DIR, CHAR>::Emit(
-    const CharType *data, std::size_t chars) {
+template <Direction DIR>
+bool InternalIoStatementState<DIR>::Emit(
+    const char *data, std::size_t bytes, std::size_t /*elementBytes*/) {
   if constexpr (DIR == Direction::Input) {
     Crash("InternalIoStatementState<Direction::Input>::Emit() called");
     return false;
   }
-  return unit_.Emit(data, chars * sizeof(CharType), *this);
+  return unit_.Emit(data, bytes, *this);
 }
 
-template <Direction DIR, typename CHAR>
-std::size_t InternalIoStatementState<DIR, CHAR>::GetNextInputBytes(
-    const char *&p) {
+template <Direction DIR>
+std::size_t InternalIoStatementState<DIR>::GetNextInputBytes(const char *&p) {
   return unit_.GetNextInputBytes(p, *this);
 }
 
-template <Direction DIR, typename CHAR>
-bool InternalIoStatementState<DIR, CHAR>::AdvanceRecord(int n) {
+template <Direction DIR>
+bool InternalIoStatementState<DIR>::AdvanceRecord(int n) {
   while (n-- > 0) {
     if (!unit_.AdvanceRecord(*this)) {
       return false;
@@ -118,13 +112,11 @@ bool InternalIoStatementState<DIR, CHAR>::AdvanceRecord(int n) {
   return true;
 }
 
-template <Direction DIR, typename CHAR>
-void InternalIoStatementState<DIR, CHAR>::BackspaceRecord() {
+template <Direction DIR> void InternalIoStatementState<DIR>::BackspaceRecord() {
   unit_.BackspaceRecord(*this);
 }
 
-template <Direction DIR, typename CHAR>
-int InternalIoStatementState<DIR, CHAR>::EndIoStatement() {
+template <Direction DIR> int InternalIoStatementState<DIR>::EndIoStatement() {
   if constexpr (DIR == Direction::Output) {
     unit_.EndIoStatement(); // fill
   }
@@ -135,31 +127,28 @@ int InternalIoStatementState<DIR, CHAR>::EndIoStatement() {
   return result;
 }
 
-template <Direction DIR, typename CHAR>
-void InternalIoStatementState<DIR, CHAR>::HandleAbsolutePosition(
-    std::int64_t n) {
+template <Direction DIR>
+void InternalIoStatementState<DIR>::HandleAbsolutePosition(std::int64_t n) {
   return unit_.HandleAbsolutePosition(n);
 }
 
-template <Direction DIR, typename CHAR>
-void InternalIoStatementState<DIR, CHAR>::HandleRelativePosition(
-    std::int64_t n) {
+template <Direction DIR>
+void InternalIoStatementState<DIR>::HandleRelativePosition(std::int64_t n) {
   return unit_.HandleRelativePosition(n);
 }
 
 template <Direction DIR, typename CHAR>
 InternalFormattedIoStatementState<DIR, CHAR>::InternalFormattedIoStatementState(
-    Buffer buffer, std::size_t length, const CHAR *format,
+    Buffer buffer, std::size_t length, const CharType *format,
     std::size_t formatLength, const char *sourceFile, int sourceLine)
-    : InternalIoStatementState<DIR, CHAR>{buffer, length, sourceFile,
-          sourceLine},
+    : InternalIoStatementState<DIR>{buffer, length, sourceFile, sourceLine},
       ioStatementState_{*this}, format_{*this, format, formatLength} {}
 
 template <Direction DIR, typename CHAR>
 InternalFormattedIoStatementState<DIR, CHAR>::InternalFormattedIoStatementState(
-    const Descriptor &d, const CHAR *format, std::size_t formatLength,
+    const Descriptor &d, const CharType *format, std::size_t formatLength,
     const char *sourceFile, int sourceLine)
-    : InternalIoStatementState<DIR, CHAR>{d, sourceFile, sourceLine},
+    : InternalIoStatementState<DIR>{d, sourceFile, sourceLine},
       ioStatementState_{*this}, format_{*this, format, formatLength} {}
 
 template <Direction DIR, typename CHAR>
@@ -175,20 +164,19 @@ void InternalFormattedIoStatementState<DIR, CHAR>::CompleteOperation() {
 template <Direction DIR, typename CHAR>
 int InternalFormattedIoStatementState<DIR, CHAR>::EndIoStatement() {
   CompleteOperation();
-  return InternalIoStatementState<DIR, CHAR>::EndIoStatement();
+  return InternalIoStatementState<DIR>::EndIoStatement();
 }
 
-template <Direction DIR, typename CHAR>
-InternalListIoStatementState<DIR, CHAR>::InternalListIoStatementState(
+template <Direction DIR>
+InternalListIoStatementState<DIR>::InternalListIoStatementState(
     Buffer buffer, std::size_t length, const char *sourceFile, int sourceLine)
-    : InternalIoStatementState<DIR, CharType>{buffer, length, sourceFile,
-          sourceLine},
+    : InternalIoStatementState<DIR>{buffer, length, sourceFile, sourceLine},
       ioStatementState_{*this} {}
 
-template <Direction DIR, typename CHAR>
-InternalListIoStatementState<DIR, CHAR>::InternalListIoStatementState(
+template <Direction DIR>
+InternalListIoStatementState<DIR>::InternalListIoStatementState(
     const Descriptor &d, const char *sourceFile, int sourceLine)
-    : InternalIoStatementState<DIR, CharType>{d, sourceFile, sourceLine},
+    : InternalIoStatementState<DIR>{d, sourceFile, sourceLine},
       ioStatementState_{*this} {}
 
 ExternalIoStatementBase::ExternalIoStatementBase(
@@ -354,36 +342,6 @@ bool ExternalIoStatementState<DIR>::Emit(
   return unit().Emit(data, bytes, elementBytes, *this);
 }
 
-template <Direction DIR>
-bool ExternalIoStatementState<DIR>::Emit(const char *data, std::size_t bytes) {
-  if constexpr (DIR == Direction::Input) {
-    Crash("ExternalIoStatementState::Emit(char) called for input statement");
-  }
-  return unit().Emit(data, bytes, 0, *this);
-}
-
-template <Direction DIR>
-bool ExternalIoStatementState<DIR>::Emit(
-    const char16_t *data, std::size_t chars) {
-  if constexpr (DIR == Direction::Input) {
-    Crash(
-        "ExternalIoStatementState::Emit(char16_t) called for input statement");
-  }
-  return unit().Emit(reinterpret_cast<const char *>(data), chars * sizeof *data,
-      sizeof *data, *this);
-}
-
-template <Direction DIR>
-bool ExternalIoStatementState<DIR>::Emit(
-    const char32_t *data, std::size_t chars) {
-  if constexpr (DIR == Direction::Input) {
-    Crash(
-        "ExternalIoStatementState::Emit(char32_t) called for input statement");
-  }
-  return unit().Emit(reinterpret_cast<const char *>(data), chars * sizeof *data,
-      sizeof *data, *this);
-}
-
 template <Direction DIR>
 std::size_t ExternalIoStatementState<DIR>::GetNextInputBytes(const char *&p) {
   return unit().GetNextInputBytes(p, *this);
@@ -465,45 +423,9 @@ std::optional<DataEdit> IoStatementState::GetNextDataEdit(int n) {
 }
 
 bool IoStatementState::Emit(
-    const char *data, std::size_t n, std::size_t elementBytes) {
+    const char *data, std::size_t bytes, std::size_t elementBytes) {
   return common::visit(
-      [=](auto &x) { return x.get().Emit(data, n, elementBytes); }, u_);
-}
-
-bool IoStatementState::Emit(const char *data, std::size_t n) {
-  return common::visit([=](auto &x) { return x.get().Emit(data, n); }, u_);
-}
-
-bool IoStatementState::Emit(const char16_t *data, std::size_t chars) {
-  return common::visit([=](auto &x) { return x.get().Emit(data, chars); }, u_);
-}
-
-bool IoStatementState::Emit(const char32_t *data, std::size_t chars) {
-  return common::visit([=](auto &x) { return x.get().Emit(data, chars); }, u_);
-}
-
-template <typename CHAR>
-bool IoStatementState::EmitEncoded(const CHAR *data0, std::size_t chars) {
-  // Don't allow sign extension
-  using UnsignedChar = std::make_unsigned_t<CHAR>;
-  const UnsignedChar *data{reinterpret_cast<const UnsignedChar *>(data0)};
-  if (GetConnectionState().useUTF8<CHAR>()) {
-    char buffer[256];
-    std::size_t at{0};
-    while (chars-- > 0) {
-      auto len{EncodeUTF8(buffer + at, *data++)};
-      at += len;
-      if (at + maxUTF8Bytes > sizeof buffer) {
-        if (!Emit(buffer, at)) {
-          return false;
-        }
-        at = 0;
-      }
-    }
-    return at == 0 || Emit(buffer, at);
-  } else {
-    return Emit(data0, chars);
-  }
+      [=](auto &x) { return x.get().Emit(data, bytes, elementBytes); }, u_);
 }
 
 bool IoStatementState::Receive(
@@ -534,11 +456,12 @@ void IoStatementState::HandleAbsolutePosition(std::int64_t n) {
 }
 
 void IoStatementState::CompleteOperation() {
-  common::visit([](auto &x) { x.get().CompleteOperation(); }, u_);
+  common::visit([this](auto &x) { x.get().CompleteOperation(); }, u_);
 }
 
 int IoStatementState::EndIoStatement() {
-  return common::visit([](auto &x) { return x.get().EndIoStatement(); }, u_);
+  return common::visit(
+      [this](auto &x) { return x.get().EndIoStatement(); }, u_);
 }
 
 ConnectionState &IoStatementState::GetConnectionState() {
@@ -578,7 +501,8 @@ std::optional<char32_t> IoStatementState::GetCurrentChar(
     byteCount = 0;
     return std::nullopt;
   } else {
-    if (GetConnectionState().isUTF8) {
+    const ConnectionState &connection{GetConnectionState()};
+    if (connection.isUTF8) {
       std::size_t length{MeasureUTF8Bytes(*p)};
       if (length <= bytes) {
         if (auto result{DecodeUTF8(p)}) {
@@ -588,38 +512,19 @@ std::optional<char32_t> IoStatementState::GetCurrentChar(
       }
       GetIoErrorHandler().SignalError(IostatUTF8Decoding);
       // Error recovery: return the next byte
+    } else if (connection.internalIoCharKind > 1) {
+      byteCount = connection.internalIoCharKind;
+      if (byteCount == 2) {
+        return *reinterpret_cast<const char16_t *>(p);
+      } else {
+        return *reinterpret_cast<const char32_t *>(p);
+      }
     }
     byteCount = 1;
     return *p;
   }
 }
 
-bool IoStatementState::EmitRepeated(char ch, std::size_t n) {
-  return common::visit(
-      [=](auto &x) {
-        for (std::size_t j{0}; j < n; ++j) {
-          if (!x.get().Emit(&ch, 1)) {
-            return false;
-          }
-        }
-        return true;
-      },
-      u_);
-}
-
-bool IoStatementState::EmitField(
-    const char *p, std::size_t length, std::size_t width) {
-  if (width <= 0) {
-    width = static_cast<int>(length);
-  }
-  if (length > static_cast<std::size_t>(width)) {
-    return EmitRepeated('*', width);
-  } else {
-    return EmitRepeated(' ', static_cast<int>(width - length)) &&
-        Emit(p, length);
-  }
-}
-
 std::optional<char32_t> IoStatementState::NextInField(
     std::optional<int> &remaining, const DataEdit &edit) {
   std::size_t byteCount{0};
@@ -755,7 +660,7 @@ bool ListDirectedStatementState<Direction::Output>::EmitLeadingSpaceOrAdvance(
     return io.AdvanceRecord();
   }
   if (space) {
-    return io.Emit(" ", 1);
+    return EmitAscii(io, " ", 1);
   }
   return true;
 }
@@ -928,21 +833,6 @@ bool ChildIoStatementState<DIR>::Emit(
   return child_.parent().Emit(data, bytes, elementBytes);
 }
 
-template <Direction DIR>
-bool ChildIoStatementState<DIR>::Emit(const char *data, std::size_t bytes) {
-  return child_.parent().Emit(data, bytes);
-}
-
-template <Direction DIR>
-bool ChildIoStatementState<DIR>::Emit(const char16_t *data, std::size_t chars) {
-  return child_.parent().Emit(data, chars);
-}
-
-template <Direction DIR>
-bool ChildIoStatementState<DIR>::Emit(const char32_t *data, std::size_t chars) {
-  return child_.parent().Emit(data, chars);
-}
-
 template <Direction DIR>
 std::size_t ChildIoStatementState<DIR>::GetNextInputBytes(const char *&p) {
   return child_.parent().GetNextInputBytes(p);
@@ -1509,23 +1399,8 @@ InquireIOLengthState::InquireIOLengthState(
     const char *sourceFile, int sourceLine)
     : NoUnitIoStatementState{*this, sourceFile, sourceLine} {}
 
-bool InquireIOLengthState::Emit(const char *, std::size_t n, std::size_t) {
-  bytes_ += n;
-  return true;
-}
-
-bool InquireIOLengthState::Emit(const char *p, std::size_t n) {
-  bytes_ += sizeof *p * n;
-  return true;
-}
-
-bool InquireIOLengthState::Emit(const char16_t *p, std::size_t n) {
-  bytes_ += sizeof *p * n;
-  return true;
-}
-
-bool InquireIOLengthState::Emit(const char32_t *p, std::size_t n) {
-  bytes_ += sizeof *p * n;
+bool InquireIOLengthState::Emit(const char *, std::size_t bytes, std::size_t) {
+  bytes_ += bytes;
   return true;
 }
 
@@ -1537,10 +1412,4 @@ int ErroneousIoStatementState::EndIoStatement() {
   return IoStatementBase::EndIoStatement();
 }
 
-template bool IoStatementState::EmitEncoded<char>(const char *, std::size_t);
-template bool IoStatementState::EmitEncoded<char16_t>(
-    const char16_t *, std::size_t);
-template bool IoStatementState::EmitEncoded<char32_t>(
-    const char32_t *, std::size_t);
-
 } // namespace Fortran::runtime::io

diff  --git a/flang/runtime/io-stmt.h b/flang/runtime/io-stmt.h
index d343abe61140e..a4f9d633d288c 100644
--- a/flang/runtime/io-stmt.h
+++ b/flang/runtime/io-stmt.h
@@ -40,7 +40,7 @@ class ErroneousIoStatementState;
 
 template <Direction, typename CHAR = char>
 class InternalFormattedIoStatementState;
-template <Direction, typename CHAR = char> class InternalListIoStatementState;
+template <Direction> class InternalListIoStatementState;
 template <Direction, typename CHAR = char>
 class ExternalFormattedIoStatementState;
 template <Direction> class ExternalListIoStatementState;
@@ -87,11 +87,7 @@ class IoStatementState {
   // Completes an I/O statement and reclaims storage.
   int EndIoStatement();
 
-  bool Emit(const char *, std::size_t, std::size_t elementBytes);
-  bool Emit(const char *, std::size_t);
-  bool Emit(const char16_t *, std::size_t chars);
-  bool Emit(const char32_t *, std::size_t chars);
-  template <typename CHAR> bool EmitEncoded(const CHAR *, std::size_t);
+  bool Emit(const char *, std::size_t bytes, std::size_t elementBytes = 0);
   bool Receive(char *, std::size_t, std::size_t elementBytes = 0);
   std::size_t GetNextInputBytes(const char *&);
   bool AdvanceRecord(int = 1);
@@ -127,15 +123,10 @@ class IoStatementState {
   // Vacant after the end of the current record
   std::optional<char32_t> GetCurrentChar(std::size_t &byteCount);
 
-  bool EmitRepeated(char, std::size_t);
-  bool EmitField(const char *, std::size_t length, std::size_t width);
-
-  // For fixed-width fields, initialize the number of remaining characters.
-  // Skip over leading blanks, then return the first non-blank character (if
-  // any).
-  std::optional<char32_t> PrepareInput(
-      const DataEdit &edit, std::optional<int> &remaining) {
-    remaining.reset();
+  // For fixed-width fields, return the number of remaining characters.
+  // Skip over leading blanks.
+  std::optional<int> CueUpInput(const DataEdit &edit) {
+    std::optional<int> remaining;
     if (edit.IsListDirected()) {
       std::size_t byteCount{0};
       GetNextNonBlank(byteCount);
@@ -145,7 +136,7 @@ class IoStatementState {
       }
       SkipSpaces(remaining);
     }
-    return NextInField(remaining, edit);
+    return remaining;
   }
 
   std::optional<char32_t> SkipSpaces(std::optional<int> &remaining) {
@@ -255,11 +246,8 @@ class IoStatementBase : public IoErrorHandler {
   int EndIoStatement() { return GetIoStat(); }
 
   // These are default no-op backstops that can be overridden by descendants.
-  bool Emit(const char *, std::size_t, std::size_t elementBytes);
-  bool Emit(const char *, std::size_t);
-  bool Emit(const char16_t *, std::size_t chars);
-  bool Emit(const char32_t *, std::size_t chars);
-  bool Receive(char *, std::size_t, std::size_t elementBytes = 0);
+  bool Emit(const char *, std::size_t bytes, std::size_t elementBytes = 0);
+  bool Receive(char *, std::size_t bytes, std::size_t elementBytes = 0);
   std::size_t GetNextInputBytes(const char *&);
   bool AdvanceRecord(int);
   void BackspaceRecord();
@@ -330,22 +318,19 @@ class ListDirectedStatementState<Direction::Input>
   bool imaginaryPart_{false};
 };
 
-template <Direction DIR, typename CHAR = char>
+template <Direction DIR>
 class InternalIoStatementState : public IoStatementBase,
                                  public IoDirectionState<DIR> {
 public:
-  using CharType = CHAR;
   using Buffer =
-      std::conditional_t<DIR == Direction::Input, const CharType *, CharType *>;
+      std::conditional_t<DIR == Direction::Input, const char *, char *>;
   InternalIoStatementState(Buffer, std::size_t,
       const char *sourceFile = nullptr, int sourceLine = 0);
   InternalIoStatementState(
       const Descriptor &, const char *sourceFile = nullptr, int sourceLine = 0);
   int EndIoStatement();
 
-  using IoStatementBase::Emit;
-  bool Emit(
-      const CharType *data, std::size_t chars /* not necessarily bytes */);
+  bool Emit(const char *data, std::size_t bytes, std::size_t elementBytes = 0);
   std::size_t GetNextInputBytes(const char *&);
   bool AdvanceRecord(int = 1);
   void BackspaceRecord();
@@ -361,11 +346,11 @@ class InternalIoStatementState : public IoStatementBase,
 
 template <Direction DIR, typename CHAR>
 class InternalFormattedIoStatementState
-    : public InternalIoStatementState<DIR, CHAR>,
+    : public InternalIoStatementState<DIR>,
       public FormattedIoStatementState<DIR> {
 public:
   using CharType = CHAR;
-  using typename InternalIoStatementState<DIR, CharType>::Buffer;
+  using typename InternalIoStatementState<DIR>::Buffer;
   InternalFormattedIoStatementState(Buffer internal, std::size_t internalLength,
       const CharType *format, std::size_t formatLength,
       const char *sourceFile = nullptr, int sourceLine = 0);
@@ -382,17 +367,16 @@ class InternalFormattedIoStatementState
 
 private:
   IoStatementState ioStatementState_; // points to *this
-  using InternalIoStatementState<DIR, CharType>::unit_;
+  using InternalIoStatementState<DIR>::unit_;
   // format_ *must* be last; it may be partial someday
   FormatControl<InternalFormattedIoStatementState> format_;
 };
 
-template <Direction DIR, typename CHAR>
-class InternalListIoStatementState : public InternalIoStatementState<DIR, CHAR>,
+template <Direction DIR>
+class InternalListIoStatementState : public InternalIoStatementState<DIR>,
                                      public ListDirectedStatementState<DIR> {
 public:
-  using CharType = CHAR;
-  using typename InternalIoStatementState<DIR, CharType>::Buffer;
+  using typename InternalIoStatementState<DIR>::Buffer;
   InternalListIoStatementState(Buffer internal, std::size_t internalLength,
       const char *sourceFile = nullptr, int sourceLine = 0);
   InternalListIoStatementState(
@@ -402,7 +386,7 @@ class InternalListIoStatementState : public InternalIoStatementState<DIR, CHAR>,
 
 private:
   IoStatementState ioStatementState_; // points to *this
-  using InternalIoStatementState<DIR, CharType>::unit_;
+  using InternalIoStatementState<DIR>::unit_;
 };
 
 class ExternalIoStatementBase : public IoStatementBase {
@@ -431,10 +415,7 @@ class ExternalIoStatementState : public ExternalIoStatementBase,
   MutableModes &mutableModes() { return mutableModes_; }
   void CompleteOperation();
   int EndIoStatement();
-  bool Emit(const char *, std::size_t, std::size_t elementBytes);
-  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 */);
+  bool Emit(const char *, std::size_t bytes, std::size_t elementBytes = 0);
   std::size_t GetNextInputBytes(const char *&);
   bool AdvanceRecord(int = 1);
   void BackspaceRecord();
@@ -498,10 +479,7 @@ class ChildIoStatementState : public IoStatementBase,
   ExternalFileUnit *GetExternalFileUnit() const;
   void CompleteOperation();
   int EndIoStatement();
-  bool Emit(const char *, std::size_t, std::size_t elementBytes);
-  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 */);
+  bool Emit(const char *, std::size_t bytes, std::size_t elementBytes = 0);
   std::size_t GetNextInputBytes(const char *&);
   void HandleRelativePosition(std::int64_t);
   void HandleAbsolutePosition(std::int64_t);
@@ -696,10 +674,7 @@ class InquireIOLengthState : public NoUnitIoStatementState,
 public:
   InquireIOLengthState(const char *sourceFile = nullptr, int sourceLine = 0);
   std::size_t bytes() const { return bytes_; }
-  bool Emit(const char *, std::size_t, std::size_t elementBytes);
-  bool Emit(const char *, std::size_t);
-  bool Emit(const char16_t *, std::size_t chars);
-  bool Emit(const char32_t *, std::size_t chars);
+  bool Emit(const char *, std::size_t bytes, std::size_t elementBytes = 0);
 
 private:
   std::size_t bytes_{0};
@@ -735,12 +710,5 @@ class ErroneousIoStatementState : public IoStatementBase {
   ExternalFileUnit *unit_{nullptr};
 };
 
-extern template bool IoStatementState::EmitEncoded<char>(
-    const char *, std::size_t);
-extern template bool IoStatementState::EmitEncoded<char16_t>(
-    const char16_t *, std::size_t);
-extern template bool IoStatementState::EmitEncoded<char32_t>(
-    const char32_t *, std::size_t);
-
 } // namespace Fortran::runtime::io
 #endif // FORTRAN_RUNTIME_IO_STMT_H_

diff  --git a/flang/runtime/namelist.cpp b/flang/runtime/namelist.cpp
index 3e2c7a012bada..c206c029086cb 100644
--- a/flang/runtime/namelist.cpp
+++ b/flang/runtime/namelist.cpp
@@ -8,6 +8,7 @@
 
 #include "namelist.h"
 #include "descriptor-io.h"
+#include "emit-encoded.h"
 #include "io-stmt.h"
 #include "flang/Runtime/io-api.h"
 #include <algorithm>
@@ -34,17 +35,17 @@ bool IONAME(OutputNamelist)(Cookie cookie, const NamelistGroup &group) {
   // Internal functions to advance records and convert case
   const auto EmitWithAdvance{[&](char ch) -> bool {
     return (!connection.NeedAdvance(1) || io.AdvanceRecord()) &&
-        io.Emit(&ch, 1);
+        EmitAscii(io, &ch, 1);
   }};
   const auto EmitUpperCase{[&](const char *str) -> bool {
     if (connection.NeedAdvance(std::strlen(str)) &&
-        !(io.AdvanceRecord() && io.Emit(" ", 1))) {
+        !(io.AdvanceRecord() && EmitAscii(io, " ", 1))) {
       return false;
     }
     for (; *str; ++str) {
       char up{*str >= 'a' && *str <= 'z' ? static_cast<char>(*str - 'a' + 'A')
                                          : *str};
-      if (!io.Emit(&up, 1)) {
+      if (!EmitAscii(io, &up, 1)) {
         return false;
       }
     }
@@ -141,7 +142,6 @@ static std::optional<SubscriptValue> GetSubscriptValue(IoStatementState &io) {
 static bool HandleSubscripts(IoStatementState &io, Descriptor &desc,
     const Descriptor &source, const char *name) {
   IoErrorHandler &handler{io.GetIoErrorHandler()};
-  io.HandleRelativePosition(1); // skip '('
   // Allow for blanks in subscripts; they're nonstandard, but not
   // ambiguous within the parentheses.
   SubscriptValue lower[maxRank], upper[maxRank], stride[maxRank];
@@ -251,7 +251,6 @@ static bool HandleSubstring(
   SubscriptValue chars{static_cast<SubscriptValue>(desc.ElementBytes()) / kind};
   // Allow for blanks in substring bounds; they're nonstandard, but not
   // ambiguous within the parentheses.
-  io.HandleRelativePosition(1); // skip '('
   std::optional<SubscriptValue> lower, upper;
   std::size_t byteCount{0};
   std::optional<char32_t> ch{io.GetNextNonBlank(byteCount)};
@@ -304,7 +303,6 @@ static bool HandleSubstring(
 static bool HandleComponent(IoStatementState &io, Descriptor &desc,
     const Descriptor &source, const char *name) {
   IoErrorHandler &handler{io.GetIoErrorHandler()};
-  io.HandleRelativePosition(1); // skip '%'
   char compName[nameBufferSize];
   if (GetLowerCaseName(io, compName, sizeof compName)) {
     const DescriptorAddendum *addendum{source.Addendum()};
@@ -436,6 +434,7 @@ bool IONAME(InputNamelist)(Cookie cookie, const NamelistGroup &group) {
       do {
         Descriptor &mutableDescriptor{staticDesc[whichStaticDesc].descriptor()};
         whichStaticDesc ^= 1;
+        io.HandleRelativePosition(byteCount); // skip over '(' or '%'
         if (*next == '(') {
           if (!hadSubstring && (hadSubscripts || useDescriptor->rank() == 0)) {
             mutableDescriptor = *useDescriptor;
@@ -449,9 +448,11 @@ bool IONAME(InputNamelist)(Cookie cookie, const NamelistGroup &group) {
                                 "NAMELIST group '%s'",
                 name, group.groupName);
             return false;
-          } else if (!HandleSubscripts(
-                         io, mutableDescriptor, *useDescriptor, name)) {
-            return false;
+          } else {
+            if (!HandleSubscripts(
+                    io, mutableDescriptor, *useDescriptor, name)) {
+              return false;
+            }
           }
           hadSubscripts = true;
         } else {
@@ -488,7 +489,7 @@ bool IONAME(InputNamelist)(Cookie cookie, const NamelistGroup &group) {
         "No '/' found after NAMELIST group '%s'", group.groupName);
     return false;
   }
-  io.HandleRelativePosition(1);
+  io.HandleRelativePosition(byteCount);
   return true;
 }
 

diff  --git a/flang/test/Semantics/io03.f90 b/flang/test/Semantics/io03.f90
index 70f25ad524e8a..45255d6415c31 100644
--- a/flang/test/Semantics/io03.f90
+++ b/flang/test/Semantics/io03.f90
@@ -55,10 +55,7 @@
       decimal='comma', end=9, eor=9, err=9, id=id, iomsg=msg, iostat=stat2, &
       pad='no', round='processor_defined', size=kk) jj
 
-  !ERROR: Invalid character kind for an internal file variable
   read(internal_file2, *) jj
-
-  !ERROR: Invalid character kind for an internal file variable
   read(internal_file4, *) jj
 
   !ERROR: Internal file must not have a vector subscript

diff  --git a/flang/unittests/Runtime/Format.cpp b/flang/unittests/Runtime/Format.cpp
index 963820d40efc2..970373043131a 100644
--- a/flang/unittests/Runtime/Format.cpp
+++ b/flang/unittests/Runtime/Format.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "CrashHandlerFixture.h"
+#include "../runtime/connection.h"
 #include "../runtime/format-implementation.h"
 #include "../runtime/io-error.h"
 #include <string>
@@ -24,33 +25,25 @@ class TestFormatContext : public IoErrorHandler {
 public:
   using CharType = char;
   TestFormatContext() : IoErrorHandler{"format.cpp", 1} {}
-  bool Emit(const char *, std::size_t);
-  bool Emit(const char16_t *, std::size_t);
-  bool Emit(const char32_t *, std::size_t);
+  bool Emit(const char *, std::size_t, std::size_t = 0);
   bool AdvanceRecord(int = 1);
   void HandleRelativePosition(std::int64_t);
   void HandleAbsolutePosition(std::int64_t);
   void Report(const DataEdit &);
   ResultsTy results;
   MutableModes &mutableModes() { return mutableModes_; }
+  ConnectionState &GetConnectionState() { return connectionState_; }
 
 private:
   MutableModes mutableModes_;
+  ConnectionState connectionState_;
 };
 
-bool TestFormatContext::Emit(const char *s, std::size_t len) {
+bool TestFormatContext::Emit(const char *s, std::size_t len, std::size_t) {
   std::string str{s, len};
   results.push_back("'"s + str + '\'');
   return true;
 }
-bool TestFormatContext::Emit(const char16_t *, std::size_t) {
-  Crash("TestFormatContext::Emit(const char16_t *) called");
-  return false;
-}
-bool TestFormatContext::Emit(const char32_t *, std::size_t) {
-  Crash("TestFormatContext::Emit(const char32_t *) called");
-  return false;
-}
 
 bool TestFormatContext::AdvanceRecord(int n) {
   while (n-- > 0) {


        


More information about the flang-commits mailing list