diff --git a/flang/include/flang/Runtime/io-api.h b/flang/include/flang/Runtime/io-api.h --- a/flang/include/flang/Runtime/io-api.h +++ b/flang/include/flang/Runtime/io-api.h @@ -280,10 +280,10 @@ bool IONAME(GetNewUnit)(Cookie, int &, int kind = 4); // READ(SIZE=), after all input items -bool IONAME(GetSize)(Cookie, std::int64_t, int kind = 8); +std::size_t IONAME(GetSize)(Cookie); // INQUIRE(IOLENGTH=), after all output items -bool IONAME(GetIoLength)(Cookie, std::int64_t, int kind = 8); +std::size_t IONAME(GetIoLength)(Cookie); // GetIoMsg() does not modify its argument unless an error or // end-of-record/file condition is present. diff --git a/flang/runtime/descriptor-io.h b/flang/runtime/descriptor-io.h --- a/flang/runtime/descriptor-io.h +++ b/flang/runtime/descriptor-io.h @@ -315,7 +315,9 @@ // Regular derived type unformatted I/O, not user-defined auto *externalUnf{io.get_if>()}; auto *childUnf{io.get_if>()}; - RUNTIME_CHECK(handler, externalUnf != nullptr || childUnf != nullptr); + auto *inq{ + DIR == Direction::Output ? io.get_if() : nullptr}; + RUNTIME_CHECK(handler, externalUnf || childUnf || inq); std::size_t elementBytes{descriptor.ElementBytes()}; std::size_t numElements{descriptor.Elements()}; SubscriptValue subscripts[maxRank]; @@ -326,7 +328,8 @@ std::size_t elementBytes) -> bool { if constexpr (DIR == Direction::Output) { return externalUnf ? externalUnf->Emit(&x, totalBytes, elementBytes) - : childUnf->Emit(&x, totalBytes, elementBytes); + : childUnf ? childUnf->Emit(&x, totalBytes, elementBytes) + : inq->Emit(&x, totalBytes, elementBytes); } else { return externalUnf ? externalUnf->Receive(&x, totalBytes, elementBytes) : childUnf->Receive(&x, totalBytes, elementBytes); @@ -363,7 +366,7 @@ return false; } } - if (!io.get_if()) { + if (!io.get_if>()) { return UnformattedDescriptorIO(io, descriptor); } IoErrorHandler &handler{io.GetIoErrorHandler()}; diff --git a/flang/runtime/edit-input.cpp b/flang/runtime/edit-input.cpp --- a/flang/runtime/edit-input.cpp +++ b/flang/runtime/edit-input.cpp @@ -56,6 +56,7 @@ if (next) { negative = *next == '-'; if (negative || *next == '+') { + io.GotChar(); io.SkipSpaces(remaining); next = io.NextInField(remaining); } @@ -453,6 +454,7 @@ next = io.NextInField(remaining)) { if (skip > 0) { --skip; + io.GotChar(-1); } else { *x++ = *next; --length; diff --git a/flang/runtime/io-api.cpp b/flang/runtime/io-api.cpp --- a/flang/runtime/io-api.cpp +++ b/flang/runtime/io-api.cpp @@ -925,6 +925,8 @@ if (auto *unf{io.get_if< ExternalUnformattedIoStatementState>()}) { return unf->Emit(x, length, elementBytes); + } else if (auto *inq{io.get_if()}) { + return inq->Emit(x, length, elementBytes); } io.GetIoErrorHandler().Crash("OutputUnformattedBlock() called for an I/O " "statement that is not unformatted output"); @@ -1080,6 +1082,27 @@ return descr::DescriptorIO(*cookie, descriptor); } +std::size_t IONAME(GetSize)(Cookie cookie) { + IoStatementState &io{*cookie}; + if (const auto *formatted{ + io.get_if>()}) { + return formatted->GetEditDescriptorChars(); + } + io.GetIoErrorHandler().Crash( + "GetIoSize() called for an I/O statement that is not a formatted READ()"); + return 0; +} + +std::size_t IONAME(GetIoLength)(Cookie cookie) { + IoStatementState &io{*cookie}; + if (const auto *inq{io.get_if()}) { + return inq->bytes(); + } + io.GetIoErrorHandler().Crash("GetIoLength() called for an I/O statement that " + "is not INQUIRE(IOLENGTH=)"); + return 0; +} + void IONAME(GetIoMsg)(Cookie cookie, char *msg, std::size_t length) { IoErrorHandler &handler{cookie->GetIoErrorHandler()}; if (handler.InError()) { // leave "msg" alone when no error diff --git a/flang/runtime/io-stmt.h b/flang/runtime/io-stmt.h --- a/flang/runtime/io-stmt.h +++ b/flang/runtime/io-stmt.h @@ -52,7 +52,19 @@ template using IoDirectionState = std::conditional_t; -struct FormattedIoStatementState {}; + +// Common state for all kinds of formatted I/O +template class FormattedIoStatementState {}; +template <> class FormattedIoStatementState { +public: + std::size_t GetEditDescriptorChars() const; + void GotChar(int); + +private: + // Account of characters read for edit descriptors (i.e., formatted I/O + // with a FORMAT, not list-directed or NAMELIST), not including padding. + std::size_t chars_{0}; // for READ(SIZE=) +}; // The Cookie type in the I/O API is a pointer (for C) to this class. class IoStatementState { @@ -83,6 +95,7 @@ bool Inquire(InquiryKeywordHash, bool &); bool Inquire(InquiryKeywordHash, std::int64_t, bool &); // PENDING= bool Inquire(InquiryKeywordHash, std::int64_t &); + void GotChar(signed int = 1); // for READ(SIZE=); can be <0 MutableModes &mutableModes(); ConnectionState &GetConnectionState(); @@ -115,8 +128,7 @@ std::optional GetNextNonBlank(); template void CheckFormattedStmtType(const char *name) { - if (!get_if() || - !get_if>()) { + if (!get_if>()) { GetIoErrorHandler().Crash( "%s called for I/O statement that is not formatted %s", name, D == Direction::Output ? "output" : "input"); @@ -191,7 +203,7 @@ template class ListDirectedStatementState; template <> class ListDirectedStatementState - : public FormattedIoStatementState { + : public FormattedIoStatementState { public: bool EmitLeadingSpaceOrAdvance( IoStatementState &, std::size_t = 1, bool isCharacter = false); @@ -209,7 +221,7 @@ }; template <> class ListDirectedStatementState - : public FormattedIoStatementState { + : public FormattedIoStatementState { public: // Skips value separators, handles repetition and null values. // Vacant when '/' appears; present with descriptor == ListDirectedNullValue @@ -269,7 +281,7 @@ template class InternalFormattedIoStatementState : public InternalIoStatementState, - public FormattedIoStatementState { + public FormattedIoStatementState { public: using CharType = CHAR; using typename InternalIoStatementState::Buffer; @@ -353,8 +365,9 @@ }; template -class ExternalFormattedIoStatementState : public ExternalIoStatementState, - public FormattedIoStatementState { +class ExternalFormattedIoStatementState + : public ExternalIoStatementState, + public FormattedIoStatementState { public: using CharType = CHAR; ExternalFormattedIoStatementState(ExternalFileUnit &, const CharType *format, @@ -411,7 +424,7 @@ template class ChildFormattedIoStatementState : public ChildIoStatementState, - public FormattedIoStatementState { + public FormattedIoStatementState { public: using CharType = CHAR; ChildFormattedIoStatementState(ChildIo &, const CharType *format, @@ -584,6 +597,10 @@ 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); private: std::size_t bytes_{0}; diff --git a/flang/runtime/io-stmt.cpp b/flang/runtime/io-stmt.cpp --- a/flang/runtime/io-stmt.cpp +++ b/flang/runtime/io-stmt.cpp @@ -521,6 +521,7 @@ } HandleRelativePosition(1); if (remaining) { + GotChar(); --*remaining; } } else { @@ -556,6 +557,7 @@ if (auto next{GetCurrentChar()}) { --*remaining; HandleRelativePosition(1); + GotChar(); return next; } const ConnectionState &connection{GetConnectionState()}; @@ -610,6 +612,25 @@ return std::visit([&](auto &x) { return x.get().Inquire(inquiry, n); }, u_); } +void IoStatementState::GotChar(int n) { + if (auto *formattedIn{ + get_if>()}) { + formattedIn->GotChar(n); + } else { + GetIoErrorHandler().Crash("IoStatementState::GotChar() called for " + "statement that is not formatted input"); + } +} + +std::size_t +FormattedIoStatementState::GetEditDescriptorChars() const { + return chars_; +} + +void FormattedIoStatementState::GotChar(int n) { + chars_ += n; +} + bool ListDirectedStatementState::EmitLeadingSpaceOrAdvance( IoStatementState &io, std::size_t length, bool isCharacter) { if (length == 0) { @@ -1325,4 +1346,25 @@ const char *sourceFile, int sourceLine) : NoUnitIoStatementState{sourceFile, sourceLine, *this} {} +bool InquireIOLengthState::Emit( + const char *, std::size_t n, std::size_t elementBytes) { + bytes_ += n * elementBytes; + 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; + return true; +} + } // namespace Fortran::runtime::io diff --git a/flang/runtime/unit.cpp b/flang/runtime/unit.cpp --- a/flang/runtime/unit.cpp +++ b/flang/runtime/unit.cpp @@ -768,8 +768,13 @@ bool ChildIo::CheckFormattingAndDirection(Terminator &terminator, const char *what, bool unformatted, Direction direction) { - bool parentIsUnformatted{!parent_.get_if()}; bool parentIsInput{!parent_.get_if>()}; + bool parentIsFormatted{parentIsInput + ? parent_.get_if>() != + nullptr + : parent_.get_if>() != + nullptr}; + bool parentIsUnformatted{!parentIsFormatted}; if (unformatted != parentIsUnformatted) { terminator.Crash("Child %s attempted on %s parent I/O unit", what, parentIsUnformatted ? "unformatted" : "formatted"); diff --git a/flang/unittests/Runtime/ExternalIOTest.cpp b/flang/unittests/Runtime/ExternalIOTest.cpp --- a/flang/unittests/Runtime/ExternalIOTest.cpp +++ b/flang/unittests/Runtime/ExternalIOTest.cpp @@ -41,6 +41,15 @@ ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) << "EndIoStatement() for OpenNewUnit"; + // INQUIRE(IOLENGTH=) j + io = IONAME(BeginInquireIoLength)(__FILE__, __LINE__); + ASSERT_TRUE(IONAME(OutputUnformattedBlock)( + io, reinterpret_cast(&buffer), 1, recl)) + << "OutputUnformattedBlock() for InquireIoLength"; + ASSERT_EQ(IONAME(GetIoLength)(io), recl) << "GetIoLength"; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for InquireIoLength"; + static constexpr int records{10}; for (int j{1}; j <= records; ++j) { // WRITE(UNIT=unit,REC=j) j @@ -49,7 +58,7 @@ buffer = j; ASSERT_TRUE(IONAME(OutputUnformattedBlock)( - io, reinterpret_cast(&buffer), recl, recl)) + io, reinterpret_cast(&buffer), 1, recl)) << "OutputUnformattedBlock()"; ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) @@ -61,7 +70,7 @@ io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__); ASSERT_TRUE(IONAME(SetRec)(io, j)) << "SetRec(" << j << ')'; ASSERT_TRUE(IONAME(InputUnformattedBlock)( - io, reinterpret_cast(&buffer), recl, recl)) + io, reinterpret_cast(&buffer), 1, recl)) << "InputUnformattedBlock()"; ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) @@ -158,6 +167,17 @@ ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) << "EndIoStatement() for OpenNewUnit"; + // INQUIRE(IOLENGTH=) j, ... + io = IONAME(BeginInquireIoLength)(__FILE__, __LINE__); + for (int j{1}; j <= 3; ++j) { + ASSERT_TRUE(IONAME(OutputUnformattedBlock)( + io, reinterpret_cast(&buffer), 1, recl)) + << "OutputUnformattedBlock() for InquireIoLength"; + } + ASSERT_EQ(IONAME(GetIoLength)(io), 3 * recl) << "GetIoLength"; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for InquireIoLength"; + static const int records{10}; for (int j{1}; j <= records; ++j) { // DO J=1,RECORDS; WRITE(UNIT=unit) j; END DO @@ -417,7 +437,7 @@ << "EndIoStatement() for Backspace (before read)"; std::snprintf(fmt, sizeof fmt, "(%dI4)", j); - // READ(UNIT=unit,FMT=fmt) n; check + // READ(UNIT=unit,FMT=fmt,SIZE=chars) n; check io = IONAME(BeginExternalFormattedInput)( fmt, std::strlen(fmt), unit, __FILE__, __LINE__); @@ -426,6 +446,9 @@ ASSERT_TRUE(IONAME(InputInteger)(io, check[k])) << "InputInteger()"; } + std::size_t chars{IONAME(GetSize)(io)}; + ASSERT_EQ(chars, j * 4u) + << "GetSize()=" << chars << ", expected " << (j * 4u) << '\n'; ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) << "EndIoStatement() for InputInteger"; for (int k{0}; k < j; ++k) {