diff --git a/flang/runtime/io-api.h b/flang/runtime/io-api.h --- a/flang/runtime/io-api.h +++ b/flang/runtime/io-api.h @@ -29,6 +29,26 @@ using AsynchronousId = int; static constexpr ExternalUnit DefaultUnit{-1}; // READ(*), WRITE(*), PRINT +// INQUIRE specifiers are encoded as simple base-26 packings of +// the spellings of their keywords. +using InquiryKeywordHash = std::uint64_t; +constexpr InquiryKeywordHash HashInquiryKeyword(const char *p) { + InquiryKeywordHash hash{1}; + while (char ch{*p++}) { + std::uint64_t letter{0}; + if (ch >= 'a' && ch <= 'z') { + letter = ch - 'a'; + } else { + letter = ch - 'A'; + } + hash = 26 * hash + letter; + } + return hash; +} + +const char *InquiryKeywordHashDecode( + char *buffer, std::size_t, InquiryKeywordHash); + extern "C" { #define IONAME(name) RTNAME(io##name) @@ -150,7 +170,7 @@ // BeginInquireIoLength() is basically a no-op output statement. Cookie IONAME(BeginInquireUnit)( ExternalUnit, const char *sourceFile = nullptr, int sourceLine = 0); -Cookie IONAME(BeginInquireFile)(const char *, std::size_t, int kind = 1, +Cookie IONAME(BeginInquireFile)(const char *, std::size_t, const char *sourceFile = nullptr, int sourceLine = 0); Cookie IONAME(BeginInquireIoLength)( const char *sourceFile = nullptr, int sourceLine = 0); @@ -255,10 +275,7 @@ // For CLOSE: STATUS=KEEP, DELETE bool IONAME(SetStatus)(Cookie, const char *, std::size_t); -// SetFile() may pass a CHARACTER argument of non-default kind, -// and such filenames are converted to UTF-8 before being -// presented to the filesystem. -bool IONAME(SetFile)(Cookie, const char *, std::size_t chars, int kind = 1); +bool IONAME(SetFile)(Cookie, const char *, std::size_t chars); // Acquires the runtime-created unit number for OPEN(NEWUNIT=) bool IONAME(GetNewUnit)(Cookie, int &, int kind = 4); @@ -275,18 +292,17 @@ // INQUIRE() specifiers are mostly identified by their NUL-terminated // case-insensitive names. -// ACCESS, ACTION, ASYNCHRONOUS, BLANK, DECIMAL, DELIM, DIRECT, ENCODING, -// FORM, FORMATTED, NAME, PAD, POSITION, READ, READWRITE, ROUND, +// ACCESS, ACTION, ASYNCHRONOUS, BLANK, CONVERT, DECIMAL, DELIM, DIRECT, +// ENCODING, FORM, FORMATTED, NAME, PAD, POSITION, READ, READWRITE, ROUND, // SEQUENTIAL, SIGN, STREAM, UNFORMATTED, WRITE: -bool IONAME(InquireCharacter)( - Cookie, const char *specifier, char *, std::size_t); +bool IONAME(InquireCharacter)(Cookie, InquiryKeywordHash, char *, std::size_t); // EXIST, NAMED, OPENED, and PENDING (without ID): -bool IONAME(InquireLogical)(Cookie, const char *specifier, bool &); +bool IONAME(InquireLogical)(Cookie, InquiryKeywordHash, bool &); // PENDING with ID bool IONAME(InquirePendingId)(Cookie, std::int64_t, bool &); // NEXTREC, NUMBER, POS, RECL, SIZE bool IONAME(InquireInteger64)( - Cookie, const char *specifier, std::int64_t &, int kind = 8); + Cookie, InquiryKeywordHash, std::int64_t &, int kind = 8); // This function must be called to end an I/O statement, and its // cookie value may not be used afterwards unless it is recycled diff --git a/flang/runtime/io-api.cpp b/flang/runtime/io-api.cpp --- a/flang/runtime/io-api.cpp +++ b/flang/runtime/io-api.cpp @@ -23,6 +23,23 @@ namespace Fortran::runtime::io { +const char *InquiryKeywordHashDecode( + char *buffer, std::size_t n, InquiryKeywordHash hash) { + if (n < 1) { + return nullptr; + } + char *p{buffer + n}; + *--p = '\0'; + while (hash > 1) { + if (p < buffer) { + return nullptr; + } + *--p = 'A' + (hash % 26); + hash /= 26; + } + return hash == 1 ? p : nullptr; +} + template Cookie BeginInternalArrayListIO(const Descriptor &descriptor, void ** /*scratchArea*/, std::size_t /*scratchBytes*/, @@ -289,8 +306,8 @@ Cookie IONAME(BeginEndfile)( ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { Terminator terminator{sourceFile, sourceLine}; - ExternalFileUnit &unit{ - ExternalFileUnit::LookUpOrCrash(unitNumber, terminator)}; + ExternalFileUnit &unit{ExternalFileUnit::LookUpOrCreateAnonymous( + unitNumber, Direction::Output, true /*formatted*/, terminator)}; return &unit.BeginIoStatement( unit, ExternalMiscIoStatementState::Endfile, sourceFile, sourceLine); } @@ -298,12 +315,50 @@ Cookie IONAME(BeginRewind)( ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { Terminator terminator{sourceFile, sourceLine}; - ExternalFileUnit &unit{ - ExternalFileUnit::LookUpOrCrash(unitNumber, terminator)}; + ExternalFileUnit &unit{ExternalFileUnit::LookUpOrCreateAnonymous( + unitNumber, Direction::Input, true /*formatted*/, terminator)}; return &unit.BeginIoStatement( unit, ExternalMiscIoStatementState::Rewind, sourceFile, sourceLine); } +Cookie IONAME(BeginInquireUnit)( + ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { + if (ExternalFileUnit * unit{ExternalFileUnit::LookUp(unitNumber)}) { + return &unit->BeginIoStatement( + *unit, sourceFile, sourceLine); + } else { + // INQUIRE(UNIT=unrecognized unit) + Terminator oom{sourceFile, sourceLine}; + return &New{oom}(sourceFile, sourceLine) + .release() + ->ioStatementState(); + } +} + +Cookie IONAME(BeginInquireFile)(const char *path, std::size_t pathLength, + const char *sourceFile, int sourceLine) { + Terminator oom{sourceFile, sourceLine}; + auto trimmed{ + SaveDefaultCharacter(path, TrimTrailingSpaces(path, pathLength), oom)}; + if (ExternalFileUnit * unit{ExternalFileUnit::LookUp(trimmed.get())}) { + // INQUIRE(FILE=) to a connected unit + return &unit->BeginIoStatement( + *unit, sourceFile, sourceLine); + } else { + return &New{oom}( + std::move(trimmed), sourceFile, sourceLine) + .release() + ->ioStatementState(); + } +} + +Cookie IONAME(BeginInquireIoLength)(const char *sourceFile, int sourceLine) { + Terminator oom{sourceFile, sourceLine}; + return &New{oom}(sourceFile, sourceLine) + .release() + ->ioStatementState(); +} + // Control list items void IONAME(EnableHandlers)(Cookie cookie, bool hasIoStat, bool hasErr, @@ -522,29 +577,21 @@ io.GetIoErrorHandler().Crash( "SetAccess() called when not in an OPEN statement"); } - ConnectionState &connection{open->GetConnectionState()}; - Access access{connection.access}; static const char *keywords[]{"SEQUENTIAL", "DIRECT", "STREAM", nullptr}; switch (IdentifyValue(keyword, length, keywords)) { case 0: - access = Access::Sequential; + open->set_access(Access::Sequential); break; case 1: - access = Access::Direct; + open->set_access(Access::Direct); break; case 2: - access = Access::Stream; + open->set_access(Access::Stream); break; default: open->SignalError(IostatErrorInKeyword, "Invalid ACCESS='%.*s'", static_cast(length), keyword); } - if (access != connection.access) { - if (open->wasExtant()) { - open->SignalError("ACCESS= may not be changed on an open unit"); - } - connection.access = access; - } return true; } @@ -661,25 +708,18 @@ io.GetIoErrorHandler().Crash( "SetEncoding() called when not in an OPEN statement"); } - bool isUnformatted{false}; static const char *keywords[]{"FORMATTED", "UNFORMATTED", nullptr}; switch (IdentifyValue(keyword, length, keywords)) { case 0: - isUnformatted = false; + open->set_isUnformatted(false); break; case 1: - isUnformatted = true; + open->set_isUnformatted(true); break; default: open->SignalError(IostatErrorInKeyword, "Invalid FORM='%.*s'", static_cast(length), keyword); } - if (isUnformatted != open->unit().isUnformatted) { - if (open->wasExtant()) { - open->SignalError("FORM= may not be changed on an open unit"); - } - open->unit().isUnformatted = isUnformatted; - } return true; } @@ -777,11 +817,10 @@ "SetStatus() called when not in an OPEN or CLOSE statement"); } -bool IONAME(SetFile)( - Cookie cookie, const char *path, std::size_t chars, int kind) { +bool IONAME(SetFile)(Cookie cookie, const char *path, std::size_t chars) { IoStatementState &io{*cookie}; if (auto *open{io.get_if()}) { - open->set_path(path, chars, kind); + open->set_path(path, chars); return true; } io.GetIoErrorHandler().Crash( @@ -789,7 +828,8 @@ return false; } -static bool SetInteger(int &x, int kind, int value) { +template +static bool SetInteger(INT &x, int kind, std::int64_t value) { switch (kind) { case 1: reinterpret_cast(x) = value; @@ -798,7 +838,7 @@ reinterpret_cast(x) = value; return true; case 4: - x = value; + reinterpret_cast(x) = value; return true; case 8: reinterpret_cast(x) = value; @@ -1059,6 +1099,34 @@ } } +bool IONAME(InquireCharacter)(Cookie cookie, InquiryKeywordHash inquiry, + char *result, std::size_t length) { + IoStatementState &io{*cookie}; + return io.Inquire(inquiry, result, length); +} + +bool IONAME(InquireLogical)( + Cookie cookie, InquiryKeywordHash inquiry, bool &result) { + IoStatementState &io{*cookie}; + return io.Inquire(inquiry, result); +} + +bool IONAME(InquirePendingId)(Cookie cookie, std::int64_t id, bool &result) { + IoStatementState &io{*cookie}; + return io.Inquire(HashInquiryKeyword("PENDING"), id, result); +} + +bool IONAME(InquireInteger64)( + Cookie cookie, InquiryKeywordHash inquiry, std::int64_t &result, int kind) { + IoStatementState &io{*cookie}; + std::int64_t n; + if (io.Inquire(inquiry, n)) { + SetInteger(result, kind, n); + return true; + } + return false; +} + enum Iostat IONAME(EndIoStatement)(Cookie cookie) { IoStatementState &io{*cookie}; return static_cast(io.EndIoStatement()); diff --git a/flang/runtime/io-error.h b/flang/runtime/io-error.h --- a/flang/runtime/io-error.h +++ b/flang/runtime/io-error.h @@ -38,7 +38,7 @@ void SignalError(int iostatOrErrno, const char *msg, ...); void SignalError(int iostatOrErrno); - template void SignalError(const char *msg, X &&... xs) { + template void SignalError(const char *msg, X &&...xs) { SignalError(IostatGenericError, msg, std::forward(xs)...); } diff --git a/flang/runtime/io-stmt.h b/flang/runtime/io-stmt.h --- a/flang/runtime/io-stmt.h +++ b/flang/runtime/io-stmt.h @@ -16,6 +16,7 @@ #include "file.h" #include "format.h" #include "internal-unit.h" +#include "io-api.h" #include "io-error.h" #include #include @@ -26,6 +27,11 @@ class ExternalFileUnit; class OpenStatementState; +class InquireUnitState; +class InquireNoUnitState; +class InquireUnconnectedFileState; +class InquireIOLengthState; +class ExternalMiscIoStatementState; class CloseStatementState; class NoopCloseStatementState; @@ -36,7 +42,6 @@ class ExternalFormattedIoStatementState; template class ExternalListIoStatementState; template class UnformattedIoStatementState; -class ExternalMiscIoStatementState; // The Cookie type in the I/O API is a pointer (for C) to this class. class IoStatementState { @@ -60,6 +65,10 @@ ExternalFileUnit *GetExternalFileUnit() const; // null if internal unit MutableModes &mutableModes(); void BeginReadingRecord(); + bool Inquire(InquiryKeywordHash, char *, std::size_t); + bool Inquire(InquiryKeywordHash, bool &); + bool Inquire(InquiryKeywordHash, std::int64_t, bool &); // PENDING= + bool Inquire(InquiryKeywordHash, std::int64_t &); // N.B.: this also works with base classes template A *get_if() const { @@ -98,6 +107,10 @@ std::reference_wrapper>, std::reference_wrapper>, std::reference_wrapper>, + std::reference_wrapper, + std::reference_wrapper, + std::reference_wrapper, + std::reference_wrapper, std::reference_wrapper> u_; }; @@ -110,6 +123,12 @@ std::optional GetNextDataEdit(IoStatementState &, int = 1); ExternalFileUnit *GetExternalFileUnit() const { return nullptr; } void BeginReadingRecord() {} + + bool Inquire(InquiryKeywordHash, char *, std::size_t); + bool Inquire(InquiryKeywordHash, bool &); + bool Inquire(InquiryKeywordHash, std::int64_t, bool &); + bool Inquire(InquiryKeywordHash, std::int64_t &); + void BadInquiryKeywordHashCrash(InquiryKeywordHash); }; struct InputStatementState {}; @@ -303,10 +322,12 @@ wasExtant} {} bool wasExtant() const { return wasExtant_; } void set_status(OpenStatus status) { status_ = status; } // STATUS= - void set_path(const char *, std::size_t, int kind); // FILE= + void set_path(const char *, std::size_t); // FILE= void set_position(Position position) { position_ = position; } // POSITION= void set_action(Action action) { action_ = action; } // ACTION= void set_convert(Convert convert) { convert_ = convert; } // CONVERT= + void set_access(Access access) { access_ = access; } // ACCESS= + void set_isUnformatted(bool yes = true) { isUnformatted_ = yes; } // FORM= int EndIoStatement(); private: @@ -317,6 +338,8 @@ Convert convert_{Convert::Native}; OwningPtr path_; std::size_t pathLength_; + std::optional isUnformatted_; + std::optional access_; }; class CloseStatementState : public ExternalIoStatementBase { @@ -331,21 +354,31 @@ CloseStatus status_{CloseStatus::Keep}; }; -class NoopCloseStatementState : public IoStatementBase { +// For CLOSE(bad unit) and INQUIRE(unconnected unit) +class NoUnitIoStatementState : public IoStatementBase { public: - NoopCloseStatementState(const char *sourceFile, int sourceLine) - : IoStatementBase{sourceFile, sourceLine}, ioStatementState_{*this} {} IoStatementState &ioStatementState() { return ioStatementState_; } - void set_status(CloseStatus) {} // discards MutableModes &mutableModes() { return connection_.modes; } ConnectionState &GetConnectionState() { return connection_; } int EndIoStatement(); +protected: + template + NoUnitIoStatementState(const char *sourceFile, int sourceLine, A &stmt) + : IoStatementBase{sourceFile, sourceLine}, ioStatementState_{stmt} {} + private: IoStatementState ioStatementState_; // points to *this ConnectionState connection_; }; +class NoopCloseStatementState : public NoUnitIoStatementState { +public: + NoopCloseStatementState(const char *sourceFile, int sourceLine) + : NoUnitIoStatementState{sourceFile, sourceLine, *this} {} + void set_status(CloseStatus) {} // discards +}; + extern template class InternalIoStatementState; extern template class InternalIoStatementState; extern template class InternalFormattedIoStatementState; @@ -369,6 +402,49 @@ extern template class FormatControl< ExternalFormattedIoStatementState>; +class InquireUnitState : public ExternalIoStatementBase { +public: + InquireUnitState(ExternalFileUnit &unit, const char *sourceFile = nullptr, + int sourceLine = 0); + bool Inquire(InquiryKeywordHash, char *, std::size_t); + bool Inquire(InquiryKeywordHash, bool &); + bool Inquire(InquiryKeywordHash, std::int64_t, bool &); + bool Inquire(InquiryKeywordHash, std::int64_t &); +}; + +class InquireNoUnitState : public NoUnitIoStatementState { +public: + InquireNoUnitState(const char *sourceFile = nullptr, int sourceLine = 0); + bool Inquire(InquiryKeywordHash, char *, std::size_t); + bool Inquire(InquiryKeywordHash, bool &); + bool Inquire(InquiryKeywordHash, std::int64_t, bool &); + bool Inquire(InquiryKeywordHash, std::int64_t &); +}; + +class InquireUnconnectedFileState : public NoUnitIoStatementState { +public: + InquireUnconnectedFileState(OwningPtr &&path, + const char *sourceFile = nullptr, int sourceLine = 0); + bool Inquire(InquiryKeywordHash, char *, std::size_t); + bool Inquire(InquiryKeywordHash, bool &); + bool Inquire(InquiryKeywordHash, std::int64_t, bool &); + bool Inquire(InquiryKeywordHash, std::int64_t &); + +private: + OwningPtr path_; // trimmed and NUL terminated +}; + +class InquireIOLengthState : public NoUnitIoStatementState, + public OutputStatementState { +public: + InquireIOLengthState(const char *sourceFile = nullptr, int sourceLine = 0); + std::size_t bytes() const { return bytes_; } + bool Emit(const char *, std::size_t, std::size_t elementBytes = 0); + +private: + std::size_t bytes_{0}; +}; + class ExternalMiscIoStatementState : public ExternalIoStatementBase { public: enum Which { Flush, Backspace, Endfile, Rewind }; diff --git a/flang/runtime/io-stmt.cpp b/flang/runtime/io-stmt.cpp --- a/flang/runtime/io-stmt.cpp +++ b/flang/runtime/io-stmt.cpp @@ -26,6 +26,37 @@ return std::nullopt; } +bool IoStatementBase::Inquire(InquiryKeywordHash, char *, std::size_t) { + Crash( + "IoStatementBase::Inquire() called for I/O statement other than INQUIRE"); + return false; +} + +bool IoStatementBase::Inquire(InquiryKeywordHash, bool &) { + Crash( + "IoStatementBase::Inquire() called for I/O statement other than INQUIRE"); + return false; +} + +bool IoStatementBase::Inquire(InquiryKeywordHash, std::int64_t, bool &) { + Crash( + "IoStatementBase::Inquire() called for I/O statement other than INQUIRE"); + return false; +} + +bool IoStatementBase::Inquire(InquiryKeywordHash, std::int64_t &) { + Crash( + "IoStatementBase::Inquire() called for I/O statement other than INQUIRE"); + return false; +} + +void IoStatementBase::BadInquiryKeywordHashCrash(InquiryKeywordHash inquiry) { + char buffer[16]; + const char *decode{InquiryKeywordHashDecode(buffer, sizeof buffer, inquiry)}; + Crash("bad InquiryKeywordHash 0x%x (%s)", inquiry, + decode ? decode : "(cannot decode)"); +} + template InternalIoStatementState::InternalIoStatementState( Buffer scalar, std::size_t length, const char *sourceFile, int sourceLine) @@ -151,14 +182,9 @@ return result; } -void OpenStatementState::set_path( - const char *path, std::size_t length, int kind) { - if (kind != 1) { // TODO - Crash("OPEN: FILE= with unimplemented: CHARACTER(KIND=%d)", kind); - } - std::size_t bytes{length * kind}; // TODO: UTF-8 encoding of Unicode path - path_ = SaveDefaultCharacter(path, bytes, *this); - pathLength_ = length; +void OpenStatementState::set_path(const char *path, std::size_t length) { + pathLength_ = TrimTrailingSpaces(path, length); + path_ = SaveDefaultCharacter(path, pathLength_, *this); } int OpenStatementState::EndIoStatement() { @@ -166,8 +192,31 @@ SignalError("OPEN statement for connected unit may not have STATUS= other " "than 'OLD'"); } - unit().OpenUnit(status_.value_or(OpenStatus::Unknown), action_, position_, - std::move(path_), pathLength_, convert_, *this); + if (path_.get() || wasExtant_ || + (status_ && *status_ == OpenStatus::Scratch)) { + unit().OpenUnit(status_.value_or(OpenStatus::Unknown), action_, position_, + std::move(path_), pathLength_, convert_, *this); + } else { + unit().OpenAnonymousUnit(status_.value_or(OpenStatus::Unknown), action_, + position_, convert_, *this); + } + if (access_) { + if (*access_ != unit().access) { + if (wasExtant_) { + SignalError("ACCESS= may not be changed on an open unit"); + } + } + unit().access = *access_; + } + if (!isUnformatted_) { + isUnformatted_ = unit().access != Access::Sequential; + } + if (*isUnformatted_ != unit().isUnformatted) { + if (wasExtant_) { + SignalError("FORM= may not be changed on an open unit"); + } + unit().isUnformatted = *isUnformatted_; + } return ExternalIoStatementBase::EndIoStatement(); } @@ -178,7 +227,7 @@ return result; } -int NoopCloseStatementState::EndIoStatement() { +int NoUnitIoStatementState::EndIoStatement() { auto result{IoStatementBase::EndIoStatement()}; FreeMemory(this); return result; @@ -454,6 +503,26 @@ width > connection.RemainingSpaceInRecord(); } +bool IoStatementState::Inquire( + InquiryKeywordHash inquiry, char *out, std::size_t chars) { + return std::visit( + [&](auto &x) { return x.get().Inquire(inquiry, out, chars); }, u_); +} + +bool IoStatementState::Inquire(InquiryKeywordHash inquiry, bool &out) { + return std::visit([&](auto &x) { return x.get().Inquire(inquiry, out); }, u_); +} + +bool IoStatementState::Inquire( + InquiryKeywordHash inquiry, std::int64_t id, bool &out) { + return std::visit( + [&](auto &x) { return x.get().Inquire(inquiry, id, out); }, u_); +} + +bool IoStatementState::Inquire(InquiryKeywordHash inquiry, std::int64_t &n) { + return std::visit([&](auto &x) { return x.get().Inquire(inquiry, n); }, u_); +} + bool ListDirectedStatementState::EmitLeadingSpaceOrAdvance( IoStatementState &io, std::size_t length, bool isCharacter) { if (length == 0) { @@ -678,4 +747,419 @@ return ExternalIoStatementBase::EndIoStatement(); } +InquireUnitState::InquireUnitState( + ExternalFileUnit &unit, const char *sourceFile, int sourceLine) + : ExternalIoStatementBase{unit, sourceFile, sourceLine} {} + +bool InquireUnitState::Inquire( + InquiryKeywordHash inquiry, char *result, std::size_t length) { + const char *str{nullptr}; + switch (inquiry) { + case HashInquiryKeyword("ACCESS"): + switch (unit().access) { + case Access::Sequential: + str = "SEQUENTIAL"; + break; + case Access::Direct: + str = "DIRECT"; + break; + case Access::Stream: + str = "STREAM"; + break; + } + break; + case HashInquiryKeyword("ACTION"): + str = unit().mayWrite() ? unit().mayRead() ? "READWRITE" : "WRITE" : "READ"; + break; + case HashInquiryKeyword("ASYNCHRONOUS"): + str = unit().mayAsynchronous() ? "YES" : "NO"; + break; + case HashInquiryKeyword("BLANK"): + str = unit().isUnformatted ? "UNDEFINED" + : unit().modes.editingFlags & blankZero ? "ZERO" + : "NULL"; + break; + case HashInquiryKeyword("CONVERT"): + str = unit().swapEndianness() ? "SWAP" : "NATIVE"; + break; + case HashInquiryKeyword("DECIMAL"): + str = unit().isUnformatted ? "UNDEFINED" + : unit().modes.editingFlags & decimalComma ? "COMMA" + : "POINT"; + break; + case HashInquiryKeyword("DELIM"): + if (unit().isUnformatted) { + str = "UNDEFINED"; + } else { + switch (unit().modes.delim) { + case '\'': + str = "APOSTROPHE"; + break; + case '"': + str = "QUOTE"; + break; + default: + str = "NONE"; + break; + } + } + break; + case HashInquiryKeyword("DIRECT"): + str = unit().mayPosition() ? "YES" : "NO"; + break; + case HashInquiryKeyword("ENCODING"): + str = unit().isUnformatted ? "UNDEFINED" + : unit().isUTF8 ? "UTF-8" + : "ASCII"; + break; + case HashInquiryKeyword("FORM"): + str = unit().isUnformatted ? "UNFORMATTED" : "FORMATTED"; + break; + case HashInquiryKeyword("FORMATTED"): + str = "YES"; + break; + case HashInquiryKeyword("NAME"): + str = unit().path(); + if (!str) { + return true; // result is undefined + } + break; + case HashInquiryKeyword("PAD"): + str = unit().isUnformatted ? "UNDEFINED" : unit().modes.pad ? "YES" : "NO"; + break; + case HashInquiryKeyword("POSITION"): + if (unit().access == Access::Direct) { + str = "UNDEFINED"; + } else { + auto size{unit().knownSize()}; + auto pos{unit().position()}; + if (pos == size.value_or(pos + 1)) { + str = "APPEND"; + } else if (pos == 0) { + str = "REWIND"; + } else { + str = "ASIS"; // processor-dependent & no common behavior + } + } + break; + case HashInquiryKeyword("READ"): + str = unit().mayRead() ? "YES" : "NO"; + break; + case HashInquiryKeyword("READWRITE"): + str = unit().mayRead() && unit().mayWrite() ? "YES" : "NO"; + break; + case HashInquiryKeyword("ROUND"): + if (unit().isUnformatted) { + str = "UNDEFINED"; + } else { + switch (unit().modes.round) { + case decimal::FortranRounding::RoundNearest: + str = "NEAREST"; + break; + case decimal::FortranRounding::RoundUp: + str = "UP"; + break; + case decimal::FortranRounding::RoundDown: + str = "DOWN"; + break; + case decimal::FortranRounding::RoundToZero: + str = "ZERO"; + break; + case decimal::FortranRounding::RoundCompatible: + str = "COMPATIBLE"; + break; + } + } + break; + case HashInquiryKeyword("SEQUENTIAL"): + str = "YES"; + break; + case HashInquiryKeyword("SIGN"): + str = unit().isUnformatted ? "UNDEFINED" + : unit().modes.editingFlags & signPlus ? "PLUS" + : "SUPPRESS"; + break; + case HashInquiryKeyword("STREAM"): + str = "YES"; + break; + case HashInquiryKeyword("WRITE"): + str = unit().mayWrite() ? "YES" : "NO"; + break; + case HashInquiryKeyword("UNFORMATTED"): + str = "YES"; + break; + } + if (str) { + ToFortranDefaultCharacter(result, length, str); + return true; + } else { + BadInquiryKeywordHashCrash(inquiry); + return false; + } +} + +bool InquireUnitState::Inquire(InquiryKeywordHash inquiry, bool &result) { + switch (inquiry) { + case HashInquiryKeyword("EXIST"): + result = true; + return true; + case HashInquiryKeyword("NAMED"): + result = unit().path() != nullptr; + return true; + case HashInquiryKeyword("OPENED"): + result = true; + return true; + case HashInquiryKeyword("PENDING"): + result = false; // asynchronous I/O is not implemented + return true; + default: + BadInquiryKeywordHashCrash(inquiry); + return false; + } +} + +bool InquireUnitState::Inquire( + InquiryKeywordHash inquiry, std::int64_t, bool &result) { + switch (inquiry) { + case HashInquiryKeyword("PENDING"): + result = false; // asynchronous I/O is not implemented + return true; + default: + BadInquiryKeywordHashCrash(inquiry); + return false; + } +} + +bool InquireUnitState::Inquire( + InquiryKeywordHash inquiry, std::int64_t &result) { + switch (inquiry) { + case HashInquiryKeyword("NEXTREC"): + if (unit().access == Access::Direct) { + result = unit().currentRecordNumber; + } + return true; + case HashInquiryKeyword("NUMBER"): + result = unit().unitNumber(); + return true; + case HashInquiryKeyword("POS"): + result = unit().position(); + return true; + case HashInquiryKeyword("RECL"): + if (unit().access == Access::Stream) { + result = -2; + } else if (unit().isFixedRecordLength && unit().recordLength) { + result = *unit().recordLength; + } else { + result = std::numeric_limits::max(); + } + return true; + case HashInquiryKeyword("SIZE"): + if (auto size{unit().knownSize()}) { + result = *size; + } else { + result = -1; + } + return true; + default: + BadInquiryKeywordHashCrash(inquiry); + return false; + } +} + +InquireNoUnitState::InquireNoUnitState(const char *sourceFile, int sourceLine) + : NoUnitIoStatementState{sourceFile, sourceLine, *this} {} + +bool InquireNoUnitState::Inquire( + InquiryKeywordHash inquiry, char *result, std::size_t length) { + switch (inquiry) { + case HashInquiryKeyword("ACCESS"): + case HashInquiryKeyword("ACTION"): + case HashInquiryKeyword("ASYNCHRONOUS"): + case HashInquiryKeyword("BLANK"): + case HashInquiryKeyword("CONVERT"): + case HashInquiryKeyword("DECIMAL"): + case HashInquiryKeyword("DELIM"): + case HashInquiryKeyword("FORM"): + case HashInquiryKeyword("NAME"): + case HashInquiryKeyword("PAD"): + case HashInquiryKeyword("POSITION"): + case HashInquiryKeyword("ROUND"): + case HashInquiryKeyword("SIGN"): + ToFortranDefaultCharacter(result, length, "UNDEFINED"); + return true; + case HashInquiryKeyword("DIRECT"): + case HashInquiryKeyword("ENCODING"): + case HashInquiryKeyword("FORMATTED"): + case HashInquiryKeyword("READ"): + case HashInquiryKeyword("READWRITE"): + case HashInquiryKeyword("SEQUENTIAL"): + case HashInquiryKeyword("STREAM"): + case HashInquiryKeyword("WRITE"): + case HashInquiryKeyword("UNFORMATTED"): + ToFortranDefaultCharacter(result, length, "UNKNONN"); + return true; + default: + BadInquiryKeywordHashCrash(inquiry); + return false; + } +} + +bool InquireNoUnitState::Inquire(InquiryKeywordHash inquiry, bool &result) { + switch (inquiry) { + case HashInquiryKeyword("EXIST"): + result = true; + return true; + case HashInquiryKeyword("NAMED"): + case HashInquiryKeyword("OPENED"): + case HashInquiryKeyword("PENDING"): + result = false; + return true; + default: + BadInquiryKeywordHashCrash(inquiry); + return false; + } +} + +bool InquireNoUnitState::Inquire( + InquiryKeywordHash inquiry, std::int64_t, bool &result) { + switch (inquiry) { + case HashInquiryKeyword("PENDING"): + result = false; + return true; + default: + BadInquiryKeywordHashCrash(inquiry); + return false; + } +} + +bool InquireNoUnitState::Inquire( + InquiryKeywordHash inquiry, std::int64_t &result) { + switch (inquiry) { + case HashInquiryKeyword("NEXTREC"): + case HashInquiryKeyword("NUMBER"): + case HashInquiryKeyword("POS"): + case HashInquiryKeyword("RECL"): + case HashInquiryKeyword("SIZE"): + result = -1; + return true; + default: + BadInquiryKeywordHashCrash(inquiry); + return false; + } +} + +InquireUnconnectedFileState::InquireUnconnectedFileState( + OwningPtr &&path, const char *sourceFile, int sourceLine) + : NoUnitIoStatementState{sourceFile, sourceLine, *this}, path_{std::move( + path)} {} + +bool InquireUnconnectedFileState::Inquire( + InquiryKeywordHash inquiry, char *result, std::size_t length) { + const char *str{nullptr}; + switch (inquiry) { + case HashInquiryKeyword("ACCESS"): + case HashInquiryKeyword("ACTION"): + case HashInquiryKeyword("ASYNCHRONOUS"): + case HashInquiryKeyword("BLANK"): + case HashInquiryKeyword("CONVERT"): + case HashInquiryKeyword("DECIMAL"): + case HashInquiryKeyword("DELIM"): + case HashInquiryKeyword("FORM"): + case HashInquiryKeyword("PAD"): + case HashInquiryKeyword("POSITION"): + case HashInquiryKeyword("ROUND"): + case HashInquiryKeyword("SIGN"): + str = "UNDEFINED"; + break; + case HashInquiryKeyword("DIRECT"): + case HashInquiryKeyword("ENCODING"): + str = "UNKNONN"; + break; + case HashInquiryKeyword("READ"): + str = MayRead(path_.get()) ? "YES" : "NO"; + break; + case HashInquiryKeyword("READWRITE"): + str = MayReadAndWrite(path_.get()) ? "YES" : "NO"; + break; + case HashInquiryKeyword("WRITE"): + str = MayWrite(path_.get()) ? "YES" : "NO"; + break; + case HashInquiryKeyword("FORMATTED"): + case HashInquiryKeyword("SEQUENTIAL"): + case HashInquiryKeyword("STREAM"): + case HashInquiryKeyword("UNFORMATTED"): + str = "YES"; + break; + case HashInquiryKeyword("NAME"): + str = path_.get(); + return true; + } + if (str) { + ToFortranDefaultCharacter(result, length, str); + return true; + } else { + BadInquiryKeywordHashCrash(inquiry); + return false; + } +} + +bool InquireUnconnectedFileState::Inquire( + InquiryKeywordHash inquiry, bool &result) { + switch (inquiry) { + case HashInquiryKeyword("EXIST"): + result = IsExtant(path_.get()); + return true; + case HashInquiryKeyword("NAMED"): + result = true; + return true; + case HashInquiryKeyword("OPENED"): + result = false; + return true; + case HashInquiryKeyword("PENDING"): + result = false; + return true; + default: + BadInquiryKeywordHashCrash(inquiry); + return false; + } +} + +bool InquireUnconnectedFileState::Inquire( + InquiryKeywordHash inquiry, std::int64_t, bool &result) { + switch (inquiry) { + case HashInquiryKeyword("PENDING"): + result = false; + return true; + default: + BadInquiryKeywordHashCrash(inquiry); + return false; + } +} + +bool InquireUnconnectedFileState::Inquire( + InquiryKeywordHash inquiry, std::int64_t &result) { + switch (inquiry) { + case HashInquiryKeyword("NEXTREC"): + case HashInquiryKeyword("NUMBER"): + case HashInquiryKeyword("POS"): + case HashInquiryKeyword("RECL"): + case HashInquiryKeyword("SIZE"): + result = -1; + return true; + default: + BadInquiryKeywordHashCrash(inquiry); + return false; + } +} + +InquireIOLengthState::InquireIOLengthState( + const char *sourceFile, int sourceLine) + : NoUnitIoStatementState{sourceFile, sourceLine, *this} {} + +bool InquireIOLengthState::Emit( + const char *, std::size_t n, std::size_t /*elementBytes*/) { + bytes_ += n; + return true; +} + } // namespace Fortran::runtime::io diff --git a/flang/runtime/memory.h b/flang/runtime/memory.h --- a/flang/runtime/memory.h +++ b/flang/runtime/memory.h @@ -42,7 +42,7 @@ public: explicit SizedNew(const Terminator &terminator) : terminator_{terminator} {} template - [[nodiscard]] OwningPtr operator()(std::size_t bytes, X &&... x) { + [[nodiscard]] OwningPtr operator()(std::size_t bytes, X &&...x) { return OwningPtr{new (AllocateMemoryOrCrash(terminator_, bytes)) A{std::forward(x)...}}; } @@ -53,7 +53,7 @@ template struct New : public SizedNew { using SizedNew::SizedNew; - template [[nodiscard]] OwningPtr operator()(X &&... x) { + template [[nodiscard]] OwningPtr operator()(X &&...x) { return SizedNew::operator()(sizeof(A), std::forward(x)...); } }; diff --git a/flang/runtime/tools.h b/flang/runtime/tools.h --- a/flang/runtime/tools.h +++ b/flang/runtime/tools.h @@ -18,6 +18,8 @@ class Terminator; +std::size_t TrimTrailingSpaces(const char *, std::size_t); + OwningPtr SaveDefaultCharacter( const char *, std::size_t, const Terminator &); diff --git a/flang/runtime/tools.cpp b/flang/runtime/tools.cpp --- a/flang/runtime/tools.cpp +++ b/flang/runtime/tools.cpp @@ -12,6 +12,13 @@ namespace Fortran::runtime { +std::size_t TrimTrailingSpaces(const char *s, std::size_t n) { + while (n > 0 && s[n - 1] == ' ') { + --n; + } + return n; +} + OwningPtr SaveDefaultCharacter( const char *s, std::size_t length, const Terminator &terminator) { if (s) { diff --git a/flang/runtime/unit-map.h b/flang/runtime/unit-map.h --- a/flang/runtime/unit-map.h +++ b/flang/runtime/unit-map.h @@ -34,6 +34,12 @@ return p ? *p : Create(n, terminator); } + // Unit look-up by name is needed for INQUIRE(FILE="...") + ExternalFileUnit *LookUp(const char *path) { + CriticalSection critical{lock_}; + return Find(path); + } + ExternalFileUnit &NewUnit(const Terminator &terminator) { CriticalSection critical{lock_}; return Create(nextNewUnit_--, terminator); @@ -72,6 +78,7 @@ } return nullptr; } + ExternalFileUnit *Find(const char *path); ExternalFileUnit &Create(int, const Terminator &); diff --git a/flang/runtime/unit-map.cpp b/flang/runtime/unit-map.cpp --- a/flang/runtime/unit-map.cpp +++ b/flang/runtime/unit-map.cpp @@ -72,6 +72,20 @@ } } +ExternalFileUnit *UnitMap::Find(const char *path) { + if (path) { + // TODO: Faster data structure + for (int j{0}; j < buckets_; ++j) { + for (Chain *p{bucket_[j].get()}; p; p = p->next.get()) { + if (p->unit.path() && std::strcmp(p->unit.path(), path) == 0) { + return &p->unit; + } + } + } + } + return nullptr; +} + ExternalFileUnit &UnitMap::Create(int n, const Terminator &terminator) { Chain &chain{*New{terminator}(n).release()}; chain.next.reset(&chain); diff --git a/flang/runtime/unit.h b/flang/runtime/unit.h --- a/flang/runtime/unit.h +++ b/flang/runtime/unit.h @@ -35,6 +35,7 @@ public: explicit ExternalFileUnit(int unitNumber) : unitNumber_{unitNumber} {} int unitNumber() const { return unitNumber_; } + bool swapEndianness() const { return swapEndianness_; } static ExternalFileUnit *LookUp(int unit); static ExternalFileUnit &LookUpOrCrash(int unit, const Terminator &); @@ -42,6 +43,7 @@ int unit, const Terminator &, bool &wasExtant); static ExternalFileUnit &LookUpOrCreateAnonymous( int unit, Direction, bool isUnformatted, const Terminator &); + static ExternalFileUnit *LookUp(const char *path); static ExternalFileUnit &CreateNew(int unit, const Terminator &); static ExternalFileUnit *LookUpForClose(int unit); static int NewUnit(const Terminator &); @@ -51,13 +53,15 @@ void OpenUnit(OpenStatus, std::optional, Position, OwningPtr &&path, std::size_t pathLength, Convert, IoErrorHandler &); + void OpenAnonymousUnit( + OpenStatus, std::optional, Position, Convert, IoErrorHandler &); void CloseUnit(CloseStatus, IoErrorHandler &); void DestroyClosed(); bool SetDirection(Direction, IoErrorHandler &); template - IoStatementState &BeginIoStatement(X &&... xs) { + IoStatementState &BeginIoStatement(X &&...xs) { // TODO: Child data transfer statements vs. locking lock_.Take(); // dropped in EndIoStatement() A &state{u_.emplace(std::forward(xs)...)}; @@ -111,7 +115,7 @@ ExternalListIoStatementState, ExternalListIoStatementState, UnformattedIoStatementState, - UnformattedIoStatementState, + UnformattedIoStatementState, InquireUnitState, ExternalMiscIoStatementState> u_; diff --git a/flang/runtime/unit.cpp b/flang/runtime/unit.cpp --- a/flang/runtime/unit.cpp +++ b/flang/runtime/unit.cpp @@ -59,20 +59,19 @@ ExternalFileUnit &result{ GetUnitMap().LookUpOrCreate(unit, terminator, exists)}; if (!exists) { - // I/O to an unconnected unit reads/creates a local file, e.g. fort.7 - std::size_t pathMaxLen{32}; - auto path{SizedNew{terminator}(pathMaxLen)}; - std::snprintf(path.get(), pathMaxLen, "fort.%d", unit); IoErrorHandler handler{terminator}; - result.OpenUnit( - dir == Direction::Input ? OpenStatus::Old : OpenStatus::Replace, - Action::ReadWrite, Position::Rewind, std::move(path), - std::strlen(path.get()), Convert::Native, handler); + result.OpenAnonymousUnit( + dir == Direction::Input ? OpenStatus::Unknown : OpenStatus::Replace, + Action::ReadWrite, Position::Rewind, Convert::Native, handler); result.isUnformatted = isUnformatted; } return result; } +ExternalFileUnit *ExternalFileUnit::LookUp(const char *path) { + return GetUnitMap().LookUp(path); +} + ExternalFileUnit &ExternalFileUnit::CreateNew( int unit, const Terminator &terminator) { bool wasExtant{false}; @@ -125,10 +124,7 @@ handler.SignalError(IostatOpenBadRecl, "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is invalid", unitNumber(), static_cast(*recordLength)); - } else if (!totalBytes) { - handler.SignalError(IostatOpenUnknownSize, - "OPEN(UNIT=%d,ACCESS='DIRECT'): file size is not known"); - } else if (*totalBytes % *recordLength != 0) { + } else if (totalBytes && (*totalBytes % *recordLength != 0)) { handler.SignalError(IostatOpenBadAppend, "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is not an " "even divisor of the file size %jd", @@ -137,7 +133,7 @@ } } if (position == Position::Append) { - if (*totalBytes && recordLength && *recordLength) { + if (totalBytes && recordLength && *recordLength) { endfileRecordNumber = 1 + (*totalBytes / *recordLength); } else { // Fake it so that we can backspace relative from the end @@ -149,6 +145,17 @@ } } +void ExternalFileUnit::OpenAnonymousUnit(OpenStatus status, + std::optional action, Position position, Convert convert, + IoErrorHandler &handler) { + // I/O to an unconnected unit reads/creates a local file, e.g. fort.7 + std::size_t pathMaxLen{32}; + auto path{SizedNew{handler}(pathMaxLen)}; + std::snprintf(path.get(), pathMaxLen, "fort.%d", unitNumber_); + OpenUnit(status, action, position, std::move(path), std::strlen(path.get()), + convert, handler); +} + void ExternalFileUnit::CloseUnit(CloseStatus status, IoErrorHandler &handler) { DoImpliedEndfile(handler); Flush(handler);