[flang-commits] [flang] 6a1c3ef - [flang] Implement NAMELIST I/O in the runtime
peter klausler via flang-commits
flang-commits at lists.llvm.org
Thu May 6 11:18:45 PDT 2021
Author: peter klausler
Date: 2021-05-06T11:18:36-07:00
New Revision: 6a1c3efa051e012aaf102b7d9e7e428a58ea8ad9
URL: https://github.com/llvm/llvm-project/commit/6a1c3efa051e012aaf102b7d9e7e428a58ea8ad9
DIFF: https://github.com/llvm/llvm-project/commit/6a1c3efa051e012aaf102b7d9e7e428a58ea8ad9.diff
LOG: [flang] Implement NAMELIST I/O in the runtime
Add InputNamelist and OutputNamelist as I/O data transfer APIs
to be used with internal & external list-directed I/O; delete the
needless original namelist-specific Begin... calls.
Implement NAMELIST output and input; add basic tests.
Differential Revision: https://reviews.llvm.org/D101931
Added:
flang/runtime/namelist.cpp
flang/runtime/namelist.h
flang/unittests/RuntimeGTest/Namelist.cpp
Modified:
flang/include/flang/ISO_Fortran_binding.h
flang/lib/Lower/IO.cpp
flang/lib/Lower/RTBuilder.h
flang/runtime/CMakeLists.txt
flang/runtime/connection.cpp
flang/runtime/connection.h
flang/runtime/descriptor-io.h
flang/runtime/descriptor.cpp
flang/runtime/descriptor.h
flang/runtime/edit-input.cpp
flang/runtime/edit-output.cpp
flang/runtime/format.h
flang/runtime/io-api.cpp
flang/runtime/io-api.h
flang/runtime/io-stmt.cpp
flang/runtime/io-stmt.h
flang/runtime/unit.cpp
flang/unittests/RuntimeGTest/CMakeLists.txt
flang/unittests/RuntimeGTest/NumericalFormatTest.cpp
flang/unittests/RuntimeGTest/tools.h
Removed:
################################################################################
diff --git a/flang/include/flang/ISO_Fortran_binding.h b/flang/include/flang/ISO_Fortran_binding.h
index f6169028e17f..bbb958728f4b 100644
--- a/flang/include/flang/ISO_Fortran_binding.h
+++ b/flang/include/flang/ISO_Fortran_binding.h
@@ -13,7 +13,7 @@
#include <stddef.h>
/* Standard interface to Fortran from C and C++.
- * These interfaces are named in section 18.5 of the Fortran 2018
+ * These interfaces are named in subclause 18.5 of the Fortran 2018
* standard, with most of the actual details being left to the
* implementation.
*/
diff --git a/flang/lib/Lower/IO.cpp b/flang/lib/Lower/IO.cpp
index aae12aaf183c..cca8561b8168 100644
--- a/flang/lib/Lower/IO.cpp
+++ b/flang/lib/Lower/IO.cpp
@@ -39,12 +39,10 @@ static constexpr std::tuple<
mkIOKey(BeginInternalArrayFormattedOutput),
mkIOKey(BeginInternalArrayFormattedInput), mkIOKey(BeginInternalListOutput),
mkIOKey(BeginInternalListInput), mkIOKey(BeginInternalFormattedOutput),
- mkIOKey(BeginInternalFormattedInput), mkIOKey(BeginInternalNamelistOutput),
- mkIOKey(BeginInternalNamelistInput), mkIOKey(BeginExternalListOutput),
+ mkIOKey(BeginInternalFormattedInput), mkIOKey(BeginExternalListOutput),
mkIOKey(BeginExternalListInput), mkIOKey(BeginExternalFormattedOutput),
mkIOKey(BeginExternalFormattedInput), mkIOKey(BeginUnformattedOutput),
- mkIOKey(BeginUnformattedInput), mkIOKey(BeginExternalNamelistOutput),
- mkIOKey(BeginExternalNamelistInput), mkIOKey(BeginAsynchronousOutput),
+ mkIOKey(BeginUnformattedInput), mkIOKey(BeginAsynchronousOutput),
mkIOKey(BeginAsynchronousInput), mkIOKey(BeginWait), mkIOKey(BeginWaitAll),
mkIOKey(BeginClose), mkIOKey(BeginFlush), mkIOKey(BeginBackspace),
mkIOKey(BeginEndfile), mkIOKey(BeginRewind), mkIOKey(BeginOpenUnit),
@@ -810,7 +808,7 @@ static const auto *getIOControl(const A &stmt) {
}
/// returns true iff the expression in the parse tree is not really a format but
-/// rather a namelist variable.
+/// rather a namelist group
template <typename A>
static bool formatIsActuallyNamelist(const A &format) {
if (auto *e = std::get_if<Fortran::parser::Expr>(&format.u)) {
@@ -1159,26 +1157,20 @@ mlir::FuncOp getBeginDataTransfer(mlir::Location loc, FirOpBuilder &builder,
return getIORuntimeFunc<mkIOKey(BeginAsynchronousInput)>(loc, builder);
if (isFormatted) {
if (isIntern) {
- if (isNml)
- return getIORuntimeFunc<mkIOKey(BeginInternalNamelistInput)>(loc,
- builder);
if (isOtherIntern) {
- if (isList)
+ if (isList || isNml)
return getIORuntimeFunc<mkIOKey(BeginInternalArrayListInput)>(
loc, builder);
return getIORuntimeFunc<mkIOKey(BeginInternalArrayFormattedInput)>(
loc, builder);
}
- if (isList)
+ if (isList || isNml)
return getIORuntimeFunc<mkIOKey(BeginInternalListInput)>(loc,
builder);
return getIORuntimeFunc<mkIOKey(BeginInternalFormattedInput)>(loc,
builder);
}
- if (isNml)
- return getIORuntimeFunc<mkIOKey(BeginExternalNamelistInput)>(loc,
- builder);
- if (isList)
+ if (isList || isNml)
return getIORuntimeFunc<mkIOKey(BeginExternalListInput)>(loc, builder);
return getIORuntimeFunc<mkIOKey(BeginExternalFormattedInput)>(loc,
builder);
@@ -1189,26 +1181,20 @@ mlir::FuncOp getBeginDataTransfer(mlir::Location loc, FirOpBuilder &builder,
return getIORuntimeFunc<mkIOKey(BeginAsynchronousOutput)>(loc, builder);
if (isFormatted) {
if (isIntern) {
- if (isNml)
- return getIORuntimeFunc<mkIOKey(BeginInternalNamelistOutput)>(
- loc, builder);
if (isOtherIntern) {
- if (isList)
+ if (isList || isNml)
return getIORuntimeFunc<mkIOKey(BeginInternalArrayListOutput)>(
loc, builder);
return getIORuntimeFunc<mkIOKey(BeginInternalArrayFormattedOutput)>(
loc, builder);
}
- if (isList)
+ if (isList || isNml)
return getIORuntimeFunc<mkIOKey(BeginInternalListOutput)>(loc,
builder);
return getIORuntimeFunc<mkIOKey(BeginInternalFormattedOutput)>(loc,
builder);
}
- if (isNml)
- return getIORuntimeFunc<mkIOKey(BeginExternalNamelistOutput)>(loc,
- builder);
- if (isList)
+ if (isList || isNml)
return getIORuntimeFunc<mkIOKey(BeginExternalListOutput)>(loc, builder);
return getIORuntimeFunc<mkIOKey(BeginExternalFormattedOutput)>(loc,
builder);
diff --git a/flang/lib/Lower/RTBuilder.h b/flang/lib/Lower/RTBuilder.h
index 4b130b650dd3..38dfa6034bdd 100644
--- a/flang/lib/Lower/RTBuilder.h
+++ b/flang/lib/Lower/RTBuilder.h
@@ -164,7 +164,8 @@ constexpr TypeBuilderFunc getModel<const Fortran::runtime::Descriptor &>() {
};
}
template <>
-constexpr TypeBuilderFunc getModel<const Fortran::runtime::NamelistGroup &>() {
+constexpr TypeBuilderFunc
+getModel<const Fortran::runtime::io::NamelistGroup &>() {
return [](mlir::MLIRContext *context) -> mlir::Type {
// FIXME: a namelist group must be some well-defined data structure, use a
// tuple as a proxy for the moment
diff --git a/flang/runtime/CMakeLists.txt b/flang/runtime/CMakeLists.txt
index 781e8e5171b4..c63fd3dd5f18 100644
--- a/flang/runtime/CMakeLists.txt
+++ b/flang/runtime/CMakeLists.txt
@@ -54,6 +54,7 @@ add_flang_library(FortranRuntime
main.cpp
memory.cpp
misc-intrinsic.cpp
+ namelist.cpp
numeric.cpp
random.cpp
reduction.cpp
diff --git a/flang/runtime/connection.cpp b/flang/runtime/connection.cpp
index d61f4ce5e08d..dc6823a83024 100644
--- a/flang/runtime/connection.cpp
+++ b/flang/runtime/connection.cpp
@@ -18,6 +18,10 @@ std::size_t ConnectionState::RemainingSpaceInRecord() const {
return positionInRecord >= recl ? 0 : recl - positionInRecord;
}
+bool ConnectionState::NeedAdvance(std::size_t width) const {
+ return positionInRecord > 0 && width > RemainingSpaceInRecord();
+}
+
bool ConnectionState::IsAtEOF() const {
return endfileRecordNumber && currentRecordNumber >= *endfileRecordNumber;
}
diff --git a/flang/runtime/connection.h b/flang/runtime/connection.h
index 24aae6588aa6..6eb6b62ccab7 100644
--- a/flang/runtime/connection.h
+++ b/flang/runtime/connection.h
@@ -35,6 +35,7 @@ struct ConnectionAttributes {
struct ConnectionState : public ConnectionAttributes {
bool IsAtEOF() const; // true when read has hit EOF or endfile record
std::size_t RemainingSpaceInRecord() const;
+ bool NeedAdvance(std::size_t) const;
void HandleAbsolutePosition(std::int64_t);
void HandleRelativePosition(std::int64_t);
diff --git a/flang/runtime/descriptor-io.h b/flang/runtime/descriptor-io.h
index 49476682cd72..e664f4c9874d 100644
--- a/flang/runtime/descriptor-io.h
+++ b/flang/runtime/descriptor-io.h
@@ -32,6 +32,10 @@ inline A &ExtractElement(IoStatementState &io, const Descriptor &descriptor,
// 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 <typename A, Direction DIR>
inline bool FormattedIntegerIO(
IoStatementState &io, const Descriptor &descriptor) {
diff --git a/flang/runtime/descriptor.cpp b/flang/runtime/descriptor.cpp
index 3a750255eb23..6715afa8aee4 100644
--- a/flang/runtime/descriptor.cpp
+++ b/flang/runtime/descriptor.cpp
@@ -234,6 +234,25 @@ bool Descriptor::SubscriptsForZeroBasedElementNumber(SubscriptValue *subscript,
return true;
}
+bool Descriptor::EstablishPointerSection(const Descriptor &source,
+ const SubscriptValue *lower, const SubscriptValue *upper,
+ const SubscriptValue *stride) {
+ *this = source;
+ raw_.attribute = CFI_attribute_pointer;
+ int newRank{raw_.rank};
+ for (int j{0}; j < raw_.rank; ++j) {
+ if (!stride || stride[j] == 0) {
+ if (newRank > 0) {
+ --newRank;
+ } else {
+ return false;
+ }
+ }
+ }
+ raw_.rank = newRank;
+ return CFI_section(&raw_, &source.raw_, lower, upper, stride) == CFI_SUCCESS;
+}
+
void Descriptor::Check() const {
// TODO
}
diff --git a/flang/runtime/descriptor.h b/flang/runtime/descriptor.h
index 2ce90f39f747..d86c136faff3 100644
--- a/flang/runtime/descriptor.h
+++ b/flang/runtime/descriptor.h
@@ -314,9 +314,13 @@ class Descriptor {
return true;
}
- void Check() const;
+ // Establishes a pointer to a section or element.
+ bool EstablishPointerSection(const Descriptor &source,
+ const SubscriptValue *lower = nullptr,
+ const SubscriptValue *upper = nullptr,
+ const SubscriptValue *stride = nullptr);
- // TODO: creation of array sections
+ void Check() const;
void Dump(FILE * = stdout) const;
diff --git a/flang/runtime/edit-input.cpp b/flang/runtime/edit-input.cpp
index 08693f251b07..6ecbc164bc6f 100644
--- a/flang/runtime/edit-input.cpp
+++ b/flang/runtime/edit-input.cpp
@@ -13,26 +13,10 @@
namespace Fortran::runtime::io {
-// For fixed-width fields, initialize the number of remaining characters.
-// Skip over leading blanks, then return the first non-blank character (if any).
-static std::optional<char32_t> PrepareInput(
- IoStatementState &io, const DataEdit &edit, std::optional<int> &remaining) {
- remaining.reset();
- if (edit.descriptor == DataEdit::ListDirected) {
- io.GetNextNonBlank();
- } else {
- if (edit.width.value_or(0) > 0) {
- remaining = *edit.width;
- }
- io.SkipSpaces(remaining);
- }
- return io.NextInField(remaining);
-}
-
static bool EditBOZInput(IoStatementState &io, const DataEdit &edit, void *n,
int base, int totalBitSize) {
std::optional<int> remaining;
- std::optional<char32_t> next{PrepareInput(io, edit, remaining)};
+ std::optional<char32_t> next{io.PrepareInput(edit, remaining)};
common::UnsignedInt128 value{0};
for (; next; next = io.NextInField(remaining)) {
char32_t ch{*next};
@@ -67,7 +51,7 @@ static bool EditBOZInput(IoStatementState &io, const DataEdit &edit, void *n,
// 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 = PrepareInput(io, edit, remaining);
+ next = io.PrepareInput(edit, remaining);
bool negative{false};
if (next) {
negative = *next == '-';
@@ -249,7 +233,19 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
exponent = 0;
return 0;
}
- if (remaining) {
+ // Consume the trailing ')' of a list-directed or NAMELIST complex
+ // input value.
+ if (edit.descriptor == DataEdit::ListDirectedImaginaryPart) {
+ if (next && (*next == ' ' || *next == '\t')) {
+ next = io.NextInField(remaining);
+ }
+ if (!next) { // NextInField fails on separators like ')'
+ next = io.GetCurrentChar();
+ if (next && *next == ')') {
+ io.HandleRelativePosition(1);
+ }
+ }
+ } else if (remaining) {
while (next && (*next == ' ' || *next == '\t')) {
next = io.NextInField(remaining);
}
@@ -338,7 +334,7 @@ bool EditLogicalInput(IoStatementState &io, const DataEdit &edit, bool &x) {
return false;
}
std::optional<int> remaining;
- std::optional<char32_t> next{PrepareInput(io, edit, remaining)};
+ std::optional<char32_t> next{io.PrepareInput(edit, remaining)};
if (next && *next == '.') { // skip optional period
next = io.NextInField(remaining);
}
@@ -372,29 +368,53 @@ bool EditLogicalInput(IoStatementState &io, const DataEdit &edit, bool &x) {
// See 13.10.3.1 paragraphs 7-9 in Fortran 2018
static bool EditDelimitedCharacterInput(
IoStatementState &io, char *x, std::size_t length, char32_t delimiter) {
+ bool result{true};
while (true) {
- if (auto ch{io.GetCurrentChar()}) {
- io.HandleRelativePosition(1);
- if (*ch == delimiter) {
- ch = io.GetCurrentChar();
- if (ch && *ch == delimiter) {
- // Repeated delimiter: use as character value. Can't straddle a
- // record boundary.
+ auto ch{io.GetCurrentChar()};
+ if (!ch) {
+ if (io.AdvanceRecord()) {
+ continue;
+ } else {
+ result = false; // EOF in character value
+ break;
+ }
+ }
+ io.HandleRelativePosition(1);
+ if (*ch == delimiter) {
+ if (auto next{io.GetCurrentChar()}) {
+ if (*next == delimiter) {
+ // Repeated delimiter: use as character value
io.HandleRelativePosition(1);
- } else {
- std::fill_n(x, length, ' ');
- return true;
+ } else { // closing delimiter
+ break;
}
+ } else { // delimiter was at the end of the record
+ if (length > 0) {
+ // Look ahead on next record: if it begins with the delimiter,
+ // treat it as a split character value, ignoring both delimiters
+ ConnectionState &connection{io.GetConnectionState()};
+ auto position{connection.positionInRecord};
+ if (io.AdvanceRecord()) {
+ if (auto next{io.GetCurrentChar()}; next && *next == delimiter) {
+ // Character constant split over a record boundary
+ io.HandleRelativePosition(1);
+ continue;
+ }
+ // Not a character value split over a record boundary.
+ io.BackspaceRecord();
+ connection.HandleAbsolutePosition(position);
+ }
+ }
+ break;
}
- if (length > 0) {
- *x++ = *ch;
- --length;
- }
- } else if (!io.AdvanceRecord()) { // EOF
- std::fill_n(x, length, ' ');
- return false;
+ }
+ if (length > 0) {
+ *x++ = *ch;
+ --length;
}
}
+ std::fill_n(x, length, ' ');
+ return result;
}
static bool EditListDirectedDefaultCharacterInput(
diff --git a/flang/runtime/edit-output.cpp b/flang/runtime/edit-output.cpp
index 76f24cb07437..2c5803bb5957 100644
--- a/flang/runtime/edit-output.cpp
+++ b/flang/runtime/edit-output.cpp
@@ -74,14 +74,14 @@ bool EditIntegerOutput(IoStatementState &io, const DataEdit &edit, INT n) {
} else if (n == 0) {
leadingZeroes = 1;
}
- int total{signChars + leadingZeroes + digits};
- if (editWidth > 0 && total > editWidth) {
+ int subTotal{signChars + leadingZeroes + digits};
+ int leadingSpaces{std::max(0, editWidth - subTotal)};
+ if (editWidth > 0 && leadingSpaces + subTotal > editWidth) {
return io.EmitRepeated('*', editWidth);
}
- int leadingSpaces{std::max(0, editWidth - total)};
if (edit.IsListDirected()) {
- if (static_cast<std::size_t>(total) >
- io.GetConnectionState().RemainingSpaceInRecord() &&
+ int total{std::max(leadingSpaces, 1) + subTotal};
+ if (io.GetConnectionState().NeedAdvance(static_cast<std::size_t>(total)) &&
!io.AdvanceRecord()) {
return false;
}
@@ -135,9 +135,7 @@ bool RealOutputEditingBase::EmitPrefix(
: 0};
length += prefixLength + suffixLength;
ConnectionState &connection{io_.GetConnectionState()};
- return (connection.positionInRecord == 0 ||
- length <= connection.RemainingSpaceInRecord() ||
- io_.AdvanceRecord()) &&
+ return (!connection.NeedAdvance(length) || io_.AdvanceRecord()) &&
io_.Emit(" (", prefixLength);
} else if (width > length) {
return io_.EmitRepeated(' ', width - length);
@@ -416,7 +414,7 @@ bool RealOutputEditing<binaryPrecision>::Edit(const DataEdit &edit) {
bool ListDirectedLogicalOutput(IoStatementState &io,
ListDirectedStatementState<Direction::Output> &list, bool truth) {
- return list.EmitLeadingSpaceOrAdvance(io, 1) && io.Emit(truth ? "T" : "F", 1);
+ return list.EmitLeadingSpaceOrAdvance(io) && io.Emit(truth ? "T" : "F", 1);
}
bool EditLogicalOutput(IoStatementState &io, const DataEdit &edit, bool truth) {
@@ -436,38 +434,42 @@ bool EditLogicalOutput(IoStatementState &io, const DataEdit &edit, bool truth) {
bool ListDirectedDefaultCharacterOutput(IoStatementState &io,
ListDirectedStatementState<Direction::Output> &list, const char *x,
std::size_t length) {
- bool ok{list.EmitLeadingSpaceOrAdvance(io, length, true)};
+ bool ok{true};
MutableModes &modes{io.mutableModes()};
ConnectionState &connection{io.GetConnectionState()};
if (modes.delim) {
+ ok = ok && list.EmitLeadingSpaceOrAdvance(io);
// Value is delimited with ' or " marks, and interior
// instances of that character are doubled. When split
// over multiple lines, delimit each lines' part.
- ok &= io.Emit(&modes.delim, 1);
+ ok = ok && io.Emit(&modes.delim, 1);
for (std::size_t j{0}; j < length; ++j) {
- if (list.NeedAdvance(connection, 2)) {
- ok &= io.Emit(&modes.delim, 1) && io.AdvanceRecord() &&
+ if (connection.NeedAdvance(2)) {
+ ok = ok && io.Emit(&modes.delim, 1) && io.AdvanceRecord() &&
io.Emit(&modes.delim, 1);
}
if (x[j] == modes.delim) {
- ok &= io.EmitRepeated(modes.delim, 2);
+ ok = ok && io.EmitRepeated(modes.delim, 2);
} else {
- ok &= io.Emit(&x[j], 1);
+ ok = ok && io.Emit(&x[j], 1);
}
}
- ok &= io.Emit(&modes.delim, 1);
+ ok = ok && io.Emit(&modes.delim, 1);
} else {
// Undelimited list-directed output
+ ok = ok &&
+ list.EmitLeadingSpaceOrAdvance(
+ io, length > 0 && !list.lastWasUndelimitedCharacter());
std::size_t put{0};
- while (put < length) {
+ while (ok && put < length) {
auto chunk{std::min(length - put, connection.RemainingSpaceInRecord())};
- ok &= io.Emit(x + put, chunk);
+ ok = ok && io.Emit(x + put, chunk);
put += chunk;
if (put < length) {
- ok &= io.AdvanceRecord() && io.Emit(" ", 1);
+ ok = ok && io.AdvanceRecord() && io.Emit(" ", 1);
}
}
- list.lastWasUndelimitedCharacter = true;
+ list.set_lastWasUndelimitedCharacter(true);
}
return ok;
}
diff --git a/flang/runtime/format.h b/flang/runtime/format.h
index 3a10b88f2923..9dcd59a54a8b 100644
--- a/flang/runtime/format.h
+++ b/flang/runtime/format.h
@@ -34,13 +34,14 @@ struct MutableModes {
bool pad{true}; // PAD= mode on READ
char delim{'\0'}; // DELIM=
short scale{0}; // kP
+ bool inNamelist{false}; // skip ! comments
};
// A single edit descriptor extracted from a FORMAT
struct DataEdit {
char descriptor; // capitalized: one of A, I, B, O, Z, F, E(N/S/X), D, G
- // Special internal data edit descriptors for list-directed I/O
+ // Special internal data edit descriptors for list-directed & NAMELIST I/O
static constexpr char ListDirected{'g'}; // non-COMPLEX list-directed
static constexpr char ListDirectedRealPart{'r'}; // emit "(r," or "(r;"
static constexpr char ListDirectedImaginaryPart{'z'}; // emit "z)"
diff --git a/flang/runtime/io-api.cpp b/flang/runtime/io-api.cpp
index 1fa2fb91b779..9c2d436c8afa 100644
--- a/flang/runtime/io-api.cpp
+++ b/flang/runtime/io-api.cpp
@@ -147,9 +147,9 @@ Cookie IONAME(BeginInternalFormattedInput)(const char *internal,
format, formatLength, scratchArea, scratchBytes, sourceFile, sourceLine);
}
-template <Direction DIR>
-Cookie BeginExternalListIO(
- ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
+template <Direction DIR, template <Direction> class STATE, typename... A>
+Cookie BeginExternalListIO(const char *what, int unitNumber,
+ const char *sourceFile, int sourceLine, A &&...xs) {
Terminator terminator{sourceFile, sourceLine};
if (unitNumber == DefaultUnit) {
unitNumber = DIR == Direction::Input ? 5 : 6;
@@ -157,33 +157,33 @@ Cookie BeginExternalListIO(
ExternalFileUnit &unit{ExternalFileUnit::LookUpOrCreateAnonymous(
unitNumber, DIR, false /*!unformatted*/, terminator)};
if (unit.access == Access::Direct) {
- terminator.Crash("List-directed I/O attempted on direct access file");
+ terminator.Crash("%s attempted on direct access file", what);
return nullptr;
}
if (!unit.isUnformatted.has_value()) {
unit.isUnformatted = false;
}
if (*unit.isUnformatted) {
- terminator.Crash("List-directed I/O attempted on unformatted file");
+ terminator.Crash("%s attempted on unformatted file", what);
return nullptr;
}
IoErrorHandler handler{terminator};
unit.SetDirection(DIR, handler);
- IoStatementState &io{unit.BeginIoStatement<ExternalListIoStatementState<DIR>>(
- unit, sourceFile, sourceLine)};
+ IoStatementState &io{unit.BeginIoStatement<STATE<DIR>>(
+ std::forward<A>(xs)..., unit, sourceFile, sourceLine)};
return &io;
}
Cookie IONAME(BeginExternalListOutput)(
ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
- return BeginExternalListIO<Direction::Output>(
- unitNumber, sourceFile, sourceLine);
+ return BeginExternalListIO<Direction::Output, ExternalListIoStatementState>(
+ "List-directed output", unitNumber, sourceFile, sourceLine);
}
Cookie IONAME(BeginExternalListInput)(
ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
- return BeginExternalListIO<Direction::Input>(
- unitNumber, sourceFile, sourceLine);
+ return BeginExternalListIO<Direction::Input, ExternalListIoStatementState>(
+ "List-directed input", unitNumber, sourceFile, sourceLine);
}
template <Direction DIR>
diff --git a/flang/runtime/io-api.h b/flang/runtime/io-api.h
index 80a6de95069c..13254ce6f66e 100644
--- a/flang/runtime/io-api.h
+++ b/flang/runtime/io-api.h
@@ -18,11 +18,11 @@
namespace Fortran::runtime {
class Descriptor;
-class NamelistGroup;
} // namespace Fortran::runtime
namespace Fortran::runtime::io {
+class NamelistGroup;
class IoStatementState;
using Cookie = IoStatementState *;
using ExternalUnit = int;
@@ -70,6 +70,10 @@ constexpr std::size_t RecommendedInternalIoScratchAreaBytes(
return 32 + 8 * maxFormatParenthesesNestingDepth;
}
+// For NAMELIST I/O, use the API for the appropriate form of list-directed
+// I/O initiation and configuration, then call OutputNamelist/InputNamelist
+// below.
+
// Internal I/O to/from character arrays &/or non-default-kind character
// requires a descriptor, which is copied.
Cookie IONAME(BeginInternalArrayListOutput)(const Descriptor &,
@@ -106,16 +110,6 @@ Cookie IONAME(BeginInternalFormattedInput)(const char *internal,
void **scratchArea = nullptr, std::size_t scratchBytes = 0,
const char *sourceFile = nullptr, int sourceLine = 0);
-// Internal namelist I/O
-Cookie IONAME(BeginInternalNamelistOutput)(const Descriptor &,
- const NamelistGroup &, void **scratchArea = nullptr,
- std::size_t scratchBytes = 0, const char *sourceFile = nullptr,
- int sourceLine = 0);
-Cookie IONAME(BeginInternalNamelistInput)(const Descriptor &,
- const NamelistGroup &, void **scratchArea = nullptr,
- std::size_t scratchBytes = 0, const char *sourceFile = nullptr,
- int sourceLine = 0);
-
// External synchronous I/O initiation
Cookie IONAME(BeginExternalListOutput)(ExternalUnit = DefaultUnit,
const char *sourceFile = nullptr, int sourceLine = 0);
@@ -131,12 +125,6 @@ Cookie IONAME(BeginUnformattedOutput)(ExternalUnit = DefaultUnit,
const char *sourceFile = nullptr, int sourceLine = 0);
Cookie IONAME(BeginUnformattedInput)(ExternalUnit = DefaultUnit,
const char *sourceFile = nullptr, int sourceLine = 0);
-Cookie IONAME(BeginExternalNamelistOutput)(const NamelistGroup &,
- ExternalUnit = DefaultUnit, const char *sourceFile = nullptr,
- int sourceLine = 0);
-Cookie IONAME(BeginExternalNamelistInput)(const NamelistGroup &,
- ExternalUnit = DefaultUnit, const char *sourceFile = nullptr,
- int sourceLine = 0);
// Asynchronous I/O is supported (at most) for unformatted direct access
// block transfers.
@@ -215,7 +203,7 @@ bool IONAME(SetRound)(Cookie, const char *, std::size_t);
// SIGN=PLUS, SUPPRESS, PROCESSOR_DEFINED
bool IONAME(SetSign)(Cookie, const char *, std::size_t);
-// Data item transfer for modes other than namelist.
+// Data item transfer for modes other than NAMELIST:
// Any data object that can be passed as an actual argument without the
// use of a temporary can be transferred by means of a descriptor;
// vector-valued subscripts and coindexing will require elementwise
@@ -254,6 +242,11 @@ bool IONAME(InputAscii)(Cookie, char *, std::size_t);
bool IONAME(OutputLogical)(Cookie, bool);
bool IONAME(InputLogical)(Cookie, bool &);
+// NAMELIST I/O must be the only data item in an (otherwise)
+// list-directed I/O statement.
+bool IONAME(OutputNamelist)(Cookie, const NamelistGroup &);
+bool IONAME(InputNamelist)(Cookie, const NamelistGroup &);
+
// Additional specifier interfaces for the connection-list of
// on OPEN statement (only). SetBlank(), SetDecimal(),
// SetDelim(), GetIoMsg(), SetPad(), SetRound(), & SetSign()
diff --git a/flang/runtime/io-stmt.cpp b/flang/runtime/io-stmt.cpp
index 89279b3bcc1b..099d9038a8ac 100644
--- a/flang/runtime/io-stmt.cpp
+++ b/flang/runtime/io-stmt.cpp
@@ -427,6 +427,20 @@ bool IoStatementState::EmitField(
}
}
+std::optional<char32_t> IoStatementState::PrepareInput(
+ const DataEdit &edit, std::optional<int> &remaining) {
+ remaining.reset();
+ if (edit.descriptor == DataEdit::ListDirected) {
+ GetNextNonBlank();
+ } else {
+ if (edit.width.value_or(0) > 0) {
+ remaining = *edit.width;
+ }
+ SkipSpaces(remaining);
+ }
+ return NextInField(remaining);
+}
+
std::optional<char32_t> IoStatementState::SkipSpaces(
std::optional<int> &remaining) {
while (!remaining || *remaining > 0) {
@@ -447,7 +461,7 @@ std::optional<char32_t> IoStatementState::SkipSpaces(
std::optional<char32_t> IoStatementState::NextInField(
std::optional<int> &remaining) {
- if (!remaining) { // list-directed or namelist: check for separators
+ if (!remaining) { // list-directed or NAMELIST: check for separators
if (auto next{GetCurrentChar()}) {
switch (*next) {
case ' ':
@@ -494,8 +508,9 @@ std::optional<char32_t> IoStatementState::NextInField(
std::optional<char32_t> IoStatementState::GetNextNonBlank() {
auto ch{GetCurrentChar()};
- while (!ch || *ch == ' ' || *ch == '\t') {
- if (ch) {
+ bool inNamelist{GetConnectionState().modes.inNamelist};
+ while (!ch || *ch == ' ' || *ch == '\t' || (inNamelist && *ch == '!')) {
+ if (ch && (*ch == ' ' || *ch == '\t')) {
HandleRelativePosition(1);
} else if (!AdvanceRecord()) {
return std::nullopt;
@@ -505,12 +520,6 @@ std::optional<char32_t> IoStatementState::GetNextNonBlank() {
return ch;
}
-bool ListDirectedStatementState<Direction::Output>::NeedAdvance(
- const ConnectionState &connection, std::size_t width) const {
- return connection.positionInRecord > 0 &&
- width > connection.RemainingSpaceInRecord();
-}
-
bool IoStatementState::Inquire(
InquiryKeywordHash inquiry, char *out, std::size_t chars) {
return std::visit(
@@ -538,9 +547,9 @@ bool ListDirectedStatementState<Direction::Output>::EmitLeadingSpaceOrAdvance(
}
const ConnectionState &connection{io.GetConnectionState()};
int space{connection.positionInRecord == 0 ||
- !(isCharacter && lastWasUndelimitedCharacter)};
- lastWasUndelimitedCharacter = false;
- if (NeedAdvance(connection, space + length)) {
+ !(isCharacter && lastWasUndelimitedCharacter())};
+ set_lastWasUndelimitedCharacter(false);
+ if (connection.NeedAdvance(space + length)) {
return io.AdvanceRecord();
}
if (space) {
@@ -596,10 +605,6 @@ ListDirectedStatementState<Direction::Input>::GetNextDataEdit(
auto ch{io.GetNextNonBlank()};
if (imaginaryPart_) {
imaginaryPart_ = false;
- if (ch && *ch == ')') {
- io.HandleRelativePosition(1);
- ch = io.GetNextNonBlank();
- }
} else if (realPart_) {
realPart_ = false;
imaginaryPart_ = true;
@@ -621,6 +626,8 @@ ListDirectedStatementState<Direction::Input>::GetNextDataEdit(
return edit;
}
// Consume comma & whitespace after previous item.
+ // This includes the comma between real and imaginary components
+ // in list-directed/NAMELIST complex input.
io.HandleRelativePosition(1);
ch = io.GetNextNonBlank();
if (!ch) {
diff --git a/flang/runtime/io-stmt.h b/flang/runtime/io-stmt.h
index 000b1ac3238f..b76c5202619b 100644
--- a/flang/runtime/io-stmt.h
+++ b/flang/runtime/io-stmt.h
@@ -93,9 +93,16 @@ class IoStatementState {
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);
+
std::optional<char32_t> SkipSpaces(std::optional<int> &remaining);
std::optional<char32_t> NextInField(std::optional<int> &remaining);
- std::optional<char32_t> GetNextNonBlank(); // can advance record
+ // Skips spaces, advances records, and ignores NAMELIST comments
+ std::optional<char32_t> GetNextNonBlank();
template <Direction D> void CheckFormattedStmtType(const char *name) {
if (!get_if<FormattedIoStatementState>() ||
@@ -148,19 +155,25 @@ struct IoStatementBase : public DefaultFormatControlCallbacks {
void BadInquiryKeywordHashCrash(InquiryKeywordHash);
};
-// Common state for list-directed internal & external I/O
+// Common state for list-directed & NAMELIST I/O, both internal & external
template <Direction> class ListDirectedStatementState;
template <>
class ListDirectedStatementState<Direction::Output>
: public FormattedIoStatementState {
public:
- static std::size_t RemainingSpaceInRecord(const ConnectionState &);
- bool NeedAdvance(const ConnectionState &, std::size_t) const;
bool EmitLeadingSpaceOrAdvance(
- IoStatementState &, std::size_t, bool isCharacter = false);
+ IoStatementState &, std::size_t = 1, bool isCharacter = false);
std::optional<DataEdit> GetNextDataEdit(
IoStatementState &, int maxRepeat = 1);
- bool lastWasUndelimitedCharacter{false};
+ bool lastWasUndelimitedCharacter() const {
+ return lastWasUndelimitedCharacter_;
+ }
+ void set_lastWasUndelimitedCharacter(bool yes = true) {
+ lastWasUndelimitedCharacter_ = yes;
+ }
+
+private:
+ bool lastWasUndelimitedCharacter_{false};
};
template <>
class ListDirectedStatementState<Direction::Input>
diff --git a/flang/runtime/namelist.cpp b/flang/runtime/namelist.cpp
new file mode 100644
index 000000000000..f26ae849dbd2
--- /dev/null
+++ b/flang/runtime/namelist.cpp
@@ -0,0 +1,309 @@
+//===-- runtime/namelist.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 "namelist.h"
+#include "descriptor-io.h"
+#include "io-api.h"
+#include "io-stmt.h"
+#include <cstring>
+#include <limits>
+
+namespace Fortran::runtime::io {
+
+bool IONAME(OutputNamelist)(Cookie cookie, const NamelistGroup &group) {
+ IoStatementState &io{*cookie};
+ io.CheckFormattedStmtType<Direction::Output>("OutputNamelist");
+ ConnectionState &connection{io.GetConnectionState()};
+ connection.modes.inNamelist = true;
+ // Internal functions to advance records and convert case
+ const auto EmitWithAdvance{[&](char ch) -> bool {
+ return (!connection.NeedAdvance(1) || io.AdvanceRecord()) &&
+ io.Emit(&ch, 1);
+ }};
+ const auto EmitUpperCase{[&](const char *str) -> bool {
+ if (connection.NeedAdvance(std::strlen(str)) &&
+ !(io.AdvanceRecord() && io.Emit(" ", 1))) {
+ return false;
+ }
+ for (; *str; ++str) {
+ char up{*str >= 'a' && *str <= 'z' ? static_cast<char>(*str - 'a' + 'A')
+ : *str};
+ if (!io.Emit(&up, 1)) {
+ return false;
+ }
+ }
+ return true;
+ }};
+ // &GROUP
+ if (!(EmitWithAdvance('&') && EmitUpperCase(group.groupName))) {
+ return false;
+ }
+ for (std::size_t j{0}; j < group.items; ++j) {
+ // [,]ITEM=...
+ const NamelistGroup::Item &item{group.item[j]};
+ if (!(EmitWithAdvance(j == 0 ? ' ' : ',') && EmitUpperCase(item.name) &&
+ EmitWithAdvance('=') &&
+ descr::DescriptorIO<Direction::Output>(io, item.descriptor))) {
+ return false;
+ }
+ }
+ // terminal /
+ return EmitWithAdvance('/');
+}
+
+static bool GetLowerCaseName(
+ IoStatementState &io, char buffer[], std::size_t maxLength) {
+ if (auto ch{io.GetCurrentChar()}) {
+ static const auto IsLegalIdStart{[](char32_t ch) -> bool {
+ return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') ||
+ ch == '_' || ch == '@' || ch == '$';
+ }};
+ if (IsLegalIdStart(*ch)) {
+ std::size_t j{0};
+ do {
+ buffer[j] =
+ static_cast<char>(*ch >= 'A' && *ch <= 'Z' ? *ch - 'A' + 'a' : *ch);
+ io.HandleRelativePosition(1);
+ ch = io.GetCurrentChar();
+ } while (++j < maxLength && ch &&
+ (IsLegalIdStart(*ch) || (*ch >= '0' && *ch <= '9')));
+ buffer[j++] = '\0';
+ if (j <= maxLength) {
+ return true;
+ }
+ io.GetIoErrorHandler().SignalError(
+ "Identifier '%s...' in NAMELIST input group is too long", buffer);
+ }
+ }
+ return false;
+}
+
+static std::optional<SubscriptValue> GetSubscriptValue(IoStatementState &io) {
+ std::optional<SubscriptValue> value;
+ std::optional<char32_t> ch{io.GetCurrentChar()};
+ bool negate{ch && *ch == '-'};
+ if (negate) {
+ io.HandleRelativePosition(1);
+ ch = io.GetCurrentChar();
+ }
+ bool overflow{false};
+ while (ch && *ch >= '0' && *ch <= '9') {
+ SubscriptValue was{value.value_or(0)};
+ overflow |= was >= std::numeric_limits<SubscriptValue>::max() / 10;
+ value = 10 * was + *ch - '0';
+ io.HandleRelativePosition(1);
+ ch = io.GetCurrentChar();
+ }
+ if (overflow) {
+ io.GetIoErrorHandler().SignalError(
+ "NAMELIST input subscript value overflow");
+ return std::nullopt;
+ }
+ if (negate) {
+ if (value) {
+ return -*value;
+ } else {
+ io.HandleRelativePosition(-1); // give back '-' with no digits
+ }
+ }
+ return value;
+}
+
+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; it's nonstandard, but not ambiguous
+ // within the parentheses
+ SubscriptValue lower[maxRank], upper[maxRank], stride[maxRank];
+ int j{0};
+ std::size_t elemLen{source.ElementBytes()};
+ bool ok{true};
+ std::optional<char32_t> ch{io.GetNextNonBlank()};
+ for (; ch && *ch != ')'; ++j) {
+ SubscriptValue dimLower{0}, dimUpper{0}, dimStride{0};
+ if (j < maxRank && j < source.rank()) {
+ const Dimension &dim{source.GetDimension(j)};
+ dimLower = dim.LowerBound();
+ dimUpper = dim.UpperBound();
+ dimStride = elemLen ? dim.ByteStride() / elemLen : 1;
+ } else if (ok) {
+ handler.SignalError(
+ "Too many subscripts for rank-%d NAMELIST group item '%s'",
+ source.rank(), name);
+ ok = false;
+ }
+ if (auto low{GetSubscriptValue(io)}) {
+ if (*low < dimLower || (dimUpper >= dimLower && *low > dimUpper)) {
+ if (ok) {
+ handler.SignalError("Subscript %jd out of range %jd..%jd in NAMELIST "
+ "group item '%s' dimension %d",
+ static_cast<std::intmax_t>(*low),
+ static_cast<std::intmax_t>(dimLower),
+ static_cast<std::intmax_t>(dimUpper), name, j + 1);
+ ok = false;
+ }
+ } else {
+ dimLower = *low;
+ }
+ ch = io.GetNextNonBlank();
+ }
+ if (ch && *ch == ':') {
+ io.HandleRelativePosition(1);
+ ch = io.GetNextNonBlank();
+ if (auto high{GetSubscriptValue(io)}) {
+ if (*high > dimUpper) {
+ if (ok) {
+ handler.SignalError(
+ "Subscript triplet upper bound %jd out of range (>%jd) in "
+ "NAMELIST group item '%s' dimension %d",
+ static_cast<std::intmax_t>(*high),
+ static_cast<std::intmax_t>(dimUpper), name, j + 1);
+ ok = false;
+ }
+ } else {
+ dimUpper = *high;
+ }
+ ch = io.GetNextNonBlank();
+ }
+ if (ch && *ch == ':') {
+ io.HandleRelativePosition(1);
+ ch = io.GetNextNonBlank();
+ if (auto str{GetSubscriptValue(io)}) {
+ dimStride = *str;
+ ch = io.GetNextNonBlank();
+ }
+ }
+ } else { // scalar
+ dimUpper = dimLower;
+ dimStride = 0;
+ }
+ if (ch && *ch == ',') {
+ io.HandleRelativePosition(1);
+ ch = io.GetNextNonBlank();
+ }
+ if (ok) {
+ lower[j] = dimLower;
+ upper[j] = dimUpper;
+ stride[j] = dimStride;
+ }
+ }
+ if (ok) {
+ if (ch && *ch == ')') {
+ io.HandleRelativePosition(1);
+ if (desc.EstablishPointerSection(source, lower, upper, stride)) {
+ return true;
+ } else {
+ handler.SignalError(
+ "Bad subscripts for NAMELIST input group item '%s'", name);
+ }
+ } else {
+ handler.SignalError(
+ "Bad subscripts (missing ')') for NAMELIST input group item '%s'",
+ name);
+ }
+ }
+ return false;
+}
+
+bool IONAME(InputNamelist)(Cookie cookie, const NamelistGroup &group) {
+ IoStatementState &io{*cookie};
+ io.CheckFormattedStmtType<Direction::Input>("InputNamelist");
+ ConnectionState &connection{io.GetConnectionState()};
+ connection.modes.inNamelist = true;
+ IoErrorHandler &handler{io.GetIoErrorHandler()};
+ // Check the group header
+ std::optional<char32_t> next{io.GetNextNonBlank()};
+ if (!next || *next != '&') {
+ handler.SignalError(
+ "NAMELIST input group does not begin with '&' (at '%lc')", *next);
+ return false;
+ }
+ io.HandleRelativePosition(1);
+ char name[101];
+ if (!GetLowerCaseName(io, name, sizeof name)) {
+ handler.SignalError("NAMELIST input group has no name");
+ return false;
+ }
+ RUNTIME_CHECK(handler, group.groupName != nullptr);
+ if (std::strcmp(group.groupName, name) != 0) {
+ handler.SignalError(
+ "NAMELIST input group name '%s' is not the expected '%s'", name,
+ group.groupName);
+ return false;
+ }
+ // Read the group's items
+ while (true) {
+ next = io.GetNextNonBlank();
+ if (!next || *next == '/') {
+ break;
+ }
+ if (!GetLowerCaseName(io, name, sizeof name)) {
+ handler.SignalError(
+ "NAMELIST input group '%s' was not terminated", group.groupName);
+ return false;
+ }
+ std::size_t itemIndex{0};
+ for (; itemIndex < group.items; ++itemIndex) {
+ if (std::strcmp(name, group.item[itemIndex].name) == 0) {
+ break;
+ }
+ }
+ if (itemIndex >= group.items) {
+ handler.SignalError(
+ "'%s' is not an item in NAMELIST group '%s'", name, group.groupName);
+ return false;
+ }
+ // Handle indexing and components, if any. No spaces are allowed.
+ // A copy of the descriptor is made if necessary.
+ const Descriptor &itemDescriptor{group.item[itemIndex].descriptor};
+ const Descriptor *useDescriptor{&itemDescriptor};
+ StaticDescriptor<maxRank, true, 16> staticDesc[2];
+ int whichStaticDesc{0};
+ next = io.GetCurrentChar();
+ if (next && (*next == '(' || *next == '%')) {
+ do {
+ if (*next == '(') {
+ Descriptor &mutableDescriptor{
+ staticDesc[whichStaticDesc].descriptor()};
+ whichStaticDesc ^= 1;
+ HandleSubscripts(io, mutableDescriptor, *useDescriptor, name);
+ useDescriptor = &mutableDescriptor;
+ } else {
+ handler.Crash("unimplemented: component references in NAMELIST");
+ }
+ next = io.GetCurrentChar();
+ } while (next && (*next == '(' || *next == '%'));
+ }
+ // Skip the '='
+ next = io.GetNextNonBlank();
+ if (!next || *next != '=') {
+ handler.SignalError("No '=' found after item '%s' in NAMELIST group '%s'",
+ name, group.groupName);
+ return false;
+ }
+ io.HandleRelativePosition(1);
+ // Read the values into the descriptor
+ if (!descr::DescriptorIO<Direction::Input>(io, *useDescriptor)) {
+ return false;
+ }
+ next = io.GetNextNonBlank();
+ if (next && *next == ',') {
+ io.HandleRelativePosition(1);
+ }
+ }
+ if (!next || *next != '/') {
+ handler.SignalError(
+ "No '/' found after NAMELIST group '%s'", group.groupName);
+ return false;
+ }
+ io.HandleRelativePosition(1);
+ return true;
+}
+
+} // namespace Fortran::runtime::io
diff --git a/flang/runtime/namelist.h b/flang/runtime/namelist.h
new file mode 100644
index 000000000000..4f17553b2d92
--- /dev/null
+++ b/flang/runtime/namelist.h
@@ -0,0 +1,37 @@
+//===-- runtime/namelist.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
+//
+//===----------------------------------------------------------------------===//
+
+// Defines the data structure used for NAMELIST I/O
+
+#ifndef FORTRAN_RUNTIME_NAMELIST_H_
+#define FORTRAN_RUNTIME_NAMELIST_H_
+
+#include <cstddef>
+
+namespace Fortran::runtime {
+class Descriptor;
+} // namespace Fortran::runtime
+
+namespace Fortran::runtime::io {
+
+// A NAMELIST group is a named ordered collection of distinct variable names.
+// It is packaged by lowering into an instance of this class.
+// If all the items are variables with fixed addresses, the NAMELIST group
+// description can be in a read-only section.
+class NamelistGroup {
+public:
+ struct Item {
+ const char *name; // NUL-terminated lower-case
+ const Descriptor &descriptor;
+ };
+ const char *groupName; // NUL-terminated lower-case
+ std::size_t items;
+ const Item *item; // in original declaration order
+};
+} // namespace Fortran::runtime::io
+#endif // FORTRAN_RUNTIME_NAMELIST_H_
diff --git a/flang/runtime/unit.cpp b/flang/runtime/unit.cpp
index 646908f51d92..5a256c21bf40 100644
--- a/flang/runtime/unit.cpp
+++ b/flang/runtime/unit.cpp
@@ -446,14 +446,16 @@ bool ExternalFileUnit::AdvanceRecord(IoErrorHandler &handler) {
// headers &/or footers
std::uint32_t length;
length = furthestPositionInRecord - sizeof length;
- ok &= Emit(reinterpret_cast<const char *>(&length), sizeof length,
- sizeof length, handler);
+ ok = ok &&
+ Emit(reinterpret_cast<const char *>(&length), sizeof length,
+ sizeof length, handler);
positionInRecord = 0;
- ok &= Emit(reinterpret_cast<const char *>(&length), sizeof length,
- sizeof length, handler);
+ ok = ok &&
+ Emit(reinterpret_cast<const char *>(&length), sizeof length,
+ sizeof length, handler);
} else {
// Terminate formatted variable length record
- ok &= Emit("\n", 1, 1, handler); // TODO: Windows CR+LF
+ ok = ok && Emit("\n", 1, 1, handler); // TODO: Windows CR+LF
}
}
frameOffsetInFile_ +=
diff --git a/flang/unittests/RuntimeGTest/CMakeLists.txt b/flang/unittests/RuntimeGTest/CMakeLists.txt
index cc29f31c829d..cad827a8a966 100644
--- a/flang/unittests/RuntimeGTest/CMakeLists.txt
+++ b/flang/unittests/RuntimeGTest/CMakeLists.txt
@@ -3,6 +3,7 @@ add_flang_unittest(FlangRuntimeTests
CrashHandlerFixture.cpp
Format.cpp
MiscIntrinsic.cpp
+ Namelist.cpp
Numeric.cpp
NumericalFormatTest.cpp
Random.cpp
diff --git a/flang/unittests/RuntimeGTest/Namelist.cpp b/flang/unittests/RuntimeGTest/Namelist.cpp
new file mode 100644
index 000000000000..fc38cee47f86
--- /dev/null
+++ b/flang/unittests/RuntimeGTest/Namelist.cpp
@@ -0,0 +1,164 @@
+//===-- flang/unittests/RuntimeGTest/Namelist.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 "../../runtime/namelist.h"
+#include "CrashHandlerFixture.h"
+#include "tools.h"
+#include "../../runtime/descriptor.h"
+#include "../../runtime/io-api.h"
+#include <algorithm>
+#include <cinttypes>
+#include <complex>
+#include <cstring>
+#include <gtest/gtest.h>
+#include <limits>
+#include <string>
+#include <vector>
+
+using namespace Fortran::runtime;
+using namespace Fortran::runtime::io;
+
+struct NamelistTests : CrashHandlerFixture {};
+
+static void ClearDescriptorStorage(const Descriptor &descriptor) {
+ std::memset(descriptor.raw().base_addr, 0,
+ descriptor.Elements() * descriptor.ElementBytes());
+}
+
+TEST(NamelistTests, BasicSanity) {
+ static constexpr int numLines{12};
+ static constexpr int lineLength{32};
+ static char buffer[numLines][lineLength];
+ StaticDescriptor<1> statDescs[1];
+ Descriptor &internalDesc{statDescs[0].descriptor()};
+ SubscriptValue extent[]{numLines};
+ internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/lineLength,
+ &buffer, 1, extent, CFI_attribute_pointer);
+ // Set up data arrays
+ std::vector<int> ints;
+ for (int j{0}; j < 20; ++j) {
+ ints.push_back(j % 2 == 0 ? (1 << j) : -(1 << j));
+ }
+ std::vector<double> reals{0.0, -0.0, std::numeric_limits<double>::infinity(),
+ -std::numeric_limits<double>::infinity(),
+ std::numeric_limits<double>::quiet_NaN(),
+ std::numeric_limits<double>::max(), std::numeric_limits<double>::lowest(),
+ std::numeric_limits<double>::epsilon()};
+ std::vector<std::uint8_t> logicals;
+ logicals.push_back(false);
+ logicals.push_back(true);
+ logicals.push_back(false);
+ std::vector<std::complex<float>> complexes;
+ complexes.push_back(std::complex<float>{123.0, -0.5});
+ std::vector<std::string> characters;
+ characters.emplace_back("aBcDeFgHiJkLmNoPqRsTuVwXyZ");
+ characters.emplace_back("0123456789'\" ");
+ // Copy the data into new descriptors
+ OwningPtr<Descriptor> intDesc{
+ MakeArray<TypeCategory::Integer, static_cast<int>(sizeof(int))>(
+ std::vector<int>{5, 4}, std::move(ints))};
+ OwningPtr<Descriptor> realDesc{
+ MakeArray<TypeCategory::Real, static_cast<int>(sizeof(double))>(
+ std::vector<int>{4, 2}, std::move(reals))};
+ OwningPtr<Descriptor> logicalDesc{
+ MakeArray<TypeCategory::Logical, static_cast<int>(sizeof(std::uint8_t))>(
+ std::vector<int>{3}, std::move(logicals))};
+ OwningPtr<Descriptor> complexDesc{
+ MakeArray<TypeCategory::Complex, static_cast<int>(sizeof(float))>(
+ std::vector<int>{}, std::move(complexes))};
+ OwningPtr<Descriptor> characterDesc{MakeArray<TypeCategory::Character, 1>(
+ std::vector<int>{2}, std::move(characters), characters[0].size())};
+ // Create a NAMELIST group
+ static constexpr int items{5};
+ const NamelistGroup::Item itemArray[items]{{"ints", *intDesc},
+ {"reals", *realDesc}, {"logicals", *logicalDesc},
+ {"complexes", *complexDesc}, {"characters", *characterDesc}};
+ const NamelistGroup group{"group1", items, itemArray};
+ // Do an internal NAMELIST write and check results
+ auto outCookie1{IONAME(BeginInternalArrayListOutput)(
+ internalDesc, nullptr, 0, __FILE__, __LINE__)};
+ ASSERT_TRUE(IONAME(SetDelim)(outCookie1, "APOSTROPHE", 10));
+ ASSERT_TRUE(IONAME(OutputNamelist)(outCookie1, group));
+ auto outStatus1{IONAME(EndIoStatement)(outCookie1)};
+ ASSERT_EQ(outStatus1, 0) << "Failed namelist output sanity, status "
+ << static_cast<int>(outStatus1);
+
+ static const std::string expect{"&GROUP1 INTS= 1 -2 4 -8 16 -32 "
+ " 64 -128 256 -512 1024 -2048 "
+ " 4096 -8192 16384 -32768 65536 "
+ " -131072 262144 -524288,REALS= "
+ " 0. -0. Inf -Inf NaN "
+ " 1.7976931348623157E+308 "
+ " -1.7976931348623157E+308 "
+ " 2.220446049250313E-16,LOGICALS="
+ "F T F,COMPLEXES= (123.,-.5), "
+ " CHARACTERS= 'aBcDeFgHiJkLmNoPq'"
+ "'RsTuVwXyZ' '0123456789''\" '"
+ "' '/ "};
+ std::string got{buffer[0], sizeof buffer};
+ EXPECT_EQ(got, expect);
+
+ // Clear the arrays, read them back, write out again, and compare
+ ClearDescriptorStorage(*intDesc);
+ ClearDescriptorStorage(*realDesc);
+ ClearDescriptorStorage(*logicalDesc);
+ ClearDescriptorStorage(*complexDesc);
+ ClearDescriptorStorage(*characterDesc);
+ 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 sanity, status "
+ << static_cast<int>(inStatus);
+ auto outCookie2{IONAME(BeginInternalArrayListOutput)(
+ internalDesc, nullptr, 0, __FILE__, __LINE__)};
+ ASSERT_TRUE(IONAME(SetDelim)(outCookie2, "APOSTROPHE", 10));
+ ASSERT_TRUE(IONAME(OutputNamelist)(outCookie2, group));
+ auto outStatus2{IONAME(EndIoStatement)(outCookie2)};
+ ASSERT_EQ(outStatus2, 0) << "Failed namelist output sanity rewrite, status "
+ << static_cast<int>(outStatus2);
+ std::string got2{buffer[0], sizeof buffer};
+ EXPECT_EQ(got2, expect);
+}
+
+TEST(NamelistTests, Subscripts) {
+ // INTEGER :: A(-1:0, -1:1)
+ OwningPtr<Descriptor> aDesc{
+ MakeArray<TypeCategory::Integer, static_cast<int>(sizeof(int))>(
+ std::vector<int>{2, 3}, std::vector<int>(6, 0))};
+ aDesc->GetDimension(0).SetBounds(-1, 0);
+ aDesc->GetDimension(1).SetBounds(-1, 1);
+ const NamelistGroup::Item items[]{{"a", *aDesc}};
+ const NamelistGroup group{"justa", 1, items};
+ static char t1[]{"&justa A(0,1:-1:-2)=1 2/"};
+ StaticDescriptor<1> statDescs[2];
+ Descriptor &internalDesc{statDescs[0].descriptor()};
+ internalDesc.Establish(TypeCode{CFI_type_char},
+ /*elementBytes=*/std::strlen(t1), t1, 0, nullptr, 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<int>(inStatus);
+ char out[40];
+ internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/sizeof out,
+ out, 0, nullptr, CFI_attribute_pointer);
+ auto outCookie{IONAME(BeginInternalArrayListOutput)(
+ internalDesc, nullptr, 0, __FILE__, __LINE__)};
+ ASSERT_TRUE(IONAME(OutputNamelist)(outCookie, group));
+ auto outStatus{IONAME(EndIoStatement)(outCookie)};
+ ASSERT_EQ(outStatus, 0)
+ << "Failed namelist output subscripts rewrite, status "
+ << static_cast<int>(outStatus);
+ std::string got{out, sizeof out};
+ static const std::string expect{"&JUSTA A= 0 2 0 0 0 1/ "};
+ EXPECT_EQ(got, expect);
+}
+
+// TODO: Internal NAMELIST error tests
diff --git a/flang/unittests/RuntimeGTest/NumericalFormatTest.cpp b/flang/unittests/RuntimeGTest/NumericalFormatTest.cpp
index 7788c436cdab..470c2c6ce3bc 100644
--- a/flang/unittests/RuntimeGTest/NumericalFormatTest.cpp
+++ b/flang/unittests/RuntimeGTest/NumericalFormatTest.cpp
@@ -34,11 +34,10 @@ static bool CompareFormattedStrings(
static bool CompareFormatReal(
const char *format, double x, const char *expect) {
char buffer[800];
- auto *cookie{IONAME(BeginInternalFormattedOutput)(
+ auto cookie{IONAME(BeginInternalFormattedOutput)(
buffer, sizeof buffer, format, std::strlen(format))};
- IONAME(OutputReal64)(cookie, x);
+ EXPECT_TRUE(IONAME(OutputReal64)(cookie, x));
auto status{IONAME(EndIoStatement)(cookie)};
-
EXPECT_EQ(status, 0);
return CompareFormattedStrings(expect, std::string{buffer, sizeof buffer});
}
@@ -61,7 +60,7 @@ TEST(IOApiTests, HelloWorldOutputTest) {
// Create format for all types and values to be written
const char *format{"(6HHELLO,,A6,2X,I3,1X,'0x',Z8,1X,L1)"};
- auto *cookie{IONAME(BeginInternalFormattedOutput)(
+ auto cookie{IONAME(BeginInternalFormattedOutput)(
buffer, bufferSize, format, std::strlen(format))};
// Write string, integer, and logical values to buffer
@@ -86,21 +85,21 @@ TEST(IOApiTests, MultilineOutputTest) {
// Allocate buffer for multiline output
static constexpr int numLines{5};
static constexpr int lineLength{32};
- static char buffer[numLines][lineLength];
+ char buffer[numLines][lineLength];
// Create descriptor for entire buffer
static constexpr int staticDescriptorMaxRank{1};
- static StaticDescriptor<staticDescriptorMaxRank> wholeStaticDescriptor;
- static Descriptor &whole{wholeStaticDescriptor.descriptor()};
- static SubscriptValue extent[]{numLines};
+ StaticDescriptor<staticDescriptorMaxRank> wholeStaticDescriptor;
+ Descriptor &whole{wholeStaticDescriptor.descriptor()};
+ static const SubscriptValue extent[]{numLines};
whole.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/lineLength, &buffer,
staticDescriptorMaxRank, extent, CFI_attribute_pointer);
whole.Dump(stderr);
whole.Check();
// Create descriptor for buffer section
- static StaticDescriptor<staticDescriptorMaxRank> sectionStaticDescriptor;
- static Descriptor §ion{sectionStaticDescriptor.descriptor()};
+ StaticDescriptor<staticDescriptorMaxRank> sectionStaticDescriptor;
+ Descriptor §ion{sectionStaticDescriptor.descriptor()};
static const SubscriptValue lowers[]{0}, uppers[]{4}, strides[]{1};
section.Establish(whole.type(), /*elementBytes=*/whole.ElementBytes(),
nullptr, /*maxRank=*/staticDescriptorMaxRank, extent,
@@ -116,7 +115,7 @@ TEST(IOApiTests, MultilineOutputTest) {
// Create format string and initialize IO operation
const char *format{
"('?abcde,',T1,'>',T9,A,TL12,A,TR25,'<'//G0,17X,'abcd',1(2I4))"};
- static auto *cookie{IONAME(BeginInternalArrayFormattedOutput)(
+ auto cookie{IONAME(BeginInternalArrayFormattedOutput)(
section, format, std::strlen(format))};
// Write data to buffer
@@ -138,18 +137,19 @@ TEST(IOApiTests, MultilineOutputTest) {
" 888 999 "
" "};
// Ensure formatted string matches expected output
- ASSERT_TRUE(
+ EXPECT_TRUE(
CompareFormattedStrings(expect, std::string{buffer[0], sizeof buffer}))
- << "Expected " << expect << " but got " << buffer;
+ << "Expected '" << expect << "' but got '"
+ << std::string{buffer[0], sizeof buffer} << "'";
}
TEST(IOApiTests, ListInputTest) {
static const char input[]{",1*,(5.,6..)"};
- static auto *cookie{IONAME(BeginInternalListInput)(input, sizeof input - 1)};
+ auto cookie{IONAME(BeginInternalListInput)(input, sizeof input - 1)};
// Create real values for IO tests
static constexpr int numRealValues{6};
- static float z[numRealValues];
+ float z[numRealValues];
for (int j{0}; j < numRealValues; ++j) {
z[j] = -(j + 1);
}
@@ -161,13 +161,13 @@ TEST(IOApiTests, ListInputTest) {
}
// Ensure no IO errors occured during IO operations above
- static auto status{IONAME(EndIoStatement)(cookie)};
+ auto status{IONAME(EndIoStatement)(cookie)};
ASSERT_EQ(status, 0) << "Failed complex list-directed input, status "
<< static_cast<int>(status);
// Ensure writing complex values from floats does not result in an error
static constexpr int bufferSize{33};
- static char output[bufferSize];
+ char output[bufferSize];
output[bufferSize - 1] = '\0';
cookie = IONAME(BeginInternalListOutput)(output, bufferSize - 1);
for (int j{0}; j < numRealValues; j += 2) {
@@ -189,22 +189,22 @@ TEST(IOApiTests, ListInputTest) {
}
TEST(IOApiTests, DescriptorOutputTest) {
- static constexpr int bufferSize{9};
- static char buffer[bufferSize];
- static const char *format{"(2A4)"};
- static auto *cookie{IONAME(BeginInternalFormattedOutput)(
+ static constexpr int bufferSize{10};
+ char buffer[bufferSize];
+ const char *format{"(2A4)"};
+ auto cookie{IONAME(BeginInternalFormattedOutput)(
buffer, bufferSize, format, std::strlen(format))};
// Create descriptor for output
static constexpr int staticDescriptorMaxRank{1};
- static StaticDescriptor<staticDescriptorMaxRank> staticDescriptor;
- static Descriptor &desc{staticDescriptor.descriptor()};
+ StaticDescriptor<staticDescriptorMaxRank> staticDescriptor;
+ Descriptor &desc{staticDescriptor.descriptor()};
static constexpr int subscriptExtent{2};
static const SubscriptValue extent[]{subscriptExtent};
// Manually write to descriptor buffer
static constexpr int dataLength{4};
- static char data[subscriptExtent][dataLength];
+ char data[subscriptExtent][dataLength];
std::memcpy(data[0], "ABCD", dataLength);
std::memcpy(data[1], "EFGH", dataLength);
desc.Establish(TypeCode{CFI_type_char}, dataLength, &data,
@@ -214,28 +214,32 @@ TEST(IOApiTests, DescriptorOutputTest) {
IONAME(OutputDescriptor)(cookie, desc);
// Ensure no errors were encountered in initializing the cookie and descriptor
- static auto formatStatus{IONAME(EndIoStatement)(cookie)};
+ auto formatStatus{IONAME(EndIoStatement)(cookie)};
ASSERT_EQ(formatStatus, 0)
<< "descrOutputTest: '" << format << "' failed, status "
<< static_cast<int>(formatStatus);
// Ensure buffer matches expected output
- ASSERT_TRUE(
- CompareFormattedStrings("ABCDEFGH ", std::string{buffer, sizeof buffer}));
+ EXPECT_TRUE(
+ CompareFormattedStrings("ABCDEFGH ", std::string{buffer, sizeof buffer}))
+ << "descrOutputTest: formatted: got '"
+ << std::string{buffer, sizeof buffer} << "'";
// Begin list-directed output on cookie by descriptor
cookie = IONAME(BeginInternalListOutput)(buffer, sizeof buffer);
IONAME(OutputDescriptor)(cookie, desc);
// Ensure list-directed output does not result in an IO error
- static auto listDirectedStatus{IONAME(EndIoStatement)(cookie)};
+ auto listDirectedStatus{IONAME(EndIoStatement)(cookie)};
ASSERT_EQ(listDirectedStatus, 0)
<< "descrOutputTest: list-directed failed, status "
<< static_cast<int>(listDirectedStatus);
// Ensure buffer matches expected output
- ASSERT_TRUE(
- CompareFormattedStrings(" ABCDEFGH", std::string{buffer, sizeof buffer}));
+ EXPECT_TRUE(
+ CompareFormattedStrings(" ABCDEFGH ", std::string{buffer, sizeof buffer}))
+ << "descrOutputTest: list-directed: got '"
+ << std::string{buffer, sizeof buffer} << "'";
}
//------------------------------------------------------------------------------
@@ -608,7 +612,7 @@ TEST(IOApiTests, FormatDoubleValues) {
}
using IndividualTestCaseTy = std::tuple<const char *, double, const char *>;
- static std::vector<IndividualTestCaseTy> individualTestCases{
+ static const std::vector<IndividualTestCaseTy> individualTestCases{
{"(F5.3,';')", 25., "*****;"},
{"(F5.3,';')", 2.5, "2.500;"},
{"(F5.3,';')", 0.25, "0.250;"},
@@ -638,7 +642,7 @@ TEST(IOApiTests, FormatDoubleValues) {
// Ensure double input values correctly map to raw uint64 values
TEST(IOApiTests, FormatDoubleInputValues) {
using TestCaseTy = std::tuple<const char *, const char *, std::uint64_t>;
- static std::vector<TestCaseTy> testCases{
+ static const std::vector<TestCaseTy> testCases{
{"(F18.0)", " 0", 0x0},
{"(F18.0)", " ", 0x0},
{"(F18.0)", " -0", 0x8000000000000000},
@@ -663,7 +667,7 @@ TEST(IOApiTests, FormatDoubleInputValues) {
{"(DC,F18.0)", " 12,5", 0x4029000000000000},
};
for (auto const &[format, data, want] : testCases) {
- auto *cookie{IONAME(BeginInternalFormattedInput)(
+ auto cookie{IONAME(BeginInternalFormattedInput)(
data, std::strlen(data), format, std::strlen(format))};
union {
double x;
@@ -676,12 +680,12 @@ TEST(IOApiTests, FormatDoubleInputValues) {
IONAME(InputReal64)(cookie, u.x);
static constexpr int bufferSize{65};
- static char iomsg[bufferSize];
+ char iomsg[bufferSize];
std::memset(iomsg, '\0', bufferSize - 1);
// Ensure no errors were encountered reading input buffer into union value
IONAME(GetIoMsg)(cookie, iomsg, bufferSize - 1);
- static auto status{IONAME(EndIoStatement)(cookie)};
+ auto status{IONAME(EndIoStatement)(cookie)};
ASSERT_EQ(status, 0) << '\'' << format << "' failed reading '" << data
<< "', status " << static_cast<int>(status)
<< " iomsg '" << iomsg << "'";
diff --git a/flang/unittests/RuntimeGTest/tools.h b/flang/unittests/RuntimeGTest/tools.h
index c2c31dcef414..bca579b5e1af 100644
--- a/flang/unittests/RuntimeGTest/tools.h
+++ b/flang/unittests/RuntimeGTest/tools.h
@@ -34,7 +34,8 @@ static void StoreElement(
template <TypeCategory CAT, int KIND, typename A>
static OwningPtr<Descriptor> MakeArray(const std::vector<int> &shape,
- const std::vector<A> &data, std::size_t elemLen = KIND) {
+ const std::vector<A> &data,
+ std::size_t elemLen = CAT == TypeCategory::Complex ? 2 * KIND : KIND) {
auto rank{static_cast<int>(shape.size())};
auto result{Descriptor::Create(TypeCode{CAT, KIND}, elemLen, nullptr, rank,
nullptr, CFI_attribute_allocatable)};
More information about the flang-commits
mailing list