diff options
Diffstat (limited to 'flang-rt')
-rw-r--r-- | flang-rt/cmake/modules/AddFlangRT.cmake | 21 | ||||
-rw-r--r-- | flang-rt/include/flang-rt/runtime/format.h | 8 | ||||
-rw-r--r-- | flang-rt/include/flang-rt/runtime/io-stmt.h | 22 | ||||
-rw-r--r-- | flang-rt/lib/runtime/descriptor-io.cpp | 26 | ||||
-rw-r--r-- | flang-rt/lib/runtime/edit-input.cpp | 97 | ||||
-rw-r--r-- | flang-rt/lib/runtime/io-stmt.cpp | 51 | ||||
-rw-r--r-- | flang-rt/lib/runtime/namelist.cpp | 3 | ||||
-rw-r--r-- | flang-rt/lib/runtime/unit.h | 3 | ||||
-rw-r--r-- | flang-rt/test/NonGtestUnit/lit.cfg.py | 3 | ||||
-rw-r--r-- | flang-rt/unittests/CMakeLists.txt | 23 | ||||
-rw-r--r-- | flang-rt/unittests/Runtime/CMakeLists.txt | 1 | ||||
-rw-r--r-- | flang-rt/unittests/Runtime/InputExtensions.cpp | 106 | ||||
-rw-r--r-- | flang-rt/unittests/Runtime/NumericalFormatTest.cpp | 2 |
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)( |