aboutsummaryrefslogtreecommitdiff
path: root/flang-rt
diff options
context:
space:
mode:
Diffstat (limited to 'flang-rt')
-rw-r--r--flang-rt/cmake/modules/AddFlangRT.cmake21
-rw-r--r--flang-rt/include/flang-rt/runtime/format.h8
-rw-r--r--flang-rt/include/flang-rt/runtime/io-stmt.h22
-rw-r--r--flang-rt/lib/runtime/descriptor-io.cpp26
-rw-r--r--flang-rt/lib/runtime/edit-input.cpp97
-rw-r--r--flang-rt/lib/runtime/io-stmt.cpp51
-rw-r--r--flang-rt/lib/runtime/namelist.cpp3
-rw-r--r--flang-rt/lib/runtime/unit.h3
-rw-r--r--flang-rt/test/NonGtestUnit/lit.cfg.py3
-rw-r--r--flang-rt/unittests/CMakeLists.txt23
-rw-r--r--flang-rt/unittests/Runtime/CMakeLists.txt1
-rw-r--r--flang-rt/unittests/Runtime/InputExtensions.cpp106
-rw-r--r--flang-rt/unittests/Runtime/NumericalFormatTest.cpp2
13 files changed, 269 insertions, 97 deletions
diff --git a/flang-rt/cmake/modules/AddFlangRT.cmake b/flang-rt/cmake/modules/AddFlangRT.cmake
index e51590f..5854160 100644
--- a/flang-rt/cmake/modules/AddFlangRT.cmake
+++ b/flang-rt/cmake/modules/AddFlangRT.cmake
@@ -286,27 +286,6 @@ function (add_flangrt_library name)
target_compile_options(${tgtname} PUBLIC -U_LIBCPP_ENABLE_ASSERTIONS)
endif ()
- # Flang/Clang (including clang-cl) -compiled programs targeting the MSVC ABI
- # should only depend on msvcrt/ucrt. LLVM still emits libgcc/compiler-rt
- # functions in some cases like 128-bit integer math (__udivti3, __modti3,
- # __fixsfti, __floattidf, ...) that msvc does not support. We are injecting a
- # dependency to Compiler-RT's builtin library where these are implemented.
- if (MSVC AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
- if (FLANG_RT_BUILTINS_LIBRARY)
- target_compile_options(${tgtname} PRIVATE "$<$<COMPILE_LANGUAGE:CXX,C>:-Xclang>" "$<$<COMPILE_LANGUAGE:CXX,C>:--dependent-lib=${FLANG_RT_BUILTINS_LIBRARY}>")
- endif ()
- endif ()
- if (MSVC AND CMAKE_Fortran_COMPILER_ID STREQUAL "LLVMFlang")
- if (FLANG_RT_BUILTINS_LIBRARY)
- target_compile_options(${tgtname} PRIVATE "$<$<COMPILE_LANGUAGE:Fortran>:-Xflang>" "$<$<COMPILE_LANGUAGE:Fortran>:--dependent-lib=${FLANG_RT_BUILTINS_LIBRARY}>")
- else ()
- message(WARNING "Did not find libclang_rt.builtins.lib.
- LLVM may emit builtins that are not implemented in msvcrt/ucrt and
- instead falls back to builtins from Compiler-RT. Linking with ${tgtname}
- may result in a linker error.")
- endif ()
- endif ()
-
# Non-GTest unittests depend on LLVMSupport
if (ARG_LINK_TO_LLVM)
if (LLVM_LINK_LLVM_DYLIB)
diff --git a/flang-rt/include/flang-rt/runtime/format.h b/flang-rt/include/flang-rt/runtime/format.h
index 9469cb0..89f815f 100644
--- a/flang-rt/include/flang-rt/runtime/format.h
+++ b/flang-rt/include/flang-rt/runtime/format.h
@@ -36,6 +36,14 @@ enum EditingFlags {
};
struct MutableModes {
+ // Handle DC or DECIMAL='COMMA' and determine the active separator character
+ constexpr RT_API_ATTRS char32_t GetSeparatorChar() const {
+ return editingFlags & decimalComma ? char32_t{';'} : char32_t{','};
+ }
+ constexpr RT_API_ATTRS char32_t GetRadixPointChar() const {
+ return editingFlags & decimalComma ? char32_t{','} : char32_t{'.'};
+ }
+
std::uint8_t editingFlags{0}; // BN, DP, SS
enum decimal::FortranRounding round{
executionEnvironment
diff --git a/flang-rt/include/flang-rt/runtime/io-stmt.h b/flang-rt/include/flang-rt/runtime/io-stmt.h
index bfd5f69..1d680d7 100644
--- a/flang-rt/include/flang-rt/runtime/io-stmt.h
+++ b/flang-rt/include/flang-rt/runtime/io-stmt.h
@@ -84,6 +84,9 @@ public:
// This design avoids virtual member functions and function pointers,
// which may not have good support in some runtime environments.
+ RT_API_ATTRS const NonTbpDefinedIoTable *nonTbpDefinedIoTable() const;
+ RT_API_ATTRS void set_nonTbpDefinedIoTable(const NonTbpDefinedIoTable *);
+
// CompleteOperation() is the last opportunity to raise an I/O error.
// It is called by EndIoStatement(), but it can be invoked earlier to
// catch errors for (e.g.) GetIoMsg() and GetNewUnit(). If called
@@ -363,6 +366,13 @@ public:
using IoErrorHandler::IoErrorHandler;
RT_API_ATTRS bool completedOperation() const { return completedOperation_; }
+ RT_API_ATTRS const NonTbpDefinedIoTable *nonTbpDefinedIoTable() const {
+ return nonTbpDefinedIoTable_;
+ }
+ RT_API_ATTRS void set_nonTbpDefinedIoTable(
+ const NonTbpDefinedIoTable *table) {
+ nonTbpDefinedIoTable_ = table;
+ }
RT_API_ATTRS void CompleteOperation() { completedOperation_ = true; }
RT_API_ATTRS int EndIoStatement() { return GetIoStat(); }
@@ -397,6 +407,11 @@ public:
protected:
bool completedOperation_{false};
+
+private:
+ // Original NonTbpDefinedIoTable argument to Input/OutputDerivedType,
+ // saved here so that it can also be used in child I/O statements.
+ const NonTbpDefinedIoTable *nonTbpDefinedIoTable_{nullptr};
};
// Common state for list-directed & NAMELIST I/O, both internal & external
@@ -630,8 +645,10 @@ class ChildIoStatementState : public IoStatementBase,
public:
RT_API_ATTRS ChildIoStatementState(
ChildIo &, const char *sourceFile = nullptr, int sourceLine = 0);
+ RT_API_ATTRS const NonTbpDefinedIoTable *nonTbpDefinedIoTable() const;
+ RT_API_ATTRS void set_nonTbpDefinedIoTable(const NonTbpDefinedIoTable *);
RT_API_ATTRS ChildIo &child() { return child_; }
- RT_API_ATTRS MutableModes &mutableModes();
+ RT_API_ATTRS MutableModes &mutableModes() { return mutableModes_; }
RT_API_ATTRS ConnectionState &GetConnectionState();
RT_API_ATTRS ExternalFileUnit *GetExternalFileUnit() const;
RT_API_ATTRS int EndIoStatement();
@@ -644,6 +661,7 @@ public:
private:
ChildIo &child_;
+ MutableModes mutableModes_;
};
template <Direction DIR, typename CHAR>
@@ -654,7 +672,6 @@ public:
RT_API_ATTRS ChildFormattedIoStatementState(ChildIo &, const CharType *format,
std::size_t formatLength, const Descriptor *formatDescriptor = nullptr,
const char *sourceFile = nullptr, int sourceLine = 0);
- RT_API_ATTRS MutableModes &mutableModes() { return mutableModes_; }
RT_API_ATTRS void CompleteOperation();
RT_API_ATTRS int EndIoStatement();
RT_API_ATTRS bool AdvanceRecord(int = 1);
@@ -664,7 +681,6 @@ public:
}
private:
- MutableModes mutableModes_;
FormatControl<ChildFormattedIoStatementState> format_;
};
diff --git a/flang-rt/lib/runtime/descriptor-io.cpp b/flang-rt/lib/runtime/descriptor-io.cpp
index 3868c8d..e22fc79 100644
--- a/flang-rt/lib/runtime/descriptor-io.cpp
+++ b/flang-rt/lib/runtime/descriptor-io.cpp
@@ -47,15 +47,19 @@ static RT_API_ATTRS Fortran::common::optional<bool> DefinedFormattedIo(
const typeInfo::DerivedType &derived,
const typeInfo::SpecialBinding &special,
const SubscriptValue subscripts[]) {
- Fortran::common::optional<DataEdit> peek{
- io.GetNextDataEdit(0 /*to peek at it*/)};
+ // Look at the next data edit descriptor. If this is list-directed I/O, the
+ // "maxRepeat=0" argument will prevent the input from advancing over an
+ // initial '(' that shouldn't be consumed now as the start of a real part.
+ Fortran::common::optional<DataEdit> peek{io.GetNextDataEdit(/*maxRepeat=*/0)};
if (peek &&
(peek->descriptor == DataEdit::DefinedDerivedType ||
- peek->descriptor == DataEdit::ListDirected)) {
+ peek->descriptor == DataEdit::ListDirected ||
+ peek->descriptor == DataEdit::ListDirectedRealPart)) {
// Defined formatting
IoErrorHandler &handler{io.GetIoErrorHandler()};
- DataEdit edit{*io.GetNextDataEdit(1)}; // now consume it; no repeats
- RUNTIME_CHECK(handler, edit.descriptor == peek->descriptor);
+ DataEdit edit{peek->descriptor == DataEdit::ListDirectedRealPart
+ ? *peek
+ : *io.GetNextDataEdit(1)};
char ioType[2 + edit.maxIoTypeChars];
auto ioTypeLen{std::size_t{2} /*"DT"*/ + edit.ioTypeChars};
if (edit.descriptor == DataEdit::DefinedDerivedType) {
@@ -836,13 +840,23 @@ template RT_API_ATTRS int DescriptorIoTicket<Direction::Input>::Continue(
template <Direction DIR>
RT_API_ATTRS bool DescriptorIO(IoStatementState &io,
- const Descriptor &descriptor, const NonTbpDefinedIoTable *table) {
+ const Descriptor &descriptor, const NonTbpDefinedIoTable *originalTable) {
bool anyIoTookPlace{false};
+ const NonTbpDefinedIoTable *defaultTable{io.nonTbpDefinedIoTable()};
+ const NonTbpDefinedIoTable *table{originalTable};
+ if (!table) {
+ table = defaultTable;
+ } else if (table != defaultTable) {
+ io.set_nonTbpDefinedIoTable(table); // for nested I/O
+ }
WorkQueue workQueue{io.GetIoErrorHandler()};
if (workQueue.BeginDescriptorIo<DIR>(io, descriptor, table, anyIoTookPlace) ==
StatContinue) {
workQueue.Run();
}
+ if (defaultTable != table) {
+ io.set_nonTbpDefinedIoTable(defaultTable);
+ }
return anyIoTookPlace;
}
diff --git a/flang-rt/lib/runtime/edit-input.cpp b/flang-rt/lib/runtime/edit-input.cpp
index 1355767..3a8abf3 100644
--- a/flang-rt/lib/runtime/edit-input.cpp
+++ b/flang-rt/lib/runtime/edit-input.cpp
@@ -19,14 +19,10 @@
namespace Fortran::runtime::io {
RT_OFFLOAD_API_GROUP_BEGIN
-// Handle DC or DECIMAL='COMMA' and determine the active separator character
-static inline RT_API_ATTRS char32_t GetSeparatorChar(const DataEdit &edit) {
- return edit.modes.editingFlags & decimalComma ? char32_t{';'} : char32_t{','};
-}
-
static inline RT_API_ATTRS bool IsCharValueSeparator(
const DataEdit &edit, char32_t ch) {
- return ch == ' ' || ch == '\t' || ch == '/' || ch == GetSeparatorChar(edit) ||
+ return ch == ' ' || ch == '\t' || ch == '/' ||
+ ch == edit.modes.GetSeparatorChar() ||
(edit.IsNamelist() && (ch == '&' || ch == '$'));
}
@@ -37,9 +33,7 @@ static RT_API_ATTRS bool CheckCompleteListDirectedField(
if (edit.IsListDirected()) {
std::size_t byteCount;
if (auto ch{io.GetCurrentChar(byteCount)}) {
- if (IsCharValueSeparator(edit, *ch)) {
- return true;
- } else {
+ if (!IsCharValueSeparator(edit, *ch)) {
const auto &connection{io.GetConnectionState()};
io.GetIoErrorHandler().SignalError(IostatBadListDirectedInputSeparator,
"invalid character (0x%x) after list-directed input value, "
@@ -49,12 +43,9 @@ static RT_API_ATTRS bool CheckCompleteListDirectedField(
static_cast<int>(connection.currentRecordNumber));
return false;
}
- } else {
- return true; // end of record: ok
}
- } else {
- return true;
}
+ return true;
}
template <int LOG2_BASE>
@@ -73,7 +64,7 @@ static RT_API_ATTRS bool EditBOZInput(
// Count significant digits after any leading white space & zeroes
int digits{0};
int significantBits{0};
- const char32_t comma{GetSeparatorChar(edit)};
+ char32_t comma{edit.modes.GetSeparatorChar()};
for (; next; next = io.NextInField(remaining, edit)) {
char32_t ch{*next};
if (ch == ' ' || ch == '\t') {
@@ -161,10 +152,6 @@ static RT_API_ATTRS bool EditBOZInput(
return CheckCompleteListDirectedField(io, edit);
}
-static inline RT_API_ATTRS char32_t GetRadixPointChar(const DataEdit &edit) {
- return edit.modes.editingFlags & decimalComma ? char32_t{','} : char32_t{'.'};
-}
-
// Prepares input from a field, and returns the sign, if any, else '\0'.
static RT_API_ATTRS char ScanNumericPrefix(IoStatementState &io,
const DataEdit &edit, Fortran::common::optional<char32_t> &next,
@@ -226,7 +213,7 @@ RT_API_ATTRS bool EditIntegerInput(IoStatementState &io, const DataEdit &edit,
common::uint128_t value{0};
bool any{!!sign};
bool overflow{false};
- const char32_t comma{GetSeparatorChar(edit)};
+ char32_t comma{edit.modes.GetSeparatorChar()};
static constexpr auto maxu128{~common::uint128_t{0}};
for (; next; next = io.NextInField(remaining, edit, &fastField)) {
char32_t ch{*next};
@@ -243,7 +230,7 @@ RT_API_ATTRS bool EditIntegerInput(IoStatementState &io, const DataEdit &edit,
} else if (ch == comma) {
break; // end non-list-directed field early
} else {
- if (edit.modes.inNamelist && ch == GetRadixPointChar(edit)) {
+ if (edit.modes.inNamelist && ch == edit.modes.GetRadixPointChar()) {
// Ignore any fractional part that might appear in NAMELIST integer
// input, like a few other Fortran compilers do.
// TODO: also process exponents? Some compilers do, but they obviously
@@ -349,8 +336,8 @@ static RT_API_ATTRS ScannedRealInput ScanRealInput(
}
bool bzMode{(edit.modes.editingFlags & blankZero) != 0};
int exponent{0};
- if (!next || (!bzMode && *next == ' ') ||
- (!(edit.modes.editingFlags & decimalComma) && *next == ',')) {
+ char32_t comma{edit.modes.GetSeparatorChar()};
+ if (!next || (!bzMode && *next == ' ') || *next == comma) {
if (!edit.IsListDirected() && !io.GetConnectionState().IsAtEOF()) {
// An empty/blank field means zero when not list-directed.
// A fixed-width field containing only a sign is also zero;
@@ -360,7 +347,7 @@ static RT_API_ATTRS ScannedRealInput ScanRealInput(
}
return {got, exponent, false};
}
- char32_t radixPointChar{GetRadixPointChar(edit)};
+ char32_t radixPointChar{edit.modes.GetRadixPointChar()};
char32_t first{*next >= 'a' && *next <= 'z' ? *next + 'A' - 'a' : *next};
bool isHexadecimal{false};
if (first == 'N' || first == 'I') {
@@ -375,21 +362,37 @@ static RT_API_ATTRS ScannedRealInput ScanRealInput(
Put(*next);
}
}
- if (next && *next == '(') { // NaN(...)
- Put('(');
- int depth{1};
- while (true) {
- next = io.NextInField(remaining, edit);
- if (depth == 0) {
- break;
- } else if (!next) {
- return {}; // error
- } else if (*next == '(') {
- ++depth;
- } else if (*next == ')') {
- --depth;
+ if (first == 'N' && (!next || *next == '(') &&
+ remaining.value_or(1) > 0) { // NaN(...)?
+ std::size_t byteCount{0};
+ if (!next) { // NextInField won't return '(' for list-directed
+ next = io.GetCurrentChar(byteCount);
+ }
+ if (next && *next == '(') {
+ int depth{1};
+ while (true) {
+ if (*next >= 'a' && *next <= 'z') {
+ *next = *next - 'a' + 'A';
+ }
+ Put(*next);
+ io.HandleRelativePosition(byteCount);
+ io.GotChar(byteCount);
+ if (remaining) {
+ *remaining -= byteCount;
+ }
+ if (depth == 0) {
+ break; // done
+ }
+ next = io.GetCurrentChar(byteCount);
+ if (!next || remaining.value_or(1) < 1) {
+ return {}; // error
+ } else if (*next == '(') {
+ ++depth;
+ } else if (*next == ')') {
+ --depth;
+ }
}
- Put(*next);
+ next = io.NextInField(remaining, edit);
}
}
} else if (first == radixPointChar || (first >= '0' && first <= '9') ||
@@ -507,7 +510,7 @@ static RT_API_ATTRS ScannedRealInput ScanRealInput(
} else if (radixPointOffset) {
exponent += *radixPointOffset;
} else {
- // When no redix point (or comma) appears in the value, the 'd'
+ // When no radix point (or comma) appears in the value, the 'd'
// part of the edit descriptor must be interpreted as the number of
// digits in the value to be interpreted as being to the *right* of
// the assumed radix point (13.7.2.3.2)
@@ -521,9 +524,11 @@ static RT_API_ATTRS ScannedRealInput ScanRealInput(
io.SkipSpaces(remaining);
next = io.NextInField(remaining, edit);
}
- if (!next) { // NextInField fails on separators like ')'
+ if (!next || *next == ')') { // NextInField fails on separators like ')'
std::size_t byteCount{0};
- next = io.GetCurrentChar(byteCount);
+ if (!next) {
+ next = io.GetCurrentChar(byteCount);
+ }
if (next && *next == ')') {
io.HandleRelativePosition(byteCount);
}
@@ -532,7 +537,7 @@ static RT_API_ATTRS ScannedRealInput ScanRealInput(
while (next && (*next == ' ' || *next == '\t')) {
next = io.NextInField(remaining, edit);
}
- if (next && (*next != ',' || (edit.modes.editingFlags & decimalComma))) {
+ if (next && *next != comma) {
return {}; // error: unused nonblank character in fixed-width field
}
}
@@ -946,10 +951,12 @@ RT_API_ATTRS bool EditLogicalInput(
"Bad character '%lc' in LOGICAL input field", *next);
return false;
}
- if (remaining) { // ignore the rest of a fixed-width field
- io.HandleRelativePosition(*remaining);
- } else if (edit.descriptor == DataEdit::ListDirected) {
- while (io.NextInField(remaining, edit)) { // discard rest of field
+ if (remaining || edit.descriptor == DataEdit::ListDirected) {
+ // Ignore the rest of the input field; stop after separator when
+ // not list-directed.
+ char32_t comma{edit.modes.GetSeparatorChar()};
+ while (next && *next != comma) {
+ next = io.NextInField(remaining, edit);
}
}
return CheckCompleteListDirectedField(io, edit);
diff --git a/flang-rt/lib/runtime/io-stmt.cpp b/flang-rt/lib/runtime/io-stmt.cpp
index 8056c8d..36bffd4 100644
--- a/flang-rt/lib/runtime/io-stmt.cpp
+++ b/flang-rt/lib/runtime/io-stmt.cpp
@@ -526,6 +526,17 @@ Fortran::common::optional<DataEdit> IoStatementState::GetNextDataEdit(int n) {
[&](auto &x) { return x.get().GetNextDataEdit(*this, n); }, u_);
}
+const NonTbpDefinedIoTable *IoStatementState::nonTbpDefinedIoTable() const {
+ return common::visit(
+ [&](auto &x) { return x.get().nonTbpDefinedIoTable(); }, u_);
+}
+
+void IoStatementState::set_nonTbpDefinedIoTable(
+ const NonTbpDefinedIoTable *table) {
+ common::visit(
+ [&](auto &x) { return x.get().set_nonTbpDefinedIoTable(table); }, u_);
+}
+
bool IoStatementState::Emit(
const char *data, std::size_t bytes, std::size_t elementBytes) {
return common::visit(
@@ -633,10 +644,10 @@ IoStatementState::FastAsciiField IoStatementState::GetUpcomingFastAsciiField() {
if (!connection.isUTF8 && connection.internalIoCharKind <= 1) {
const char *p{nullptr};
if (std::size_t bytes{GetNextInputBytes(p)}) {
- return FastAsciiField(connection, p, bytes);
+ return FastAsciiField{connection, p, bytes};
}
}
- return FastAsciiField(connection);
+ return FastAsciiField{connection};
}
Fortran::common::optional<char32_t> IoStatementState::NextInField(
@@ -828,10 +839,7 @@ ListDirectedStatementState<Direction::Input>::GetNextDataEdit(
edit.descriptor = DataEdit::ListDirectedNullValue;
return edit;
}
- char32_t comma{','};
- if (edit.modes.editingFlags & decimalComma) {
- comma = ';';
- }
+ const char32_t comma{edit.modes.GetSeparatorChar()};
std::size_t byteCount{0};
if (remaining_ > 0 && !realPart_) { // "r*c" repetition in progress
RUNTIME_CHECK(io.GetIoErrorHandler(), repeatPosition_.has_value());
@@ -920,9 +928,12 @@ ListDirectedStatementState<Direction::Input>::GetNextDataEdit(
fastField.connection().positionInRecord = start;
}
}
- if (!imaginaryPart_ && ch && *ch == '(') {
- realPart_ = true;
- fastField.connection().HandleRelativePosition(byteCount);
+ if (!imaginaryPart_ && edit.descriptor == DataEdit::ListDirected && ch &&
+ *ch == '(') {
+ if (maxRepeat > 0) { // not being peeked at fram DefinedFormattedIo()
+ realPart_ = true;
+ fastField.connection().HandleRelativePosition(byteCount);
+ }
edit.descriptor = DataEdit::ListDirectedRealPart;
}
return edit;
@@ -952,12 +963,24 @@ bool ExternalUnformattedIoStatementState<DIR>::Receive(
template <Direction DIR>
ChildIoStatementState<DIR>::ChildIoStatementState(
ChildIo &child, const char *sourceFile, int sourceLine)
- : IoStatementBase{sourceFile, sourceLine}, child_{child} {}
+ : IoStatementBase{sourceFile, sourceLine}, child_{child},
+ mutableModes_{child.parent().mutableModes()} {}
template <Direction DIR>
-MutableModes &ChildIoStatementState<DIR>::mutableModes() {
+const NonTbpDefinedIoTable *
+ChildIoStatementState<DIR>::nonTbpDefinedIoTable() const {
#if !defined(RT_DEVICE_AVOID_RECURSION)
- return child_.parent().mutableModes();
+ return child_.parent().nonTbpDefinedIoTable();
+#else
+ ReportUnsupportedChildIo();
+#endif
+}
+
+template <Direction DIR>
+void ChildIoStatementState<DIR>::set_nonTbpDefinedIoTable(
+ const NonTbpDefinedIoTable *table) {
+#if !defined(RT_DEVICE_AVOID_RECURSION)
+ child_.parent().set_nonTbpDefinedIoTable(table);
#else
ReportUnsupportedChildIo();
#endif
@@ -1030,9 +1053,7 @@ ChildFormattedIoStatementState<DIR, CHAR>::ChildFormattedIoStatementState(
ChildIo &child, const CHAR *format, std::size_t formatLength,
const Descriptor *formatDescriptor, const char *sourceFile, int sourceLine)
: ChildIoStatementState<DIR>{child, sourceFile, sourceLine},
- mutableModes_{child.parent().mutableModes()}, format_{*this, format,
- formatLength,
- formatDescriptor} {}
+ format_{*this, format, formatLength, formatDescriptor} {}
template <Direction DIR, typename CHAR>
void ChildFormattedIoStatementState<DIR, CHAR>::CompleteOperation() {
diff --git a/flang-rt/lib/runtime/namelist.cpp b/flang-rt/lib/runtime/namelist.cpp
index 1bef387..2325ca1 100644
--- a/flang-rt/lib/runtime/namelist.cpp
+++ b/flang-rt/lib/runtime/namelist.cpp
@@ -27,8 +27,7 @@ RT_VAR_GROUP_END
RT_OFFLOAD_API_GROUP_BEGIN
static inline RT_API_ATTRS char32_t GetComma(IoStatementState &io) {
- return io.mutableModes().editingFlags & decimalComma ? char32_t{';'}
- : char32_t{','};
+ return io.mutableModes().GetSeparatorChar();
}
bool IODEF(OutputNamelist)(Cookie cookie, const NamelistGroup &group) {
diff --git a/flang-rt/lib/runtime/unit.h b/flang-rt/lib/runtime/unit.h
index 9aec9b1..f266a48 100644
--- a/flang-rt/lib/runtime/unit.h
+++ b/flang-rt/lib/runtime/unit.h
@@ -161,9 +161,6 @@ public:
lock_.Take();
#endif
A &state{u_.emplace<A>(std::forward<X>(xs)...)};
- if constexpr (!std::is_same_v<A, OpenStatementState>) {
- state.mutableModes() = ConnectionState::modes;
- }
directAccessRecWasSet_ = false;
io_.emplace(state);
return *io_;
diff --git a/flang-rt/test/NonGtestUnit/lit.cfg.py b/flang-rt/test/NonGtestUnit/lit.cfg.py
index 4bee709..8da36ad 100644
--- a/flang-rt/test/NonGtestUnit/lit.cfg.py
+++ b/flang-rt/test/NonGtestUnit/lit.cfg.py
@@ -8,8 +8,7 @@ import lit.formats
config.name = "flang-rt-OldUnit"
# suffixes: A list of file extensions to treat as test files.
-# On Windows, ".exe" also matches the GTests and will execited redundantly.
-config.suffixes = [".test", ".exe"]
+config.suffixes = [".test", ".test.exe"]
# test_source_root: The root path where unit test binaries are located.
config.test_source_root = os.path.join(config.flangrt_binary_dir, "unittests")
diff --git a/flang-rt/unittests/CMakeLists.txt b/flang-rt/unittests/CMakeLists.txt
index 5282196..831bc8a 100644
--- a/flang-rt/unittests/CMakeLists.txt
+++ b/flang-rt/unittests/CMakeLists.txt
@@ -60,6 +60,27 @@ function(add_flangrt_unittest_offload_properties target)
endif()
endfunction()
+# flang-rt on Windows requires compiler-rt for some symbols. For binaries built
+# with flang this dependency is added by the flang driver, but since the unit
+# tests are built with clang we need to add the dependency manually.
+function(add_flangrt_dependent_libs target)
+ if (MSVC AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+ if (FLANG_RT_BUILTINS_LIBRARY)
+ target_compile_options(${target} PRIVATE "$<$<COMPILE_LANGUAGE:CXX,C>:-Xclang>" "$<$<COMPILE_LANGUAGE:CXX,C>:--dependent-lib=${FLANG_RT_BUILTINS_LIBRARY}>")
+ endif ()
+ endif ()
+ if (MSVC AND CMAKE_Fortran_COMPILER_ID STREQUAL "LLVMFlang")
+ if (FLANG_RT_BUILTINS_LIBRARY)
+ target_compile_options(${target} PRIVATE "$<$<COMPILE_LANGUAGE:Fortran>:-Xflang>" "$<$<COMPILE_LANGUAGE:Fortran>:--dependent-lib=${FLANG_RT_BUILTINS_LIBRARY}>")
+ else ()
+ message(WARNING "Did not find libclang_rt.builtins.lib.
+ LLVM may emit builtins that are not implemented in msvcrt/ucrt and
+ instead falls back to builtins from Compiler-RT. Linking with ${tgtname}
+ may result in a linker error.")
+ endif ()
+ endif ()
+endfunction()
+
function(add_flangrt_unittest test_dirname)
cmake_parse_arguments(ARG
@@ -72,6 +93,7 @@ function(add_flangrt_unittest test_dirname)
target_link_libraries(${test_dirname} PRIVATE ${ARG_LINK_LIBS})
add_flangrt_unittest_offload_properties(${test_dirname})
+ add_flangrt_dependent_libs(${test_dirname})
# Required because LLVMSupport is compiled with this option.
# FIXME: According to CMake documentation, this is the default. Why is it
@@ -99,6 +121,7 @@ function(add_flangrt_nongtest_unittest test_name)
set_target_properties(${test_name}${suffix} PROPERTIES FOLDER "Flang-RT/Tests/Unit")
target_link_libraries(${test_name}${suffix} PRIVATE NonGTestTesting ${ARG_LINK_LIBS})
+ add_flangrt_dependent_libs(${test_name}${suffix})
if(NOT ARG_SLOW_TEST)
add_dependencies(FlangRTUnitTests ${test_name}${suffix})
diff --git a/flang-rt/unittests/Runtime/CMakeLists.txt b/flang-rt/unittests/Runtime/CMakeLists.txt
index 49f55a4..cf1e15d 100644
--- a/flang-rt/unittests/Runtime/CMakeLists.txt
+++ b/flang-rt/unittests/Runtime/CMakeLists.txt
@@ -19,6 +19,7 @@ add_flangrt_unittest(RuntimeTests
Derived.cpp
ExternalIOTest.cpp
Format.cpp
+ InputExtensions.cpp
Inquiry.cpp
ListInputTest.cpp
LogicalFormatTest.cpp
diff --git a/flang-rt/unittests/Runtime/InputExtensions.cpp b/flang-rt/unittests/Runtime/InputExtensions.cpp
new file mode 100644
index 0000000..4bb1124
--- /dev/null
+++ b/flang-rt/unittests/Runtime/InputExtensions.cpp
@@ -0,0 +1,106 @@
+//===-- unittests/Runtime/InputExtensions.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 "CrashHandlerFixture.h"
+#include "flang-rt/runtime/descriptor.h"
+#include "flang/Runtime/io-api.h"
+#include <algorithm>
+#include <array>
+#include <cstring>
+#include <gtest/gtest.h>
+#include <tuple>
+
+using namespace Fortran::runtime;
+using namespace Fortran::runtime::io;
+
+struct InputExtensionTests : CrashHandlerFixture {};
+
+TEST(InputExtensionTests, SeparatorInField_F) {
+ static const struct {
+ int get;
+ const char *format, *data;
+ double expect[3];
+ } test[] = {
+ {2, "(2F6)", "1.25,3.75,", {1.25, 3.75}},
+ {2, "(2F6)", "1.25 ,3.75 ,", {1.25, 3.75}},
+ {2, "(DC,2F6)", "1,25;3,75;", {1.25, 3.75}},
+ {2, "(DC,2F6)", "1,25 ;3,75 ;", {1.25, 3.75}},
+ };
+ for (std::size_t j{0}; j < sizeof test / sizeof *test; ++j) {
+ auto cookie{IONAME(BeginInternalFormattedInput)(test[j].data,
+ std::strlen(test[j].data), test[j].format,
+ std::strlen(test[j].format))};
+ for (int k{0}; k < test[j].get; ++k) {
+ float got;
+ IONAME(InputReal32)(cookie, got);
+ ASSERT_EQ(got, test[j].expect[k])
+ << "expected " << test[j].expect[k] << ", got " << got;
+ }
+ auto status{IONAME(EndIoStatement)(cookie)};
+ ASSERT_EQ(status, 0) << "error status " << status << " on F test case "
+ << j;
+ }
+}
+
+TEST(InputExtensionTests, SeparatorInField_I) {
+ static const struct {
+ int get;
+ const char *format, *data;
+ std::int64_t expect[3];
+ } test[] = {
+ {2, "(2I4)", "12,34,", {12, 34}},
+ {2, "(2I4)", "12 ,34 ,", {12, 34}},
+ {2, "(DC,2I4)", "12;34;", {12, 34}},
+ {2, "(DC,2I4)", "12 ;34 ;", {12, 34}},
+ };
+ for (std::size_t j{0}; j < sizeof test / sizeof *test; ++j) {
+ auto cookie{IONAME(BeginInternalFormattedInput)(test[j].data,
+ std::strlen(test[j].data), test[j].format,
+ std::strlen(test[j].format))};
+ for (int k{0}; k < test[j].get; ++k) {
+ std::int64_t got;
+ IONAME(InputInteger)(cookie, got);
+ ASSERT_EQ(got, test[j].expect[k])
+ << "expected " << test[j].expect[k] << ", got " << got;
+ }
+ auto status{IONAME(EndIoStatement)(cookie)};
+ ASSERT_EQ(status, 0) << "error status " << status << " on I test case "
+ << j;
+ }
+}
+
+TEST(InputExtensionTests, SeparatorInField_L) {
+ static const struct {
+ int get;
+ const char *format, *data;
+ bool expect[3];
+ } test[] = {
+ {2, "(2L4)", ".T,F,", {true, false}},
+ {2, "(2L4)", ".F,T,", {false, true}},
+ {2, "(2L4)", ".T.,F,", {true, false}},
+ {2, "(2L4)", ".F.,T,", {false, true}},
+ {2, "(DC,2L4)", ".T;F,", {true, false}},
+ {2, "(DC,2L4)", ".F;T,", {false, true}},
+ {2, "(DC,2L4)", ".T.;F,", {true, false}},
+ {2, "(DC,2L4)", ".F.;T,", {false, true}},
+ };
+ for (std::size_t j{0}; j < sizeof test / sizeof *test; ++j) {
+ auto cookie{IONAME(BeginInternalFormattedInput)(test[j].data,
+ std::strlen(test[j].data), test[j].format,
+ std::strlen(test[j].format))};
+ for (int k{0}; k < test[j].get; ++k) {
+ bool got;
+ IONAME(InputLogical)(cookie, got);
+ ASSERT_EQ(got, test[j].expect[k])
+ << "expected " << test[j].expect[k] << ", got " << got;
+ }
+ auto status{IONAME(EndIoStatement)(cookie)};
+ ASSERT_EQ(status, 0) << "error status " << status << " on L test case "
+ << j;
+ }
+}
diff --git a/flang-rt/unittests/Runtime/NumericalFormatTest.cpp b/flang-rt/unittests/Runtime/NumericalFormatTest.cpp
index 73245dc..4e5fdcc 100644
--- a/flang-rt/unittests/Runtime/NumericalFormatTest.cpp
+++ b/flang-rt/unittests/Runtime/NumericalFormatTest.cpp
@@ -921,6 +921,7 @@ TEST(IOApiTests, EditDoubleInputValues) {
{"(BZ,F18.0)", " . ", 0x0, 0},
{"(BZ,F18.0)", " . e +1 ", 0x0, 0},
{"(DC,F18.0)", " 12,5", 0x4029000000000000, 0},
+ {"(DC,F18.0)", " 12,5;", 0x4029000000000000, 0},
{"(EX22.0)", "0X0P0 ", 0x0, 0}, // +0.
{"(EX22.0)", "-0X0P0 ", 0x8000000000000000, 0}, // -0.
{"(EX22.0)", "0X.8P1 ", 0x3ff0000000000000, 0}, // 1.0
@@ -964,6 +965,7 @@ TEST(IOApiTests, EditDoubleInputValues) {
{"(RU,E9.1)", " 1.0E-325", 0x1, 0},
{"(E9.1)", "-1.0E-325", 0x8000000000000000, 0},
{"(RD,E9.1)", "-1.0E-325", 0x8000000000000001, 0},
+ {"(F7.0))", "+NaN(q)", 0x7ff8000000000000, 0},
};
for (auto const &[format, data, want, iostat] : testCases) {
auto cookie{IONAME(BeginInternalFormattedInput)(