From b8452dba28e7449d0b1e47b5341ddf0bef55be68 Mon Sep 17 00:00:00 2001 From: peter klausler Date: Wed, 20 Oct 2021 13:56:47 -0700 Subject: [PATCH] [flang] Support NAMELIST input of short arrays NAMELIST array input does not need to fully define an array. If another input item begins after at least one element, it ends input into the array and the remaining items are not modified. The tricky part of supporting this feature is that it's not always easy to determine whether the next non-blank thing in the input is a value or the next item's name, esp. in the case of logical data where T and F can be names. E.g., &group logicalArray = t f f t = 1 / should read three elements into "logicalArray" and then read an integer or real variable named "t". So the I/O runtime has to do some look-ahead to determine whether the next thing in the input is a name followed by '=', '(', or '%'. Since the '=' may be on a later record, possibly with intervening NAMELIST comments, the runtime has to support a general form of saving and restoring its current position. The infrastructure in the I/O runtime already has to support repositioning for list-directed repetition, even on non-positionable input sources like terminals and sockets; this patch adds an internal RAII API to make it easier to save a position and then do arbitrary look-ahead. Differential Revision: https://reviews.llvm.org/D112245 --- flang/runtime/connection.h | 27 +++++++++++++++++++++++ flang/runtime/descriptor-io.h | 32 ++++++++++++++++++++-------- flang/runtime/edit-input.cpp | 16 ++++++++++++++ flang/runtime/format.h | 3 +++ flang/runtime/io-stmt.h | 8 +++---- flang/runtime/namelist.cpp | 23 +++++++++++++++++++- flang/runtime/namelist.h | 7 ++++++ flang/unittests/Runtime/Namelist.cpp | 28 ++++++++++++++++++++++++ 8 files changed, 130 insertions(+), 14 deletions(-) diff --git a/flang/runtime/connection.h b/flang/runtime/connection.h index 3a2f30303199..dfeebeb522dc 100644 --- a/flang/runtime/connection.h +++ b/flang/runtime/connection.h @@ -66,5 +66,32 @@ struct ConnectionState : public ConnectionAttributes { // Mutable modes set at OPEN() that can be overridden in READ/WRITE & FORMAT MutableModes modes; // BLANK=, DECIMAL=, SIGN=, ROUND=, PAD=, DELIM=, kP }; + +// Utility class for capturing and restoring a position in an input stream. +class SavedPosition { +public: + explicit SavedPosition(ConnectionState &c) + : connection_{c}, positionInRecord_{c.positionInRecord}, + furthestPositionInRecord_{c.furthestPositionInRecord}, + leftTabLimit_{c.leftTabLimit}, previousResumptionRecordNumber_{ + c.resumptionRecordNumber} { + c.resumptionRecordNumber = c.currentRecordNumber; + } + ~SavedPosition() { + connection_.currentRecordNumber = *connection_.resumptionRecordNumber; + connection_.resumptionRecordNumber = previousResumptionRecordNumber_; + connection_.leftTabLimit = leftTabLimit_; + connection_.furthestPositionInRecord = furthestPositionInRecord_; + connection_.positionInRecord = positionInRecord_; + } + +private: + ConnectionState &connection_; + std::int64_t positionInRecord_; + std::int64_t furthestPositionInRecord_; + std::optional leftTabLimit_; + std::optional previousResumptionRecordNumber_; +}; + } // namespace Fortran::runtime::io #endif // FORTRAN_RUNTIME_IO_CONNECTION_H_ diff --git a/flang/runtime/descriptor-io.h b/flang/runtime/descriptor-io.h index 6246f3474af8..f0d3deb69d09 100644 --- a/flang/runtime/descriptor-io.h +++ b/flang/runtime/descriptor-io.h @@ -49,6 +49,7 @@ inline bool FormattedIntegerIO( SubscriptValue subscripts[maxRank]; descriptor.GetLowerBounds(subscripts); using IntType = CppTypeFor; + bool anyInput{false}; for (std::size_t j{0}; j < numElements; ++j) { if (auto edit{io.GetNextDataEdit()}) { IntType &x{ExtractElement(io, descriptor, subscripts)}; @@ -57,8 +58,10 @@ inline bool FormattedIntegerIO( return false; } } else if (edit->descriptor != DataEdit::ListDirectedNullValue) { - if (!EditIntegerInput(io, *edit, reinterpret_cast(&x), KIND)) { - return false; + if (EditIntegerInput(io, *edit, reinterpret_cast(&x), KIND)) { + anyInput = true; + } else { + return anyInput && edit->IsNamelist(); } } if (!descriptor.IncrementSubscripts(subscripts) && j + 1 < numElements) { @@ -79,6 +82,7 @@ inline bool FormattedRealIO( SubscriptValue subscripts[maxRank]; descriptor.GetLowerBounds(subscripts); using RawType = typename RealOutputEditing::BinaryFloatingPoint; + bool anyInput{false}; for (std::size_t j{0}; j < numElements; ++j) { if (auto edit{io.GetNextDataEdit()}) { RawType &x{ExtractElement(io, descriptor, subscripts)}; @@ -87,8 +91,10 @@ inline bool FormattedRealIO( return false; } } else if (edit->descriptor != DataEdit::ListDirectedNullValue) { - if (!EditRealInput(io, *edit, reinterpret_cast(&x))) { - return false; + if (EditRealInput(io, *edit, reinterpret_cast(&x))) { + anyInput = true; + } else { + return anyInput && edit->IsNamelist(); } } if (!descriptor.IncrementSubscripts(subscripts) && j + 1 < numElements) { @@ -111,6 +117,7 @@ inline bool FormattedComplexIO( bool isListOutput{ io.get_if>() != nullptr}; using RawType = typename RealOutputEditing::BinaryFloatingPoint; + bool anyInput{false}; for (std::size_t j{0}; j < numElements; ++j) { RawType *x{&ExtractElement(io, descriptor, subscripts)}; if (isListOutput) { @@ -132,9 +139,11 @@ inline bool FormattedComplexIO( } } else if (edit->descriptor == DataEdit::ListDirectedNullValue) { break; - } else if (!EditRealInput( + } else if (EditRealInput( io, *edit, reinterpret_cast(x))) { - return false; + anyInput = true; + } else { + return anyInput && edit->IsNamelist(); } } } @@ -154,6 +163,7 @@ inline bool FormattedCharacterIO( descriptor.GetLowerBounds(subscripts); std::size_t length{descriptor.ElementBytes() / sizeof(A)}; auto *listOutput{io.get_if>()}; + bool anyInput{false}; for (std::size_t j{0}; j < numElements; ++j) { A *x{&ExtractElement(io, descriptor, subscripts)}; if (listOutput) { @@ -167,8 +177,10 @@ inline bool FormattedCharacterIO( } } else { if (edit->descriptor != DataEdit::ListDirectedNullValue) { - if (!EditDefaultCharacterInput(io, *edit, x, length)) { - return false; + if (EditDefaultCharacterInput(io, *edit, x, length)) { + anyInput = true; + } else { + return anyInput && edit->IsNamelist(); } } } @@ -191,6 +203,7 @@ inline bool FormattedLogicalIO( descriptor.GetLowerBounds(subscripts); auto *listOutput{io.get_if>()}; using IntType = CppTypeFor; + bool anyInput{false}; for (std::size_t j{0}; j < numElements; ++j) { IntType &x{ExtractElement(io, descriptor, subscripts)}; if (listOutput) { @@ -207,8 +220,9 @@ inline bool FormattedLogicalIO( bool truth{}; if (EditLogicalInput(io, *edit, truth)) { x = truth; + anyInput = true; } else { - return false; + return anyInput && edit->IsNamelist(); } } } diff --git a/flang/runtime/edit-input.cpp b/flang/runtime/edit-input.cpp index 42f15562d6c7..c632865aa13a 100644 --- a/flang/runtime/edit-input.cpp +++ b/flang/runtime/edit-input.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "edit-input.h" +#include "namelist.h" #include "flang/Common/real.h" #include "flang/Common/uint128.h" #include @@ -69,6 +70,10 @@ bool EditIntegerInput( RUNTIME_CHECK(io.GetIoErrorHandler(), kind >= 1 && !(kind & (kind - 1))); switch (edit.descriptor) { case DataEdit::ListDirected: + if (IsNamelistName(io)) { + return false; + } + break; case 'G': case 'I': break; @@ -298,6 +303,10 @@ bool EditRealInput(IoStatementState &io, const DataEdit &edit, void *n) { constexpr int binaryPrecision{common::PrecisionOfRealKind(KIND)}; switch (edit.descriptor) { case DataEdit::ListDirected: + if (IsNamelistName(io)) { + return false; + } + return EditCommonRealInput(io, edit, n); case DataEdit::ListDirectedRealPart: case DataEdit::ListDirectedImaginaryPart: case 'F': @@ -326,6 +335,10 @@ bool EditRealInput(IoStatementState &io, const DataEdit &edit, void *n) { bool EditLogicalInput(IoStatementState &io, const DataEdit &edit, bool &x) { switch (edit.descriptor) { case DataEdit::ListDirected: + if (IsNamelistName(io)) { + return false; + } + break; case 'L': case 'G': break; @@ -407,6 +420,9 @@ static bool EditListDirectedDefaultCharacterInput( io.HandleRelativePosition(1); return EditDelimitedCharacterInput(io, x, length, *ch); } + if (IsNamelistName(io)) { + return false; + } // Undelimited list-directed character input: stop at a value separator // or the end of the current record. std::optional remaining{length}; diff --git a/flang/runtime/format.h b/flang/runtime/format.h index 1989aa79a98e..8dccaab969a6 100644 --- a/flang/runtime/format.h +++ b/flang/runtime/format.h @@ -51,6 +51,9 @@ struct DataEdit { return descriptor == ListDirected || descriptor == ListDirectedRealPart || descriptor == ListDirectedImaginaryPart; } + constexpr bool IsNamelist() const { + return IsListDirected() && modes.inNamelist; + } static constexpr char DefinedDerivedType{'d'}; // DT user-defined derived type diff --git a/flang/runtime/io-stmt.h b/flang/runtime/io-stmt.h index 24bbaf192364..7e683b146eb1 100644 --- a/flang/runtime/io-stmt.h +++ b/flang/runtime/io-stmt.h @@ -229,10 +229,10 @@ public: std::optional GetNextDataEdit( IoStatementState &, int maxRepeat = 1); - // Each NAMELIST input item is a distinct "list-directed" - // input statement. This member function resets this state - // so that repetition and null values work correctly for each - // successive NAMELIST input item. + // Each NAMELIST input item is treated like a distinct list-directed + // input statement. This member function resets some state so that + // repetition and null values work correctly for each successive + // NAMELIST input item. void ResetForNextNamelistItem() { remaining_ = 0; eatComma_ = false; diff --git a/flang/runtime/namelist.cpp b/flang/runtime/namelist.cpp index b9b5fbc3e4e0..c2c3d6537984 100644 --- a/flang/runtime/namelist.cpp +++ b/flang/runtime/namelist.cpp @@ -333,7 +333,7 @@ bool IONAME(InputNamelist)(Cookie cookie, const NamelistGroup &group) { return false; } io.HandleRelativePosition(1); - // Read the values into the descriptor + // Read the values into the descriptor. An array can be short. listInput->ResetForNextNamelistItem(); if (!descr::DescriptorIO(io, *useDescriptor)) { return false; @@ -352,4 +352,25 @@ bool IONAME(InputNamelist)(Cookie cookie, const NamelistGroup &group) { return true; } +bool IsNamelistName(IoStatementState &io) { + if (io.get_if>()) { + ConnectionState &connection{io.GetConnectionState()}; + if (connection.modes.inNamelist) { + SavedPosition savedPosition{connection}; + if (auto ch{io.GetNextNonBlank()}) { + if (IsLegalIdStart(*ch)) { + do { + io.HandleRelativePosition(1); + ch = io.GetCurrentChar(); + } while (ch && IsLegalIdChar(*ch)); + ch = io.GetNextNonBlank(); + // TODO: how to deal with NaN(...) ambiguity? + return ch && (ch == '=' || ch == '(' || ch == '%'); + } + } + } + } + return false; +} + } // namespace Fortran::runtime::io diff --git a/flang/runtime/namelist.h b/flang/runtime/namelist.h index 4f17553b2d92..a7c0912314f9 100644 --- a/flang/runtime/namelist.h +++ b/flang/runtime/namelist.h @@ -15,6 +15,7 @@ namespace Fortran::runtime { class Descriptor; +class IoStatementState; } // namespace Fortran::runtime namespace Fortran::runtime::io { @@ -33,5 +34,11 @@ public: std::size_t items; const Item *item; // in original declaration order }; + +// Look ahead on input for an identifier followed by a '=', '(', or '%' +// character; for use in disambiguating a name-like value (e.g. F or T) from a +// NAMELIST group item name. Always false when not reading a NAMELIST. +bool IsNamelistName(IoStatementState &); + } // namespace Fortran::runtime::io #endif // FORTRAN_RUNTIME_NAMELIST_H_ diff --git a/flang/unittests/Runtime/Namelist.cpp b/flang/unittests/Runtime/Namelist.cpp index 67336bb995a2..fd5ee6be1b79 100644 --- a/flang/unittests/Runtime/Namelist.cpp +++ b/flang/unittests/Runtime/Namelist.cpp @@ -161,4 +161,32 @@ TEST(NamelistTests, Subscripts) { EXPECT_EQ(got, expect); } +TEST(NamelistTests, ShortArrayInput) { + OwningPtr aDesc{ + MakeArray(sizeof(int))>( + std::vector{2}, std::vector(2, -1))}; + OwningPtr bDesc{ + MakeArray(sizeof(int))>( + std::vector{2}, std::vector(2, -2))}; + const NamelistGroup::Item items[]{{"a", *aDesc}, {"b", *bDesc}}; + const NamelistGroup group{"nl", 2, items}; + // Two 12-character lines of internal input + static char t1[]{"&nl a = 1 b " + " = 2 / "}; + StaticDescriptor<1, true> statDesc; + Descriptor &internalDesc{statDesc.descriptor()}; + SubscriptValue shape{2}; + internalDesc.Establish(1, 12, t1, 1, &shape, CFI_attribute_pointer); + auto inCookie{IONAME(BeginInternalArrayListInput)( + internalDesc, nullptr, 0, __FILE__, __LINE__)}; + ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group)); + auto inStatus{IONAME(EndIoStatement)(inCookie)}; + ASSERT_EQ(inStatus, 0) << "Failed namelist input subscripts, status " + << static_cast(inStatus); + EXPECT_EQ(*aDesc->ZeroBasedIndexedElement(0), 1); + EXPECT_EQ(*aDesc->ZeroBasedIndexedElement(1), -1); + EXPECT_EQ(*bDesc->ZeroBasedIndexedElement(0), 2); + EXPECT_EQ(*bDesc->ZeroBasedIndexedElement(1), -2); +} + // TODO: Internal NAMELIST error tests