//===-- lib/runtime/descriptor-io.cpp ---------------------------*- 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
//
//===----------------------------------------------------------------------===//

#include "descriptor-io.h"
#include "edit-input.h"
#include "edit-output.h"
#include "unit.h"
#include "flang-rt/runtime/descriptor.h"
#include "flang-rt/runtime/io-stmt.h"
#include "flang-rt/runtime/namelist.h"
#include "flang-rt/runtime/terminator.h"
#include "flang-rt/runtime/type-info.h"
#include "flang-rt/runtime/work-queue.h"
#include "flang/Common/optional.h"
#include "flang/Common/restorer.h"
#include "flang/Common/uint128.h"
#include "flang/Runtime/cpp-type.h"
#include "flang/Runtime/freestanding-tools.h"

// Implementation of I/O data list item transfers based on descriptors.
// (All I/O items come through here so that the code is exercised for test;
// some scalar I/O data transfer APIs could be changed to bypass their use
// of descriptors in the future for better efficiency.)

namespace Fortran::runtime::io::descr {
RT_OFFLOAD_API_GROUP_BEGIN

template <typename A>
inline RT_API_ATTRS A &ExtractElement(IoStatementState &io,
    const Descriptor &descriptor, const SubscriptValue subscripts[]) {
  A *p{descriptor.Element<A>(subscripts)};
  if (!p) {
    io.GetIoErrorHandler().Crash("Bad address for I/O item -- null base "
                                 "address or subscripts out of range");
  }
  return *p;
}

// Defined formatted I/O (maybe)
static RT_API_ATTRS Fortran::common::optional<bool> DefinedFormattedIo(
    IoStatementState &io, const Descriptor &descriptor,
    const typeInfo::DerivedType &derived,
    const typeInfo::SpecialBinding &special,
    const SubscriptValue subscripts[]) {
  // Look at the next data edit descriptor.  If this is list-directed I/O, the
  // "maxRepeat=0" argument will prevent the input from advancing over an
  // initial '(' that shouldn't be consumed now as the start of a real part.
  Fortran::common::optional<DataEdit> peek{io.GetNextDataEdit(/*maxRepeat=*/0)};
  if (peek &&
      (peek->descriptor == DataEdit::DefinedDerivedType ||
          peek->descriptor == DataEdit::ListDirected ||
          peek->descriptor == DataEdit::ListDirectedRealPart)) {
    // Defined formatting
    IoErrorHandler &handler{io.GetIoErrorHandler()};
    DataEdit edit{peek->descriptor == DataEdit::ListDirectedRealPart
            ? *peek
            : *io.GetNextDataEdit(1)};
    char ioType[2 + edit.maxIoTypeChars];
    auto ioTypeLen{std::size_t{2} /*"DT"*/ + edit.ioTypeChars};
    if (edit.descriptor == DataEdit::DefinedDerivedType) {
      ioType[0] = 'D';
      ioType[1] = 'T';
      std::memcpy(ioType + 2, edit.ioType, edit.ioTypeChars);
    } else {
      runtime::strcpy(
          ioType, io.mutableModes().inNamelist ? "NAMELIST" : "LISTDIRECTED");
      ioTypeLen = runtime::strlen(ioType);
    }
    // V_LIST= argument
    StaticDescriptor<1, true> vListStatDesc;
    Descriptor &vListDesc{vListStatDesc.descriptor()};
    bool integer8{special.specialCaseFlag()};
    std::int64_t vList64[edit.maxVListEntries];
    if (integer8) {
      // Convert v_list values to INTEGER(8)
      for (int j{0}; j < edit.vListEntries; ++j) {
        vList64[j] = edit.vList[j];
      }
      vListDesc.Establish(
          TypeCategory::Integer, sizeof(std::int64_t), nullptr, 1);
      vListDesc.set_base_addr(vList64);
      vListDesc.GetDimension(0).SetBounds(1, edit.vListEntries);
      vListDesc.GetDimension(0).SetByteStride(
          static_cast<SubscriptValue>(sizeof(std::int64_t)));
    } else {
      vListDesc.Establish(TypeCategory::Integer, sizeof(int), nullptr, 1);
      vListDesc.set_base_addr(edit.vList);
      vListDesc.GetDimension(0).SetBounds(1, edit.vListEntries);
      vListDesc.GetDimension(0).SetByteStride(
          static_cast<SubscriptValue>(sizeof(int)));
    }
    ExternalFileUnit *actualExternal{io.GetExternalFileUnit()};
    ExternalFileUnit *external{actualExternal};
    if (!external) {
      // Create a new unit to service defined I/O for an
      // internal I/O parent.
      external = &ExternalFileUnit::NewUnit(handler, true);
    }
    ChildIo &child{external->PushChildIo(io)};
    // Child formatted I/O is nonadvancing by definition (F'2018 12.6.2.4).
    auto restorer{common::ScopedSet(io.mutableModes().nonAdvancing, true)};
    std::int32_t unit{external->unitNumber()};
    std::int32_t ioStat{IostatOk};
    char ioMsg[100];
    Fortran::common::optional<std::int64_t> startPos;
    if (edit.descriptor == DataEdit::DefinedDerivedType &&
        special.which() == typeInfo::SpecialBinding::Which::ReadFormatted) {
      // DT is an edit descriptor so everything that the child
      // I/O subroutine reads counts towards READ(SIZE=).
      startPos = io.InquirePos();
    }
    const auto *bindings{
        derived.binding().OffsetElement<const typeInfo::Binding>()};
    if (special.IsArgDescriptor(0)) {
      // "dtv" argument is "class(t)", pass a descriptor
      StaticDescriptor<1, true, 10 /*?*/> elementStatDesc;
      Descriptor &elementDesc{elementStatDesc.descriptor()};
      elementDesc.Establish(
          derived, nullptr, 0, nullptr, CFI_attribute_pointer);
      elementDesc.set_base_addr(descriptor.Element<char>(subscripts));
      if (integer8) { // 64-bit UNIT=/IOSTAT=
        std::int64_t unit64{unit};
        std::int64_t ioStat64{ioStat};
        auto *p{special.GetProc<void (*)(const Descriptor &, std::int64_t &,
            char *, const Descriptor &, std::int64_t &, char *, std::size_t,
            std::size_t)>(bindings)};
        p(elementDesc, unit64, ioType, vListDesc, ioStat64, ioMsg, ioTypeLen,
            sizeof ioMsg);
        ioStat = ioStat64;
      } else { // 32-bit UNIT=/IOSTAT=
        auto *p{special.GetProc<void (*)(const Descriptor &, std::int32_t &,
            char *, const Descriptor &, std::int32_t &, char *, std::size_t,
            std::size_t)>(bindings)};
        p(elementDesc, unit, ioType, vListDesc, ioStat, ioMsg, ioTypeLen,
            sizeof ioMsg);
      }
    } else {
      // "dtv" argument is "type(t)", pass a raw pointer
      if (integer8) { // 64-bit UNIT= and IOSTAT=
        std::int64_t unit64{unit};
        std::int64_t ioStat64{ioStat};
        auto *p{special.GetProc<void (*)(const void *, std::int64_t &, char *,
            const Descriptor &, std::int64_t &, char *, std::size_t,
            std::size_t)>(bindings)};
        p(descriptor.Element<char>(subscripts), unit64, ioType, vListDesc,
            ioStat64, ioMsg, ioTypeLen, sizeof ioMsg);
        ioStat = ioStat64;
      } else { // 32-bit UNIT= and IOSTAT=
        auto *p{special.GetProc<void (*)(const void *, std::int32_t &, char *,
            const Descriptor &, std::int32_t &, char *, std::size_t,
            std::size_t)>(bindings)};
        p(descriptor.Element<char>(subscripts), unit, ioType, vListDesc, ioStat,
            ioMsg, ioTypeLen, sizeof ioMsg);
      }
    }
    handler.Forward(ioStat, ioMsg, sizeof ioMsg);
    external->PopChildIo(child);
    if (!actualExternal) {
      // Close unit created for internal I/O above.
      auto *closing{external->LookUpForClose(external->unitNumber())};
      RUNTIME_CHECK(handler, external == closing);
      external->DestroyClosed();
    }
    if (startPos) {
      io.GotChar(io.InquirePos() - *startPos);
    }
    return handler.GetIoStat() == IostatOk;
  } else {
    // There's a defined I/O subroutine, but there's a FORMAT present and
    // it does not have a DT data edit descriptor, so apply default formatting
    // to the components of the derived type as usual.
    return Fortran::common::nullopt;
  }
}

// Defined unformatted I/O
static RT_API_ATTRS bool DefinedUnformattedIo(IoStatementState &io,
    const Descriptor &descriptor, const typeInfo::DerivedType &derived,
    const typeInfo::SpecialBinding &special) {
  // Unformatted I/O must have an external unit (or child thereof).
  IoErrorHandler &handler{io.GetIoErrorHandler()};
  ExternalFileUnit *external{io.GetExternalFileUnit()};
  if (!external) { // INQUIRE(IOLENGTH=)
    handler.SignalError(IostatNonExternalDefinedUnformattedIo);
    return false;
  }
  ChildIo &child{external->PushChildIo(io)};
  int unit{external->unitNumber()};
  int ioStat{IostatOk};
  char ioMsg[100];
  std::size_t numElements{descriptor.Elements()};
  SubscriptValue subscripts[maxRank];
  descriptor.GetLowerBounds(subscripts);
  const auto *bindings{
      derived.binding().OffsetElement<const typeInfo::Binding>()};
  if (special.IsArgDescriptor(0)) {
    // "dtv" argument is "class(t)", pass a descriptor
    auto *p{special.GetProc<void (*)(
        const Descriptor &, int &, int &, char *, std::size_t)>(bindings)};
    StaticDescriptor<1, true, 10 /*?*/> elementStatDesc;
    Descriptor &elementDesc{elementStatDesc.descriptor()};
    elementDesc.Establish(derived, nullptr, 0, nullptr, CFI_attribute_pointer);
    for (; numElements-- > 0; descriptor.IncrementSubscripts(subscripts)) {
      elementDesc.set_base_addr(descriptor.Element<char>(subscripts));
      p(elementDesc, unit, ioStat, ioMsg, sizeof ioMsg);
      if (ioStat != IostatOk) {
        break;
      }
    }
  } else {
    // "dtv" argument is "type(t)", pass a raw pointer
    auto *p{special
            .GetProc<void (*)(const void *, int &, int &, char *, std::size_t)>(
                bindings)};
    for (; numElements-- > 0; descriptor.IncrementSubscripts(subscripts)) {
      p(descriptor.Element<char>(subscripts), unit, ioStat, ioMsg,
          sizeof ioMsg);
      if (ioStat != IostatOk) {
        break;
      }
    }
  }
  handler.Forward(ioStat, ioMsg, sizeof ioMsg);
  external->PopChildIo(child);
  return handler.GetIoStat() == IostatOk;
}

// Per-category descriptor-based I/O templates

// TODO (perhaps as a nontrivial but small starter project): implement
// automatic repetition counts, like "10*3.14159", for list-directed and
// NAMELIST array output.

template <int KIND, Direction DIR>
inline RT_API_ATTRS bool FormattedIntegerIO(IoStatementState &io,
    const Descriptor &descriptor, [[maybe_unused]] bool isSigned) {
  std::size_t numElements{descriptor.Elements()};
  SubscriptValue subscripts[maxRank];
  descriptor.GetLowerBounds(subscripts);
  using IntType = CppTypeFor<common::TypeCategory::Integer, KIND>;
  bool anyInput{false};
  for (std::size_t j{0}; j < numElements; ++j) {
    if (auto edit{io.GetNextDataEdit()}) {
      IntType &x{ExtractElement<IntType>(io, descriptor, subscripts)};
      if constexpr (DIR == Direction::Output) {
        if (!EditIntegerOutput<KIND>(io, *edit, x, isSigned)) {
          return false;
        }
      } else if (edit->descriptor != DataEdit::ListDirectedNullValue) {
        if (EditIntegerInput(
                io, *edit, reinterpret_cast<void *>(&x), KIND, isSigned)) {
          anyInput = true;
        } else {
          return anyInput && edit->IsNamelist();
        }
      }
      if (!descriptor.IncrementSubscripts(subscripts) && j + 1 < numElements) {
        io.GetIoErrorHandler().Crash(
            "FormattedIntegerIO: subscripts out of bounds");
      }
    } else {
      return false;
    }
  }
  return true;
}

template <int KIND, Direction DIR>
inline RT_API_ATTRS bool FormattedRealIO(
    IoStatementState &io, const Descriptor &descriptor) {
  std::size_t numElements{descriptor.Elements()};
  SubscriptValue subscripts[maxRank];
  descriptor.GetLowerBounds(subscripts);
  using RawType = typename RealOutputEditing<KIND>::BinaryFloatingPoint;
  bool anyInput{false};
  for (std::size_t j{0}; j < numElements; ++j) {
    if (auto edit{io.GetNextDataEdit()}) {
      RawType &x{ExtractElement<RawType>(io, descriptor, subscripts)};
      if constexpr (DIR == Direction::Output) {
        if (!RealOutputEditing<KIND>{io, x}.Edit(*edit)) {
          return false;
        }
      } else if (edit->descriptor != DataEdit::ListDirectedNullValue) {
        if (EditRealInput<KIND>(io, *edit, reinterpret_cast<void *>(&x))) {
          anyInput = true;
        } else {
          return anyInput && edit->IsNamelist();
        }
      }
      if (!descriptor.IncrementSubscripts(subscripts) && j + 1 < numElements) {
        io.GetIoErrorHandler().Crash(
            "FormattedRealIO: subscripts out of bounds");
      }
    } else {
      return false;
    }
  }
  return true;
}

template <int KIND, Direction DIR>
inline RT_API_ATTRS bool FormattedComplexIO(
    IoStatementState &io, const Descriptor &descriptor) {
  std::size_t numElements{descriptor.Elements()};
  SubscriptValue subscripts[maxRank];
  descriptor.GetLowerBounds(subscripts);
  bool isListOutput{
      io.get_if<ListDirectedStatementState<Direction::Output>>() != nullptr};
  using RawType = typename RealOutputEditing<KIND>::BinaryFloatingPoint;
  bool anyInput{false};
  for (std::size_t j{0}; j < numElements; ++j) {
    RawType *x{&ExtractElement<RawType>(io, descriptor, subscripts)};
    if (isListOutput) {
      DataEdit rEdit, iEdit;
      rEdit.descriptor = DataEdit::ListDirectedRealPart;
      iEdit.descriptor = DataEdit::ListDirectedImaginaryPart;
      rEdit.modes = iEdit.modes = io.mutableModes();
      if (!RealOutputEditing<KIND>{io, x[0]}.Edit(rEdit) ||
          !RealOutputEditing<KIND>{io, x[1]}.Edit(iEdit)) {
        return false;
      }
    } else {
      for (int k{0}; k < 2; ++k, ++x) {
        auto edit{io.GetNextDataEdit()};
        if (!edit) {
          return false;
        } else if constexpr (DIR == Direction::Output) {
          if (!RealOutputEditing<KIND>{io, *x}.Edit(*edit)) {
            return false;
          }
        } else if (edit->descriptor == DataEdit::ListDirectedNullValue) {
          break;
        } else if (EditRealInput<KIND>(
                       io, *edit, reinterpret_cast<void *>(x))) {
          anyInput = true;
        } else {
          return anyInput && edit->IsNamelist();
        }
      }
    }
    if (!descriptor.IncrementSubscripts(subscripts) && j + 1 < numElements) {
      io.GetIoErrorHandler().Crash(
          "FormattedComplexIO: subscripts out of bounds");
    }
  }
  return true;
}

template <typename A, Direction DIR>
inline RT_API_ATTRS bool FormattedCharacterIO(
    IoStatementState &io, const Descriptor &descriptor) {
  std::size_t numElements{descriptor.Elements()};
  SubscriptValue subscripts[maxRank];
  descriptor.GetLowerBounds(subscripts);
  std::size_t length{descriptor.ElementBytes() / sizeof(A)};
  auto *listOutput{io.get_if<ListDirectedStatementState<Direction::Output>>()};
  bool anyInput{false};
  for (std::size_t j{0}; j < numElements; ++j) {
    A *x{&ExtractElement<A>(io, descriptor, subscripts)};
    if (listOutput) {
      if (!ListDirectedCharacterOutput(io, *listOutput, x, length)) {
        return false;
      }
    } else if (auto edit{io.GetNextDataEdit()}) {
      if constexpr (DIR == Direction::Output) {
        if (!EditCharacterOutput(io, *edit, x, length)) {
          return false;
        }
      } else { // input
        if (edit->descriptor != DataEdit::ListDirectedNullValue) {
          if (EditCharacterInput(io, *edit, x, length)) {
            anyInput = true;
          } else {
            return anyInput && edit->IsNamelist();
          }
        }
      }
    } else {
      return false;
    }
    if (!descriptor.IncrementSubscripts(subscripts) && j + 1 < numElements) {
      io.GetIoErrorHandler().Crash(
          "FormattedCharacterIO: subscripts out of bounds");
    }
  }
  return true;
}

template <int KIND, Direction DIR>
inline RT_API_ATTRS bool FormattedLogicalIO(
    IoStatementState &io, const Descriptor &descriptor) {
  std::size_t numElements{descriptor.Elements()};
  SubscriptValue subscripts[maxRank];
  descriptor.GetLowerBounds(subscripts);
  auto *listOutput{io.get_if<ListDirectedStatementState<Direction::Output>>()};
  using IntType = CppTypeFor<TypeCategory::Integer, KIND>;
  bool anyInput{false};
  for (std::size_t j{0}; j < numElements; ++j) {
    IntType &x{ExtractElement<IntType>(io, descriptor, subscripts)};
    if (listOutput) {
      if (!ListDirectedLogicalOutput(io, *listOutput, x != 0)) {
        return false;
      }
    } else if (auto edit{io.GetNextDataEdit()}) {
      if constexpr (DIR == Direction::Output) {
        if (!EditLogicalOutput(io, *edit, x != 0)) {
          return false;
        }
      } else {
        if (edit->descriptor != DataEdit::ListDirectedNullValue) {
          bool truth{};
          if (EditLogicalInput(io, *edit, truth)) {
            x = truth;
            anyInput = true;
          } else {
            return anyInput && edit->IsNamelist();
          }
        }
      }
    } else {
      return false;
    }
    if (!descriptor.IncrementSubscripts(subscripts) && j + 1 < numElements) {
      io.GetIoErrorHandler().Crash(
          "FormattedLogicalIO: subscripts out of bounds");
    }
  }
  return true;
}

template <Direction DIR>
RT_API_ATTRS int DerivedIoTicket<DIR>::Continue(WorkQueue &workQueue) {
  while (!IsComplete()) {
    if (component_->genre() == typeInfo::Component::Genre::Data) {
      // Create a descriptor for the component
      Descriptor &compDesc{componentDescriptor_.descriptor()};
      component_->CreatePointerDescriptor(
          compDesc, instance_, io_.GetIoErrorHandler(), subscripts_);
      Advance();
      if (int status{workQueue.BeginDescriptorIo<DIR>(
              io_, compDesc, table_, anyIoTookPlace_)};
          status != StatOk) {
        return status;
      }
    } else {
      // Component is itself a descriptor
      char *pointer{
          instance_.Element<char>(subscripts_) + component_->offset()};
      const Descriptor &compDesc{
          *reinterpret_cast<const Descriptor *>(pointer)};
      Advance();
      if (compDesc.IsAllocated()) {
        if (int status{workQueue.BeginDescriptorIo<DIR>(
                io_, compDesc, table_, anyIoTookPlace_)};
            status != StatOk) {
          return status;
        }
      }
    }
  }
  return StatOk;
}

template RT_API_ATTRS int DerivedIoTicket<Direction::Output>::Continue(
    WorkQueue &);
template RT_API_ATTRS int DerivedIoTicket<Direction::Input>::Continue(
    WorkQueue &);

template <Direction DIR>
RT_API_ATTRS int DescriptorIoTicket<DIR>::Begin(WorkQueue &workQueue) {
  IoErrorHandler &handler{io_.GetIoErrorHandler()};
  if (handler.InError()) {
    return handler.GetIoStat();
  }
  if (!io_.get_if<IoDirectionState<DIR>>()) {
    handler.Crash("DescriptorIO() called for wrong I/O direction");
    return handler.GetIoStat();
  }
  if constexpr (DIR == Direction::Input) {
    if (!io_.BeginReadingRecord()) {
      return StatOk;
    }
  }
  if (!io_.get_if<FormattedIoStatementState<DIR>>()) {
    // Unformatted I/O
    IoErrorHandler &handler{io_.GetIoErrorHandler()};
    const DescriptorAddendum *addendum{instance_.Addendum()};
    if (const typeInfo::DerivedType *type{
            addendum ? addendum->derivedType() : nullptr}) {
      // derived type unformatted I/O
      if (DIR == Direction::Input || !io_.get_if<InquireIOLengthState>()) {
        if (table_) {
          if (const auto *definedIo{table_->Find(*type,
                  DIR == Direction::Input
                      ? common::DefinedIo::ReadUnformatted
                      : common::DefinedIo::WriteUnformatted)}) {
            if (definedIo->subroutine) {
              std::uint8_t isArgDescriptorSet{0};
              if (definedIo->flags & IsDtvArgPolymorphic) {
                isArgDescriptorSet = 1;
              }
              typeInfo::SpecialBinding special{DIR == Direction::Input
                      ? typeInfo::SpecialBinding::Which::ReadUnformatted
                      : typeInfo::SpecialBinding::Which::WriteUnformatted,
                  definedIo->subroutine, isArgDescriptorSet,
                  /*IsTypeBound=*/false,
                  /*specialCaseFlag=*/!!(definedIo->flags & DefinedIoInteger8)};
              if (DefinedUnformattedIo(io_, instance_, *type, special)) {
                anyIoTookPlace_ = true;
                return StatOk;
              }
            } else {
              int status{workQueue.BeginDerivedIo<DIR>(
                  io_, instance_, *type, table_, anyIoTookPlace_)};
              return status == StatContinue ? StatOk : status; // done here
            }
          }
        }
        if (const typeInfo::SpecialBinding *special{
                type->FindSpecialBinding(DIR == Direction::Input
                        ? typeInfo::SpecialBinding::Which::ReadUnformatted
                        : typeInfo::SpecialBinding::Which::WriteUnformatted)}) {
          if (!table_ || !table_->ignoreNonTbpEntries ||
              special->IsTypeBound()) {
            // defined derived type unformatted I/O
            if (DefinedUnformattedIo(io_, instance_, *type, *special)) {
              anyIoTookPlace_ = true;
              return StatOk;
            } else {
              return IostatEnd;
            }
          }
        }
      }
      // Default derived type unformatted I/O
      // TODO: If no component at any level has defined READ or WRITE
      // (as appropriate), the elements are contiguous, and no byte swapping
      // is active, do a block transfer via the code below.
      int status{workQueue.BeginDerivedIo<DIR>(
          io_, instance_, *type, table_, anyIoTookPlace_)};
      return status == StatContinue ? StatOk : status; // done here
    } else {
      // intrinsic type unformatted I/O
      auto *externalUnf{io_.get_if<ExternalUnformattedIoStatementState<DIR>>()};
      ChildUnformattedIoStatementState<DIR> *childUnf{nullptr};
      InquireIOLengthState *inq{nullptr};
      bool swapEndianness{false};
      if (externalUnf) {
        swapEndianness = externalUnf->unit().swapEndianness();
      } else {
        childUnf = io_.get_if<ChildUnformattedIoStatementState<DIR>>();
        if (!childUnf) {
          inq = DIR == Direction::Output ? io_.get_if<InquireIOLengthState>()
                                         : nullptr;
          RUNTIME_CHECK(handler, inq != nullptr);
        }
      }
      std::size_t elementBytes{instance_.ElementBytes()};
      std::size_t swappingBytes{elementBytes};
      if (auto maybeCatAndKind{instance_.type().GetCategoryAndKind()}) {
        // Byte swapping units can be smaller than elements, namely
        // for COMPLEX and CHARACTER.
        if (maybeCatAndKind->first == TypeCategory::Character) {
          // swap each character position independently
          swappingBytes = maybeCatAndKind->second; // kind
        } else if (maybeCatAndKind->first == TypeCategory::Complex) {
          // swap real and imaginary components independently
          swappingBytes /= 2;
        }
      }
      using CharType =
          std::conditional_t<DIR == Direction::Output, const char, char>;
      auto Transfer{[=](CharType &x, std::size_t totalBytes) -> bool {
        if constexpr (DIR == Direction::Output) {
          return externalUnf ? externalUnf->Emit(&x, totalBytes, swappingBytes)
              : childUnf     ? childUnf->Emit(&x, totalBytes, swappingBytes)
                             : inq->Emit(&x, totalBytes, swappingBytes);
        } else {
          return externalUnf
              ? externalUnf->Receive(&x, totalBytes, swappingBytes)
              : childUnf->Receive(&x, totalBytes, swappingBytes);
        }
      }};
      if (!swapEndianness &&
          instance_.IsContiguous()) { // contiguous unformatted I/O
        char &x{ExtractElement<char>(io_, instance_, subscripts_)};
        if (Transfer(x, elements_ * elementBytes)) {
          anyIoTookPlace_ = true;
        } else {
          return IostatEnd;
        }
      } else { // non-contiguous or byte-swapped intrinsic type unformatted I/O
        for (; !IsComplete(); Advance()) {
          char &x{ExtractElement<char>(io_, instance_, subscripts_)};
          if (Transfer(x, elementBytes)) {
            anyIoTookPlace_ = true;
          } else {
            return IostatEnd;
          }
        }
      }
    }
    // Unformatted I/O never needs to call Continue().
    return StatOk;
  }
  // Formatted I/O
  if (auto catAndKind{instance_.type().GetCategoryAndKind()}) {
    TypeCategory cat{catAndKind->first};
    int kind{catAndKind->second};
    bool any{false};
    switch (cat) {
    case TypeCategory::Integer:
      switch (kind) {
      case 1:
        any = FormattedIntegerIO<1, DIR>(io_, instance_, true);
        break;
      case 2:
        any = FormattedIntegerIO<2, DIR>(io_, instance_, true);
        break;
      case 4:
        any = FormattedIntegerIO<4, DIR>(io_, instance_, true);
        break;
      case 8:
        any = FormattedIntegerIO<8, DIR>(io_, instance_, true);
        break;
      case 16:
        any = FormattedIntegerIO<16, DIR>(io_, instance_, true);
        break;
      default:
        handler.Crash(
            "not yet implemented: INTEGER(KIND=%d) in formatted IO", kind);
        return IostatEnd;
      }
      break;
    case TypeCategory::Unsigned:
      switch (kind) {
      case 1:
        any = FormattedIntegerIO<1, DIR>(io_, instance_, false);
        break;
      case 2:
        any = FormattedIntegerIO<2, DIR>(io_, instance_, false);
        break;
      case 4:
        any = FormattedIntegerIO<4, DIR>(io_, instance_, false);
        break;
      case 8:
        any = FormattedIntegerIO<8, DIR>(io_, instance_, false);
        break;
      case 16:
        any = FormattedIntegerIO<16, DIR>(io_, instance_, false);
        break;
      default:
        handler.Crash(
            "not yet implemented: UNSIGNED(KIND=%d) in formatted IO", kind);
        return IostatEnd;
      }
      break;
    case TypeCategory::Real:
      switch (kind) {
      case 2:
        any = FormattedRealIO<2, DIR>(io_, instance_);
        break;
      case 3:
        any = FormattedRealIO<3, DIR>(io_, instance_);
        break;
      case 4:
        any = FormattedRealIO<4, DIR>(io_, instance_);
        break;
      case 8:
        any = FormattedRealIO<8, DIR>(io_, instance_);
        break;
      case 10:
        any = FormattedRealIO<10, DIR>(io_, instance_);
        break;
      // TODO: case double/double
      case 16:
        any = FormattedRealIO<16, DIR>(io_, instance_);
        break;
      default:
        handler.Crash(
            "not yet implemented: REAL(KIND=%d) in formatted IO", kind);
        return IostatEnd;
      }
      break;
    case TypeCategory::Complex:
      switch (kind) {
      case 2:
        any = FormattedComplexIO<2, DIR>(io_, instance_);
        break;
      case 3:
        any = FormattedComplexIO<3, DIR>(io_, instance_);
        break;
      case 4:
        any = FormattedComplexIO<4, DIR>(io_, instance_);
        break;
      case 8:
        any = FormattedComplexIO<8, DIR>(io_, instance_);
        break;
      case 10:
        any = FormattedComplexIO<10, DIR>(io_, instance_);
        break;
      // TODO: case double/double
      case 16:
        any = FormattedComplexIO<16, DIR>(io_, instance_);
        break;
      default:
        handler.Crash(
            "not yet implemented: COMPLEX(KIND=%d) in formatted IO", kind);
        return IostatEnd;
      }
      break;
    case TypeCategory::Character:
      switch (kind) {
      case 1:
        any = FormattedCharacterIO<char, DIR>(io_, instance_);
        break;
      case 2:
        any = FormattedCharacterIO<char16_t, DIR>(io_, instance_);
        break;
      case 4:
        any = FormattedCharacterIO<char32_t, DIR>(io_, instance_);
        break;
      default:
        handler.Crash(
            "not yet implemented: CHARACTER(KIND=%d) in formatted IO", kind);
        return IostatEnd;
      }
      break;
    case TypeCategory::Logical:
      switch (kind) {
      case 1:
        any = FormattedLogicalIO<1, DIR>(io_, instance_);
        break;
      case 2:
        any = FormattedLogicalIO<2, DIR>(io_, instance_);
        break;
      case 4:
        any = FormattedLogicalIO<4, DIR>(io_, instance_);
        break;
      case 8:
        any = FormattedLogicalIO<8, DIR>(io_, instance_);
        break;
      default:
        handler.Crash(
            "not yet implemented: LOGICAL(KIND=%d) in formatted IO", kind);
        return IostatEnd;
      }
      break;
    case TypeCategory::Derived: {
      // Derived type information must be present for formatted I/O.
      IoErrorHandler &handler{io_.GetIoErrorHandler()};
      const DescriptorAddendum *addendum{instance_.Addendum()};
      RUNTIME_CHECK(handler, addendum != nullptr);
      derived_ = addendum->derivedType();
      RUNTIME_CHECK(handler, derived_ != nullptr);
      if (table_) {
        if (const auto *definedIo{table_->Find(*derived_,
                DIR == Direction::Input ? common::DefinedIo::ReadFormatted
                                        : common::DefinedIo::WriteFormatted)}) {
          if (definedIo->subroutine) {
            nonTbpSpecial_.emplace(DIR == Direction::Input
                    ? typeInfo::SpecialBinding::Which::ReadFormatted
                    : typeInfo::SpecialBinding::Which::WriteFormatted,
                definedIo->subroutine,
                /*isArgDescriptorSet=*/
                (definedIo->flags & IsDtvArgPolymorphic) ? 1 : 0,
                /*isTypeBound=*/false,
                /*specialCaseFlag=*/!!(definedIo->flags & DefinedIoInteger8));
            special_ = &*nonTbpSpecial_;
          }
        }
      }
      if (!special_) {
        if (const typeInfo::SpecialBinding *binding{
                derived_->FindSpecialBinding(DIR == Direction::Input
                        ? typeInfo::SpecialBinding::Which::ReadFormatted
                        : typeInfo::SpecialBinding::Which::WriteFormatted)}) {
          if (!table_ || !table_->ignoreNonTbpEntries ||
              binding->IsTypeBound()) {
            special_ = binding;
          }
        }
      }
      return StatContinue;
    }
    }
    if (any) {
      anyIoTookPlace_ = true;
    } else {
      return IostatEnd;
    }
  } else {
    handler.Crash("DescriptorIO: bad type code (%d) in descriptor",
        static_cast<int>(instance_.type().raw()));
    return handler.GetIoStat();
  }
  return StatOk;
}

template RT_API_ATTRS int DescriptorIoTicket<Direction::Output>::Begin(
    WorkQueue &);
template RT_API_ATTRS int DescriptorIoTicket<Direction::Input>::Begin(
    WorkQueue &);

template <Direction DIR>
RT_API_ATTRS int DescriptorIoTicket<DIR>::Continue(WorkQueue &workQueue) {
  // Only derived type formatted I/O gets here.
  while (!IsComplete()) {
    if (special_) {
      if (auto defined{DefinedFormattedIo(
              io_, instance_, *derived_, *special_, subscripts_)}) {
        anyIoTookPlace_ |= *defined;
        Advance();
        continue;
      }
    }
    Descriptor &elementDesc{elementDescriptor_.descriptor()};
    elementDesc.Establish(
        *derived_, nullptr, 0, nullptr, CFI_attribute_pointer);
    elementDesc.set_base_addr(instance_.Element<char>(subscripts_));
    Advance();
    if (int status{workQueue.BeginDerivedIo<DIR>(
            io_, elementDesc, *derived_, table_, anyIoTookPlace_)};
        status != StatOk) {
      return status;
    }
  }
  return StatOk;
}

template RT_API_ATTRS int DescriptorIoTicket<Direction::Output>::Continue(
    WorkQueue &);
template RT_API_ATTRS int DescriptorIoTicket<Direction::Input>::Continue(
    WorkQueue &);

template <Direction DIR>
RT_API_ATTRS bool DescriptorIO(IoStatementState &io,
    const Descriptor &descriptor, const NonTbpDefinedIoTable *originalTable) {
  bool anyIoTookPlace{false};
  const NonTbpDefinedIoTable *defaultTable{io.nonTbpDefinedIoTable()};
  const NonTbpDefinedIoTable *table{originalTable};
  if (!table) {
    table = defaultTable;
  } else if (table != defaultTable) {
    io.set_nonTbpDefinedIoTable(table); // for nested I/O
  }
  WorkQueue workQueue{io.GetIoErrorHandler()};
  if (workQueue.BeginDescriptorIo<DIR>(io, descriptor, table, anyIoTookPlace) ==
      StatContinue) {
    workQueue.Run();
  }
  if (defaultTable != table) {
    io.set_nonTbpDefinedIoTable(defaultTable);
  }
  return anyIoTookPlace;
}

template RT_API_ATTRS bool DescriptorIO<Direction::Output>(
    IoStatementState &, const Descriptor &, const NonTbpDefinedIoTable *);
template RT_API_ATTRS bool DescriptorIO<Direction::Input>(
    IoStatementState &, const Descriptor &, const NonTbpDefinedIoTable *);

RT_OFFLOAD_API_GROUP_END
} // namespace Fortran::runtime::io::descr
