[flang] Implement READ(SIZE=) and INQUIRE(IOLENGTH=) in runtime

Count input characters corresponding to formatted edit descriptors
for READ(SIZE=); count output bytes for INQUIRE(IOLENGTH=).

The I/O APIs GetSize() and GetLength() were adjusted to return
std::size_t as function results.

Basic unit tests were added (and others fixed).

Differential Revision: https://reviews.llvm.org/D110291
This commit is contained in:
peter klausler 2021-09-20 10:52:39 -07:00
parent e6126faba0
commit 4393e3776b
8 changed files with 133 additions and 18 deletions

View File

@ -280,10 +280,10 @@ bool IONAME(SetFile)(Cookie, const char *, std::size_t chars);
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.

View File

@ -315,7 +315,9 @@ static bool UnformattedDescriptorIO(
// Regular derived type unformatted I/O, not user-defined
auto *externalUnf{io.get_if<ExternalUnformattedIoStatementState<DIR>>()};
auto *childUnf{io.get_if<ChildUnformattedIoStatementState<DIR>>()};
RUNTIME_CHECK(handler, externalUnf != nullptr || childUnf != nullptr);
auto *inq{
DIR == Direction::Output ? io.get_if<InquireIOLengthState>() : 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 @@ static bool UnformattedDescriptorIO(
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 @@ static bool DescriptorIO(IoStatementState &io, const Descriptor &descriptor) {
return false;
}
}
if (!io.get_if<FormattedIoStatementState>()) {
if (!io.get_if<FormattedIoStatementState<DIR>>()) {
return UnformattedDescriptorIO<DIR>(io, descriptor);
}
IoErrorHandler &handler{io.GetIoErrorHandler()};

View File

@ -56,6 +56,7 @@ static bool ScanNumericPrefix(IoStatementState &io, const DataEdit &edit,
if (next) {
negative = *next == '-';
if (negative || *next == '+') {
io.GotChar();
io.SkipSpaces(remaining);
next = io.NextInField(remaining);
}
@ -453,6 +454,7 @@ bool EditDefaultCharacterInput(
next = io.NextInField(remaining)) {
if (skip > 0) {
--skip;
io.GotChar(-1);
} else {
*x++ = *next;
--length;

View File

@ -925,6 +925,8 @@ bool IONAME(OutputUnformattedBlock)(Cookie cookie, const char *x,
if (auto *unf{io.get_if<
ExternalUnformattedIoStatementState<Direction::Output>>()}) {
return unf->Emit(x, length, elementBytes);
} else if (auto *inq{io.get_if<InquireIOLengthState>()}) {
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 @@ bool IONAME(InputLogical)(Cookie cookie, bool &truth) {
return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
}
std::size_t IONAME(GetSize)(Cookie cookie) {
IoStatementState &io{*cookie};
if (const auto *formatted{
io.get_if<FormattedIoStatementState<Direction::Input>>()}) {
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<InquireIOLengthState>()}) {
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

View File

@ -521,6 +521,7 @@ std::optional<char32_t> IoStatementState::SkipSpaces(
}
HandleRelativePosition(1);
if (remaining) {
GotChar();
--*remaining;
}
} else {
@ -556,6 +557,7 @@ std::optional<char32_t> IoStatementState::NextInField(
if (auto next{GetCurrentChar()}) {
--*remaining;
HandleRelativePosition(1);
GotChar();
return next;
}
const ConnectionState &connection{GetConnectionState()};
@ -610,6 +612,25 @@ bool IoStatementState::Inquire(InquiryKeywordHash inquiry, std::int64_t &n) {
return std::visit([&](auto &x) { return x.get().Inquire(inquiry, n); }, u_);
}
void IoStatementState::GotChar(int n) {
if (auto *formattedIn{
get_if<FormattedIoStatementState<Direction::Input>>()}) {
formattedIn->GotChar(n);
} else {
GetIoErrorHandler().Crash("IoStatementState::GotChar() called for "
"statement that is not formatted input");
}
}
std::size_t
FormattedIoStatementState<Direction::Input>::GetEditDescriptorChars() const {
return chars_;
}
void FormattedIoStatementState<Direction::Input>::GotChar(int n) {
chars_ += n;
}
bool ListDirectedStatementState<Direction::Output>::EmitLeadingSpaceOrAdvance(
IoStatementState &io, std::size_t length, bool isCharacter) {
if (length == 0) {
@ -1325,4 +1346,25 @@ InquireIOLengthState::InquireIOLengthState(
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

View File

@ -52,7 +52,19 @@ struct OutputStatementState {};
template <Direction D>
using IoDirectionState = std::conditional_t<D == Direction::Input,
InputStatementState, OutputStatementState>;
struct FormattedIoStatementState {};
// Common state for all kinds of formatted I/O
template <Direction D> class FormattedIoStatementState {};
template <> class FormattedIoStatementState<Direction::Input> {
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 @@ public:
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 @@ public:
std::optional<char32_t> GetNextNonBlank();
template <Direction D> void CheckFormattedStmtType(const char *name) {
if (!get_if<FormattedIoStatementState>() ||
!get_if<IoDirectionState<D>>()) {
if (!get_if<FormattedIoStatementState<D>>()) {
GetIoErrorHandler().Crash(
"%s called for I/O statement that is not formatted %s", name,
D == Direction::Output ? "output" : "input");
@ -191,7 +203,7 @@ struct IoStatementBase : public IoErrorHandler {
template <Direction> class ListDirectedStatementState;
template <>
class ListDirectedStatementState<Direction::Output>
: public FormattedIoStatementState {
: public FormattedIoStatementState<Direction::Output> {
public:
bool EmitLeadingSpaceOrAdvance(
IoStatementState &, std::size_t = 1, bool isCharacter = false);
@ -209,7 +221,7 @@ private:
};
template <>
class ListDirectedStatementState<Direction::Input>
: public FormattedIoStatementState {
: public FormattedIoStatementState<Direction::Input> {
public:
// Skips value separators, handles repetition and null values.
// Vacant when '/' appears; present with descriptor == ListDirectedNullValue
@ -269,7 +281,7 @@ protected:
template <Direction DIR, typename CHAR>
class InternalFormattedIoStatementState
: public InternalIoStatementState<DIR, CHAR>,
public FormattedIoStatementState {
public FormattedIoStatementState<DIR> {
public:
using CharType = CHAR;
using typename InternalIoStatementState<DIR, CharType>::Buffer;
@ -353,8 +365,9 @@ private:
};
template <Direction DIR, typename CHAR>
class ExternalFormattedIoStatementState : public ExternalIoStatementState<DIR>,
public FormattedIoStatementState {
class ExternalFormattedIoStatementState
: public ExternalIoStatementState<DIR>,
public FormattedIoStatementState<DIR> {
public:
using CharType = CHAR;
ExternalFormattedIoStatementState(ExternalFileUnit &, const CharType *format,
@ -411,7 +424,7 @@ private:
template <Direction DIR, typename CHAR>
class ChildFormattedIoStatementState : public ChildIoStatementState<DIR>,
public FormattedIoStatementState {
public FormattedIoStatementState<DIR> {
public:
using CharType = CHAR;
ChildFormattedIoStatementState(ChildIo &, const CharType *format,
@ -584,6 +597,10 @@ 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);
private:
std::size_t bytes_{0};

View File

@ -768,8 +768,13 @@ void ChildIo::EndIoStatement() {
bool ChildIo::CheckFormattingAndDirection(Terminator &terminator,
const char *what, bool unformatted, Direction direction) {
bool parentIsUnformatted{!parent_.get_if<FormattedIoStatementState>()};
bool parentIsInput{!parent_.get_if<IoDirectionState<Direction::Output>>()};
bool parentIsFormatted{parentIsInput
? parent_.get_if<FormattedIoStatementState<Direction::Input>>() !=
nullptr
: parent_.get_if<FormattedIoStatementState<Direction::Output>>() !=
nullptr};
bool parentIsUnformatted{!parentIsFormatted};
if (unformatted != parentIsUnformatted) {
terminator.Crash("Child %s attempted on %s parent I/O unit", what,
parentIsUnformatted ? "unformatted" : "formatted");

View File

@ -41,6 +41,15 @@ TEST(ExternalIOTests, TestDirectUnformatted) {
ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
<< "EndIoStatement() for OpenNewUnit";
// INQUIRE(IOLENGTH=) j
io = IONAME(BeginInquireIoLength)(__FILE__, __LINE__);
ASSERT_TRUE(IONAME(OutputUnformattedBlock)(
io, reinterpret_cast<const char *>(&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 @@ TEST(ExternalIOTests, TestDirectUnformatted) {
buffer = j;
ASSERT_TRUE(IONAME(OutputUnformattedBlock)(
io, reinterpret_cast<const char *>(&buffer), recl, recl))
io, reinterpret_cast<const char *>(&buffer), 1, recl))
<< "OutputUnformattedBlock()";
ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
@ -61,7 +70,7 @@ TEST(ExternalIOTests, TestDirectUnformatted) {
io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__);
ASSERT_TRUE(IONAME(SetRec)(io, j)) << "SetRec(" << j << ')';
ASSERT_TRUE(IONAME(InputUnformattedBlock)(
io, reinterpret_cast<char *>(&buffer), recl, recl))
io, reinterpret_cast<char *>(&buffer), 1, recl))
<< "InputUnformattedBlock()";
ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
@ -158,6 +167,17 @@ TEST(ExternalIOTests, TestSequentialFixedUnformatted) {
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<const char *>(&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 @@ TEST(ExternalIOTests, TestSequentialVariableFormatted) {
<< "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 @@ TEST(ExternalIOTests, TestSequentialVariableFormatted) {
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) {