diff --git a/flang/runtime/connection.h b/flang/runtime/connection.h --- a/flang/runtime/connection.h +++ b/flang/runtime/connection.h @@ -26,9 +26,10 @@ // established in an OPEN statement. struct ConnectionAttributes { Access access{Access::Sequential}; // ACCESS='SEQUENTIAL', 'DIRECT', 'STREAM' - std::optional recordLength; // RECL= when fixed-length bool isUnformatted{false}; // FORM='UNFORMATTED' bool isUTF8{false}; // ENCODING='UTF-8' + bool isFixedRecordLength{false}; // RECL= on OPEN + std::optional recordLength; // RECL= or current record }; struct ConnectionState : public ConnectionAttributes { @@ -37,6 +38,12 @@ void HandleAbsolutePosition(std::int64_t); void HandleRelativePosition(std::int64_t); + void BeginRecord() { + positionInRecord = 0; + furthestPositionInRecord = 0; + leftTabLimit.reset(); + } + // Positions in a record file (sequential or direct, not stream) std::int64_t currentRecordNumber{1}; // 1 is first std::int64_t positionInRecord{0}; // offset in current record diff --git a/flang/runtime/edit-input.cpp b/flang/runtime/edit-input.cpp --- a/flang/runtime/edit-input.cpp +++ b/flang/runtime/edit-input.cpp @@ -13,14 +13,24 @@ namespace Fortran::runtime::io { +static std::optional PrepareInput( + IoStatementState &io, const DataEdit &edit, std::optional &remaining) { + remaining.reset(); + if (edit.descriptor == DataEdit::ListDirected) { + io.GetNextNonBlank(); + } else { + if (edit.width.value_or(0) > 0) { + remaining = *edit.width; + } + io.SkipSpaces(remaining); + } + return io.NextInField(remaining); +} + static bool EditBOZInput(IoStatementState &io, const DataEdit &edit, void *n, int base, int totalBitSize) { std::optional remaining; - if (edit.width) { - remaining = std::max(0, *edit.width); - } - io.SkipSpaces(remaining); - std::optional next{io.NextInField(remaining)}; + std::optional next{PrepareInput(io, edit, remaining)}; common::UnsignedInt128 value{0}; for (; next; next = io.NextInField(remaining)) { char32_t ch{*next}; @@ -54,14 +64,7 @@ // Returns false if there's a '-' sign static bool ScanNumericPrefix(IoStatementState &io, const DataEdit &edit, std::optional &next, std::optional &remaining) { - if (edit.descriptor != DataEdit::ListDirected && edit.width) { - remaining = std::max(0, *edit.width); - } else { - // list-directed, namelist, or (nonstandard) 0-width input editing - remaining.reset(); - } - io.SkipSpaces(remaining); - next = io.NextInField(remaining); + next = PrepareInput(io, edit, remaining); bool negative{false}; if (next) { negative = *next == '-'; @@ -310,11 +313,7 @@ return false; } std::optional remaining; - if (edit.width) { - remaining = std::max(0, *edit.width); - } - io.SkipSpaces(remaining); - std::optional next{io.NextInField(remaining)}; + std::optional next{PrepareInput(io, edit, remaining)}; if (next && *next == '.') { // skip optional period next = io.NextInField(remaining); } diff --git a/flang/runtime/file.h b/flang/runtime/file.h --- a/flang/runtime/file.h +++ b/flang/runtime/file.h @@ -39,6 +39,7 @@ void set_mayPosition(bool yes) { mayPosition_ = yes; } FileOffset position() const { return position_; } bool isTerminal() const { return isTerminal_; } + std::optional knownSize() const { return knownSize_; } bool IsOpen() const { return fd_ >= 0; } void Open(OpenStatus, Position, IoErrorHandler &); @@ -94,5 +95,7 @@ int nextId_; OwningPtr pending_; }; + +bool IsATerminal(int fd); } // namespace Fortran::runtime::io #endif // FORTRAN_RUNTIME_FILE_H_ diff --git a/flang/runtime/file.cpp b/flang/runtime/file.cpp --- a/flang/runtime/file.cpp +++ b/flang/runtime/file.cpp @@ -64,9 +64,11 @@ if (fd_ >= 0) { return; } + knownSize_.reset(); break; case OpenStatus::New: flags |= O_CREAT | O_EXCL; + knownSize_ = 0; break; case OpenStatus::Scratch: if (path_.get()) { @@ -74,15 +76,18 @@ path_.reset(); } fd_ = openfile_mkstemp(handler); + knownSize_ = 0; return; case OpenStatus::Replace: flags |= O_CREAT | O_TRUNC; + knownSize_ = 0; break; case OpenStatus::Unknown: if (fd_ >= 0) { return; } flags |= O_CREAT; + knownSize_.reset(); break; } // If we reach this point, we're opening a new file. @@ -104,7 +109,6 @@ handler.SignalErrno(); } pending_.reset(); - knownSize_.reset(); if (position == Position::Append && !RawSeekToEnd()) { handler.SignalErrno(); } @@ -152,17 +156,14 @@ if (!Seek(at, handler)) { return 0; } - if (maxBytes < minBytes) { - minBytes = maxBytes; - } + minBytes = std::min(minBytes, maxBytes); std::size_t got{0}; while (got < minBytes) { auto chunk{::read(fd_, buffer + got, maxBytes - got)}; if (chunk == 0) { handler.SignalEnd(); break; - } - if (chunk < 0) { + } else if (chunk < 0) { auto err{errno}; if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) { handler.SignalError(err); @@ -352,7 +353,9 @@ int OpenFile::PendingResult(const Terminator &terminator, int iostat) { int id{nextId_++}; - pending_.reset(&New{}(terminator, id, iostat, std::move(pending_))); + pending_ = New{terminator}(id, iostat, std::move(pending_)); return id; } + +bool IsATerminal(int fd) { return ::isatty(fd); } } // namespace Fortran::runtime::io diff --git a/flang/runtime/internal-unit.cpp b/flang/runtime/internal-unit.cpp --- a/flang/runtime/internal-unit.cpp +++ b/flang/runtime/internal-unit.cpp @@ -17,6 +17,7 @@ template InternalDescriptorUnit::InternalDescriptorUnit( Scalar scalar, std::size_t length) { + isFixedRecordLength = true; recordLength = length; endfileRecordNumber = 2; void *pointer{reinterpret_cast(const_cast(scalar))}; @@ -33,6 +34,7 @@ terminator, that.SizeInBytes() <= d.SizeInBytes(maxRank, true, 0)); new (&d) Descriptor{that}; d.Check(); + isFixedRecordLength = true; recordLength = d.ElementBytes(); endfileRecordNumber = d.Elements() + 1; } @@ -123,8 +125,7 @@ } } ++currentRecordNumber; - positionInRecord = 0; - furthestPositionInRecord = 0; + BeginRecord(); return true; } @@ -132,8 +133,7 @@ void InternalDescriptorUnit::BackspaceRecord(IoErrorHandler &handler) { RUNTIME_CHECK(handler, currentRecordNumber > 1); --currentRecordNumber; - positionInRecord = 0; - furthestPositionInRecord = 0; + BeginRecord(); } template class InternalDescriptorUnit; 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 @@ -28,9 +28,10 @@ void ** /*scratchArea*/, std::size_t /*scratchBytes*/, const char *sourceFile, int sourceLine) { Terminator oom{sourceFile, sourceLine}; - return &New>{}( - oom, descriptor, sourceFile, sourceLine) - .ioStatementState(); + return &New>{oom}( + descriptor, sourceFile, sourceLine) + .release() + ->ioStatementState(); } Cookie IONAME(BeginInternalArrayListOutput)(const Descriptor &descriptor, @@ -52,9 +53,10 @@ const char *format, std::size_t formatLength, void ** /*scratchArea*/, std::size_t /*scratchBytes*/, const char *sourceFile, int sourceLine) { Terminator oom{sourceFile, sourceLine}; - return &New>{}( - oom, descriptor, format, formatLength, sourceFile, sourceLine) - .ioStatementState(); + return &New>{oom}( + descriptor, format, formatLength, sourceFile, sourceLine) + .release() + ->ioStatementState(); } Cookie IONAME(BeginInternalArrayFormattedOutput)(const Descriptor &descriptor, @@ -78,9 +80,10 @@ void ** /*scratchArea*/, std::size_t /*scratchBytes*/, const char *sourceFile, int sourceLine) { Terminator oom{sourceFile, sourceLine}; - return &New>{}(oom, internal, - internalLength, format, formatLength, sourceFile, sourceLine) - .ioStatementState(); + return &New>{oom}( + internal, internalLength, format, formatLength, sourceFile, sourceLine) + .release() + ->ioStatementState(); } Cookie IONAME(BeginInternalFormattedOutput)(char *internal, @@ -118,10 +121,12 @@ terminator.Crash("List-directed I/O attempted on unformatted file"); return nullptr; } + IoErrorHandler handler{terminator}; + unit.SetDirection(DIR, handler); IoStatementState &io{unit.BeginIoStatement>( unit, sourceFile, sourceLine)}; if constexpr (DIR == Direction::Input) { - io.AdvanceRecord(); + unit.BeginReadingRecord(handler); } return &io; } @@ -151,11 +156,13 @@ terminator.Crash("Formatted I/O attempted on unformatted file"); return nullptr; } + IoErrorHandler handler{terminator}; + unit.SetDirection(DIR, handler); IoStatementState &io{ unit.BeginIoStatement>( unit, format, formatLength, sourceFile, sourceLine)}; if constexpr (DIR == Direction::Input) { - io.AdvanceRecord(); + unit.BeginReadingRecord(handler); } return &io; } @@ -178,17 +185,19 @@ Cookie BeginUnformattedIO( ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { Terminator terminator{sourceFile, sourceLine}; - ExternalFileUnit &file{ + ExternalFileUnit &unit{ ExternalFileUnit::LookUpOrCrash(unitNumber, terminator)}; - if (!file.isUnformatted) { + if (!unit.isUnformatted) { terminator.Crash("Unformatted output attempted on formatted file"); } - IoStatementState &io{file.BeginIoStatement>( - file, sourceFile, sourceLine)}; + IoStatementState &io{unit.BeginIoStatement>( + unit, sourceFile, sourceLine)}; + IoErrorHandler handler{terminator}; + unit.SetDirection(DIR, handler); if constexpr (DIR == Direction::Input) { - io.AdvanceRecord(); + unit.BeginReadingRecord(handler); } else { - if (file.access == Access::Sequential && !file.recordLength.has_value()) { + if (unit.access == Access::Sequential && !unit.isFixedRecordLength) { // Create space for (sub)record header to be completed by // UnformattedIoStatementState::EndIoStatement() io.Emit("\0\0\0\0", 4); // placeholder for record length header @@ -222,8 +231,10 @@ Cookie IONAME(BeginOpenNewUnit)( // OPEN(NEWUNIT=j) const char *sourceFile, int sourceLine) { Terminator terminator{sourceFile, sourceLine}; - return IONAME(BeginOpenUnit)( - ExternalFileUnit::NewUnit(terminator), sourceFile, sourceLine); + ExternalFileUnit &unit{ExternalFileUnit::LookUpOrCreate( + ExternalFileUnit::NewUnit(terminator), terminator)}; + return &unit.BeginIoStatement( + unit, false /*wasExtant*/, sourceFile, sourceLine); } Cookie IONAME(BeginClose)( @@ -234,11 +245,48 @@ } else { // CLOSE(UNIT=bad unit) is just a no-op Terminator oom{sourceFile, sourceLine}; - return &New{}(oom, sourceFile, sourceLine) - .ioStatementState(); + return &New{oom}(sourceFile, sourceLine) + .release() + ->ioStatementState(); } } +Cookie IONAME(BeginFlush)( + ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { + Terminator terminator{sourceFile, sourceLine}; + ExternalFileUnit &unit{ + ExternalFileUnit::LookUpOrCrash(unitNumber, terminator)}; + return &unit.BeginIoStatement( + unit, ExternalMiscIoStatementState::Flush, sourceFile, sourceLine); +} + +Cookie IONAME(BeginBackspace)( + ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { + Terminator terminator{sourceFile, sourceLine}; + ExternalFileUnit &unit{ + ExternalFileUnit::LookUpOrCrash(unitNumber, terminator)}; + return &unit.BeginIoStatement( + unit, ExternalMiscIoStatementState::Backspace, sourceFile, sourceLine); +} + +Cookie IONAME(BeginEndfile)( + ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { + Terminator terminator{sourceFile, sourceLine}; + ExternalFileUnit &unit{ + ExternalFileUnit::LookUpOrCrash(unitNumber, terminator)}; + return &unit.BeginIoStatement( + unit, ExternalMiscIoStatementState::Endfile, sourceFile, sourceLine); +} + +Cookie IONAME(BeginRewind)( + ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { + Terminator terminator{sourceFile, sourceLine}; + ExternalFileUnit &unit{ + ExternalFileUnit::LookUpOrCrash(unitNumber, terminator)}; + return &unit.BeginIoStatement( + unit, ExternalMiscIoStatementState::Rewind, sourceFile, sourceLine); +} + // Control list items void IONAME(EnableHandlers)(Cookie cookie, bool hasIoStat, bool hasErr, @@ -384,7 +432,7 @@ "REC= may not appear unless ACCESS='DIRECT'"); return false; } - if (!connection.recordLength) { + if (!connection.isFixedRecordLength || !connection.recordLength) { io.GetIoErrorHandler().SignalError("RECL= was not specified"); return false; } @@ -636,10 +684,11 @@ if (n <= 0) { io.GetIoErrorHandler().SignalError("RECL= must be greater than zero"); } - if (open->wasExtant() && open->unit().recordLength.has_value() && - *open->unit().recordLength != static_cast(n)) { + if (open->wasExtant() && open->unit().isFixedRecordLength && + open->unit().recordLength.value_or(n) != static_cast(n)) { open->SignalError("RECL= may not be changed for an open unit"); } + open->unit().isFixedRecordLength = true; open->unit().recordLength = n; return true; } @@ -750,7 +799,17 @@ if (auto *unf{io.get_if>()}) { return unf->Emit(x, length); } - io.GetIoErrorHandler().Crash("OutputUnformatted() called for an I/O " + io.GetIoErrorHandler().Crash("OutputUnformattedBlock() called for an I/O " + "statement that is not unformatted output"); + return false; +} + +bool IONAME(InputUnformattedBlock)(Cookie cookie, char *x, std::size_t length) { + IoStatementState &io{*cookie}; + if (auto *unf{io.get_if>()}) { + return unf->Receive(x, length); + } + io.GetIoErrorHandler().Crash("InputUnformattedBlock() called for an I/O " "statement that is not unformatted output"); return false; } 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 @@ -36,6 +36,7 @@ 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 { @@ -73,7 +74,8 @@ bool EmitRepeated(char, std::size_t); bool EmitField(const char *, std::size_t length, std::size_t width); - void SkipSpaces(std::optional &remaining); + + std::optional SkipSpaces(std::optional &remaining); std::optional NextInField(std::optional &remaining); std::optional GetNextNonBlank(); // can advance record @@ -94,7 +96,8 @@ std::reference_wrapper>, std::reference_wrapper>, std::reference_wrapper>, - std::reference_wrapper>> + std::reference_wrapper>, + std::reference_wrapper> u_; }; @@ -235,7 +238,7 @@ public: using ExternalIoStatementBase::ExternalIoStatementBase; int EndIoStatement(); - bool Emit(const char *, std::size_t chars /* not bytes */); + bool Emit(const char *, std::size_t); bool Emit(const char16_t *, std::size_t chars /* not bytes */); bool Emit(const char32_t *, std::size_t chars /* not bytes */); std::optional GetCurrentChar(); @@ -280,6 +283,7 @@ class UnformattedIoStatementState : public ExternalIoStatementState { public: using ExternalIoStatementState::ExternalIoStatementState; + bool Receive(char *, std::size_t); int EndIoStatement(); }; @@ -353,5 +357,17 @@ extern template class FormatControl< ExternalFormattedIoStatementState>; +class ExternalMiscIoStatementState : public ExternalIoStatementBase { +public: + enum Which { Flush, Backspace, Endfile, Rewind }; + ExternalMiscIoStatementState(ExternalFileUnit &unit, Which which, + const char *sourceFile = nullptr, int sourceLine = 0) + : ExternalIoStatementBase{unit, sourceFile, sourceLine}, which_{which} {} + int EndIoStatement(); + +private: + Which which_; +}; + } // namespace Fortran::runtime::io #endif // FORTRAN_RUNTIME_IO_STMT_H_ 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 @@ -13,6 +13,7 @@ #include "tools.h" #include "unit.h" #include +#include #include #include @@ -182,10 +183,10 @@ } template int ExternalIoStatementState::EndIoStatement() { + if (!unit().nonAdvancing) { + unit().AdvanceRecord(*this); + } if constexpr (DIR == Direction::Output) { - if (!unit().nonAdvancing) { - unit().AdvanceRecord(*this); - } unit().FlushIfTerminal(*this); } return ExternalIoStatementBase::EndIoStatement(); @@ -291,7 +292,7 @@ } void IoStatementState::HandleRelativePosition(std::int64_t n) { - return std::visit([=](auto &x) { x.get().HandleRelativePosition(n); }, u_); + std::visit([=](auto &x) { x.get().HandleRelativePosition(n); }, u_); } int IoStatementState::EndIoStatement() { @@ -347,15 +348,22 @@ } } -void IoStatementState::SkipSpaces(std::optional &remaining) { - if (!remaining || *remaining > 0) { - for (auto ch{GetCurrentChar()}; ch && ch == ' '; ch = GetCurrentChar()) { +std::optional IoStatementState::SkipSpaces( + std::optional &remaining) { + while (!remaining || *remaining > 0) { + if (auto ch{GetCurrentChar()}) { + if (*ch != ' ') { + return ch; + } HandleRelativePosition(1); - if (remaining && !--*remaining) { - break; + if (remaining) { + --*remaining; } + } else { + break; } } + return std::nullopt; } std::optional IoStatementState::NextInField( @@ -372,6 +380,7 @@ case '\'': case '"': case '*': + case '\n': // for stream access break; default: HandleRelativePosition(1); @@ -385,7 +394,8 @@ return next; } const ConnectionState &connection{GetConnectionState()}; - if (!connection.IsAtEOF() && connection.recordLength && + if (!connection.IsAtEOF() && connection.isFixedRecordLength && + connection.recordLength && connection.positionInRecord >= *connection.recordLength) { if (connection.modes.pad) { // PAD='YES' --*remaining; @@ -554,28 +564,38 @@ } template +bool UnformattedIoStatementState::Receive(char *data, std::size_t bytes) { + if constexpr (DIR == Direction::Output) { + this->Crash( + "UnformattedIoStatementState::Receive() called for output statement"); + } + return this->unit().Receive(data, bytes, *this); +} + +template int UnformattedIoStatementState::EndIoStatement() { - auto &ext{static_cast &>(*this)}; - ExternalFileUnit &unit{ext.unit()}; - if (unit.access == Access::Sequential && !unit.recordLength.has_value()) { - // Overwrite the first four bytes of the record with its length, - // and also append the length. These four bytes were skipped over - // in BeginUnformattedOutput(). - // TODO: Break very large records up into subrecords with negative - // headers &/or footers - union { - std::uint32_t u; - char c[sizeof u]; - } u; - u.u = unit.furthestPositionInRecord - sizeof u.c; - // TODO: Convert record length to little-endian on big-endian host? - if (!(ext.Emit(u.c, sizeof u.c) && - (ext.HandleAbsolutePosition(0), ext.Emit(u.c, sizeof u.c)) && - ext.AdvanceRecord())) { - return false; + ExternalFileUnit &unit{this->unit()}; + if constexpr (DIR == Direction::Output) { + if (unit.access == Access::Sequential && !unit.isFixedRecordLength) { + // Append the length of a sequential unformatted variable-length record + // as its footer, then overwrite the reserved first four bytes of the + // record with its length as its header. These four bytes were skipped + // over in BeginUnformattedOutput(). + // TODO: Break very large records up into subrecords with negative + // headers &/or footers + union { + std::uint32_t u; + char c[sizeof u]; + } u; + u.u = unit.furthestPositionInRecord - sizeof u; + // TODO: Convert record length to little-endian on big-endian host? + if (!(this->Emit(u.c, sizeof u) && + (this->HandleAbsolutePosition(0), this->Emit(u.c, sizeof u)))) { + return false; + } } } - return ext.EndIoStatement(); + return ExternalIoStatementState::EndIoStatement(); } template class InternalIoStatementState; @@ -592,4 +612,25 @@ template class ExternalListIoStatementState; template class UnformattedIoStatementState; template class UnformattedIoStatementState; + +int ExternalMiscIoStatementState::EndIoStatement() { + ExternalFileUnit &ext{unit()}; + switch (which_) { + case Flush: + ext.Flush(*this); + std::fflush(nullptr); // flushes C stdio output streams (12.9(2)) + break; + case Backspace: + ext.BackspaceRecord(*this); + break; + case Endfile: + ext.Endfile(*this); + break; + case Rewind: + ext.Rewind(*this); + break; + } + return ExternalIoStatementBase::EndIoStatement(); +} + } // namespace Fortran::runtime::io diff --git a/flang/runtime/iostat.h b/flang/runtime/iostat.h --- a/flang/runtime/iostat.h +++ b/flang/runtime/iostat.h @@ -45,6 +45,16 @@ IostatInternalWriteOverrun, IostatErrorInFormat, IostatErrorInKeyword, + IostatEndfileNonSequential, + IostatEndfileUnwritable, + IostatOpenBadRecl, + IostatOpenUnknownSize, + IostatOpenBadAppend, + IostatWriteToReadOnly, + IostatReadFromWriteOnly, + IostatBackspaceNonSequential, + IostatBackspaceAtFirstRecord, + IostatRewindNonSequential, }; const char *IostatErrorString(int); diff --git a/flang/runtime/iostat.cpp b/flang/runtime/iostat.cpp --- a/flang/runtime/iostat.cpp +++ b/flang/runtime/iostat.cpp @@ -33,6 +33,26 @@ return "Invalid FORMAT"; case IostatErrorInKeyword: return "Bad keyword argument value"; + case IostatEndfileNonSequential: + return "ENDFILE on non-sequential file"; + case IostatEndfileUnwritable: + return "ENDFILE on read-only file"; + case IostatOpenBadRecl: + return "OPEN with bad RECL= value"; + case IostatOpenUnknownSize: + return "OPEN of file of unknown size"; + case IostatOpenBadAppend: + return "OPEN(POSITION='APPEND') of unpositionable file"; + case IostatWriteToReadOnly: + return "Attempted output to read-only file"; + case IostatReadFromWriteOnly: + return "Attempted input from write-only file"; + case IostatBackspaceNonSequential: + return "BACKSPACE on non-sequential file"; + case IostatBackspaceAtFirstRecord: + return "BACKSPACE at first record"; + case IostatRewindNonSequential: + return "REWIND on non-sequential file"; default: return nullptr; } diff --git a/flang/runtime/memory.h b/flang/runtime/memory.h --- a/flang/runtime/memory.h +++ b/flang/runtime/memory.h @@ -32,20 +32,32 @@ p = nullptr; } -template struct New { - template - [[nodiscard]] A &operator()(const Terminator &terminator, X &&... x) { - return *new (AllocateMemoryOrCrash(terminator, sizeof(A))) - A{std::forward(x)...}; - } -}; - template struct OwningPtrDeleter { void operator()(A *p) { FreeMemory(p); } }; template using OwningPtr = std::unique_ptr>; +template class SizedNew { +public: + explicit SizedNew(const Terminator &terminator) : terminator_{terminator} {} + template + [[nodiscard]] OwningPtr operator()(std::size_t bytes, X &&... x) { + return OwningPtr{new (AllocateMemoryOrCrash(terminator_, bytes)) + A{std::forward(x)...}}; + } + +private: + const Terminator &terminator_; +}; + +template struct New : public SizedNew { + using SizedNew::SizedNew; + template [[nodiscard]] OwningPtr operator()(X &&... x) { + return SizedNew::operator()(sizeof(A), std::forward(x)...); + } +}; + template struct Allocator { using value_type = A; explicit Allocator(const Terminator &t) : terminator{t} {} diff --git a/flang/runtime/stop.h b/flang/runtime/stop.h --- a/flang/runtime/stop.h +++ b/flang/runtime/stop.h @@ -20,6 +20,7 @@ bool isErrorStop DEFAULT_VALUE(false), bool quiet DEFAULT_VALUE(false)); NORETURN void RTNAME(StopStatementText)(const char *, bool isErrorStop DEFAULT_VALUE(false), bool quiet DEFAULT_VALUE(false)); +void RTNAME(PauseStatement)(NO_ARGUMENTS); NORETURN void RTNAME(FailImageStatement)(NO_ARGUMENTS); NORETURN void RTNAME(ProgramEndStatement)(NO_ARGUMENTS); diff --git a/flang/runtime/stop.cpp b/flang/runtime/stop.cpp --- a/flang/runtime/stop.cpp +++ b/flang/runtime/stop.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "stop.h" +#include "file.h" #include "io-error.h" #include "terminator.h" #include "unit.h" @@ -71,6 +72,19 @@ std::exit(EXIT_FAILURE); } +void RTNAME(PauseStatement)() { + if (Fortran::runtime::io::IsATerminal(0)) { + Fortran::runtime::io::IoErrorHandler handler{"PAUSE statement"}; + Fortran::runtime::io::ExternalFileUnit::FlushAll(handler); + std::fputs("Fortran PAUSE: hit RETURN to continue:", stderr); + std::fflush(nullptr); + if (std::fgetc(stdin) == EOF) { + CloseAllExternalUnits("PAUSE statement"); + std::exit(EXIT_SUCCESS); + } + } +} + [[noreturn]] void RTNAME(FailImageStatement)() { Fortran::runtime::NotifyOtherImagesOfFailImageStatement(); CloseAllExternalUnits("FAIL IMAGE statement"); diff --git a/flang/runtime/terminator.h b/flang/runtime/terminator.h --- a/flang/runtime/terminator.h +++ b/flang/runtime/terminator.h @@ -34,7 +34,8 @@ const char *predicate, const char *file, int line) const; // For test harnessing - overrides CrashArgs(). - static void RegisterCrashHandler(void (*)(const char *, va_list &)); + static void RegisterCrashHandler(void (*)(const char *sourceFile, + int sourceLine, const char *message, va_list &ap)); private: const char *sourceFileName_{nullptr}; diff --git a/flang/runtime/terminator.cpp b/flang/runtime/terminator.cpp --- a/flang/runtime/terminator.cpp +++ b/flang/runtime/terminator.cpp @@ -18,17 +18,18 @@ CrashArgs(message, ap); } -static void (*crashHandler)(const char *, va_list &){nullptr}; +static void (*crashHandler)(const char *, int, const char *, va_list &){ + nullptr}; void Terminator::RegisterCrashHandler( - void (*handler)(const char *, va_list &)) { + void (*handler)(const char *, int, const char *, va_list &)) { crashHandler = handler; } [[noreturn]] void Terminator::CrashArgs( const char *message, va_list &ap) const { if (crashHandler) { - crashHandler(message, ap); + crashHandler(sourceFileName_, sourceLine_, message, ap); } std::fputs("\nfatal Fortran runtime error", stderr); if (sourceFileName_) { 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 @@ -49,6 +49,7 @@ void DestroyClosed(ExternalFileUnit &); void CloseAll(IoErrorHandler &); + void FlushAll(IoErrorHandler &); private: struct Chain { 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 @@ -63,8 +63,17 @@ } } +void UnitMap::FlushAll(IoErrorHandler &handler) { + CriticalSection critical{lock_}; + for (int j{0}; j < buckets_; ++j) { + for (Chain *p{bucket_[j].get()}; p; p = p->next.get()) { + p->unit.Flush(handler); + } + } +} + ExternalFileUnit &UnitMap::Create(int n, const Terminator &terminator) { - Chain &chain{New{}(terminator, n)}; + Chain &chain{*New{terminator}(n).release()}; chain.next.reset(&chain); bucket_[Hash(n)].swap(chain.next); // pushes new node as list head return chain.unit; diff --git a/flang/runtime/unit.h b/flang/runtime/unit.h --- a/flang/runtime/unit.h +++ b/flang/runtime/unit.h @@ -43,12 +43,15 @@ static ExternalFileUnit *LookUpForClose(int unit); static int NewUnit(const Terminator &); static void CloseAll(IoErrorHandler &); + static void FlushAll(IoErrorHandler &); void OpenUnit(OpenStatus, Position, OwningPtr &&path, std::size_t pathLength, IoErrorHandler &); void CloseUnit(CloseStatus, IoErrorHandler &); void DestroyClosed(); + bool SetDirection(Direction, IoErrorHandler &); + template IoStatementState &BeginIoStatement(X &&... xs) { // TODO: Child data transfer statements vs. locking @@ -61,27 +64,38 @@ return *io_; } - bool Emit(const char *, std::size_t bytes, IoErrorHandler &); + bool Emit(const char *, std::size_t, IoErrorHandler &); + bool Receive(char *, std::size_t, IoErrorHandler &); std::optional GetCurrentChar(IoErrorHandler &); void SetLeftTabLimit(); + void BeginReadingRecord(IoErrorHandler &); bool AdvanceRecord(IoErrorHandler &); void BackspaceRecord(IoErrorHandler &); void FlushIfTerminal(IoErrorHandler &); + void Endfile(IoErrorHandler &); + void Rewind(IoErrorHandler &); void EndIoStatement(); void SetPosition(std::int64_t pos) { frameOffsetInFile_ = pos; recordOffsetInFrame_ = 0; + BeginRecord(); } private: static UnitMap &GetUnitMap(); - void NextSequentialUnformattedInputRecord(IoErrorHandler &); - void NextSequentialFormattedInputRecord(IoErrorHandler &); - void BackspaceSequentialUnformattedRecord(IoErrorHandler &); - void BackspaceSequentialFormattedRecord(IoErrorHandler &); + const char *FrameNextInput(IoErrorHandler &, std::size_t); + void BeginSequentialVariableUnformattedInputRecord(IoErrorHandler &); + void BeginSequentialVariableFormattedInputRecord(IoErrorHandler &); + void BackspaceFixedRecord(IoErrorHandler &); + void BackspaceVariableUnformattedRecord(IoErrorHandler &); + void BackspaceVariableFormattedRecord(IoErrorHandler &); + bool SetSequentialVariableFormattedRecordLength(); + void DoImpliedEndfile(IoErrorHandler &); + void DoEndfile(IoErrorHandler &); int unitNumber_{-1}; - bool isReading_{false}; + Direction direction_{Direction::Output}; + bool impliedEndfile_{false}; // seq. output has taken place Lock lock_; @@ -92,7 +106,8 @@ ExternalListIoStatementState, ExternalListIoStatementState, UnformattedIoStatementState, - UnformattedIoStatementState> + UnformattedIoStatementState, + ExternalMiscIoStatementState> u_; // Points to the active alternative (if any) in u_ for use as a Cookie @@ -100,9 +115,10 @@ // Subtle: The beginning of the frame can't be allowed to advance // during a single list-directed READ due to the possibility of a - // multi-record CHARACTER value with a "r*" repeat count. + // multi-record CHARACTER value with a "r*" repeat count. So we + // manage the frame and the current record therein separately. std::int64_t frameOffsetInFile_{0}; - std::int64_t recordOffsetInFrame_{0}; // of currentRecordNumber + std::size_t recordOffsetInFrame_{0}; // of currentRecordNumber }; } // namespace Fortran::runtime::io diff --git a/flang/runtime/unit.cpp b/flang/runtime/unit.cpp --- a/flang/runtime/unit.cpp +++ b/flang/runtime/unit.cpp @@ -17,6 +17,7 @@ // should work without a Fortran main program. static Lock unitMapLock; static UnitMap *unitMap{nullptr}; +static ExternalFileUnit *defaultInput{nullptr}; static ExternalFileUnit *defaultOutput{nullptr}; void FlushOutputOnCrash(const Terminator &terminator) { @@ -70,14 +71,48 @@ return; } // Otherwise, OPEN on open unit with new FILE= implies CLOSE + DoImpliedEndfile(handler); Flush(handler); Close(CloseStatus::Keep, handler); } set_path(std::move(newPath), newPathLength); Open(status, position, handler); + auto totalBytes{knownSize()}; + if (access == Access::Direct) { + if (!isFixedRecordLength || !recordLength) { + handler.SignalError(IostatOpenBadRecl, + "OPEN(UNIT=%d,ACCESS='DIRECT'): record length is not known", + unitNumber()); + } else if (*recordLength <= 0) { + 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) { + handler.SignalError(IostatOpenBadAppend, + "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is not an " + "even divisor of the file size %jd", + unitNumber(), static_cast(*recordLength), + static_cast(*totalBytes)); + } + } + if (position == Position::Append) { + if (*totalBytes && recordLength && *recordLength) { + endfileRecordNumber = 1 + (*totalBytes / *recordLength); + } else { + // Fake it so that we can backspace relative from the end + endfileRecordNumber = std::numeric_limits::max() - 1; + } + currentRecordNumber = *endfileRecordNumber; + } else { + currentRecordNumber = 1; + } } void ExternalFileUnit::CloseUnit(CloseStatus status, IoErrorHandler &handler) { + DoImpliedEndfile(handler); Flush(handler); Close(status, handler); } @@ -86,6 +121,29 @@ GetUnitMap().DestroyClosed(*this); // destroys *this } +bool ExternalFileUnit::SetDirection( + Direction direction, IoErrorHandler &handler) { + if (direction == Direction::Input) { + if (mayRead()) { + direction_ = Direction::Input; + return true; + } else { + handler.SignalError(IostatReadFromWriteOnly, + "READ(UNIT=%d) with ACTION='WRITE'", unitNumber()); + return false; + } + } else { + if (mayWrite()) { + direction_ = Direction::Output; + return true; + } else { + handler.SignalError(IostatWriteToReadOnly, + "WRITE(UNIT=%d) with ACTION='READ'", unitNumber()); + return false; + } + } +} + UnitMap &ExternalFileUnit::GetUnitMap() { if (unitMap) { return *unitMap; @@ -95,18 +153,22 @@ return *unitMap; } Terminator terminator{__FILE__, __LINE__}; - unitMap = &New{}(terminator); + IoErrorHandler handler{terminator}; + unitMap = New{terminator}().release(); ExternalFileUnit &out{ExternalFileUnit::LookUpOrCreate(6, terminator)}; out.Predefine(1); out.set_mayRead(false); out.set_mayWrite(true); out.set_mayPosition(false); + out.SetDirection(Direction::Output, handler); defaultOutput = &out; ExternalFileUnit &in{ExternalFileUnit::LookUpOrCreate(5, terminator)}; in.Predefine(0); in.set_mayRead(true); in.set_mayWrite(false); in.set_mayPosition(false); + in.SetDirection(Direction::Input, handler); + defaultInput = ∈ // TODO: Set UTF-8 mode from the environment return *unitMap; } @@ -120,53 +182,106 @@ defaultOutput = nullptr; } +void ExternalFileUnit::FlushAll(IoErrorHandler &handler) { + CriticalSection critical{unitMapLock}; + if (unitMap) { + unitMap->FlushAll(handler); + } +} + bool ExternalFileUnit::Emit( const char *data, std::size_t bytes, IoErrorHandler &handler) { auto furthestAfter{std::max(furthestPositionInRecord, positionInRecord + static_cast(bytes))}; if (furthestAfter > recordLength.value_or(furthestAfter)) { - handler.SignalError(IostatRecordWriteOverrun); + handler.SignalError(IostatRecordWriteOverrun, + "Attempt to write %zd bytes to position %jd in a fixed-size record of " + "%jd bytes", + bytes, static_cast(positionInRecord), + static_cast(*recordLength)); return false; } WriteFrame(frameOffsetInFile_, recordOffsetInFrame_ + furthestAfter, handler); if (positionInRecord > furthestPositionInRecord) { - std::memset(Frame() + furthestPositionInRecord, ' ', + std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord, ' ', positionInRecord - furthestPositionInRecord); } - std::memcpy(Frame() + positionInRecord, data, bytes); + std::memcpy(Frame() + recordOffsetInFrame_ + positionInRecord, data, bytes); positionInRecord += bytes; furthestPositionInRecord = furthestAfter; return true; } +bool ExternalFileUnit::Receive( + char *data, std::size_t bytes, IoErrorHandler &handler) { + RUNTIME_CHECK(handler, direction_ == Direction::Input); + auto furthestAfter{std::max(furthestPositionInRecord, + positionInRecord + static_cast(bytes))}; + if (furthestAfter > recordLength.value_or(furthestAfter)) { + handler.SignalError(IostatRecordReadOverrun, + "Attempt to read %zd bytes at position %jd in a record of %jd bytes", + bytes, static_cast(positionInRecord), + static_cast(*recordLength)); + return false; + } + auto need{recordOffsetInFrame_ + furthestAfter}; + auto got{ReadFrame(frameOffsetInFile_, need, handler)}; + if (got >= need) { + std::memcpy(data, Frame() + recordOffsetInFrame_ + positionInRecord, bytes); + positionInRecord += bytes; + furthestPositionInRecord = furthestAfter; + return true; + } else { + handler.SignalEnd(); + endfileRecordNumber = currentRecordNumber; + return false; + } +} + std::optional ExternalFileUnit::GetCurrentChar( IoErrorHandler &handler) { - isReading_ = true; // TODO: manage read/write transitions - if (isUnformatted) { - handler.Crash("GetCurrentChar() called for unformatted input"); - return std::nullopt; - } - std::size_t chunk{256}; // for stream input - if (recordLength.has_value()) { - if (positionInRecord >= *recordLength) { - return std::nullopt; - } - chunk = *recordLength - positionInRecord; + RUNTIME_CHECK(handler, direction_ == Direction::Input); + if (const char *p{FrameNextInput(handler, 1)}) { + // TODO: UTF-8 decoding; may have to get more bytes in a loop + return *p; } - auto at{recordOffsetInFrame_ + positionInRecord}; - std::size_t need{static_cast(at + 1)}; - std::size_t want{need + chunk}; - auto got{ReadFrame(frameOffsetInFile_, want, handler)}; - if (got <= need) { - endfileRecordNumber = currentRecordNumber; + return std::nullopt; +} + +const char *ExternalFileUnit::FrameNextInput( + IoErrorHandler &handler, std::size_t bytes) { + RUNTIME_CHECK(handler, !isUnformatted); + if (static_cast(positionInRecord + bytes) <= + recordLength.value_or(positionInRecord + bytes)) { + auto at{recordOffsetInFrame_ + positionInRecord}; + auto need{static_cast(at + bytes)}; + auto got{ReadFrame(frameOffsetInFile_, need, handler)}; + SetSequentialVariableFormattedRecordLength(); + if (got >= need) { + return Frame() + at; + } handler.SignalEnd(); - return std::nullopt; + endfileRecordNumber = currentRecordNumber; } - const char *p{Frame() + at}; - if (isUTF8) { - // TODO: UTF-8 decoding + return nullptr; +} + +bool ExternalFileUnit::SetSequentialVariableFormattedRecordLength() { + if (recordLength || access != Access::Sequential) { + return true; + } + if (FrameLength() > recordOffsetInFrame_) { + const char *record{Frame() + recordOffsetInFrame_}; + if (const char *nl{reinterpret_cast( + std::memchr(record, '\n', FrameLength() - recordOffsetInFrame_))}) { + recordLength = nl - record; + if (*recordLength > 0 && record[*recordLength - 1] == '\r') { + --*recordLength; + } + return true; + } } - return *p; + return false; } void ExternalFileUnit::SetLeftTabLimit() { @@ -174,55 +289,87 @@ positionInRecord = furthestPositionInRecord; } +void ExternalFileUnit::BeginReadingRecord(IoErrorHandler &handler) { + RUNTIME_CHECK(handler, direction_ == Direction::Input); + if (access == Access::Sequential) { + if (endfileRecordNumber && currentRecordNumber >= *endfileRecordNumber) { + handler.SignalEnd(); + } else if (isFixedRecordLength) { + RUNTIME_CHECK(handler, recordLength.has_value()); + auto need{static_cast(recordOffsetInFrame_ + *recordLength)}; + auto got{ReadFrame(frameOffsetInFile_, need, handler)}; + if (got < need) { + handler.SignalEnd(); + } + } else if (isUnformatted) { + BeginSequentialVariableUnformattedInputRecord(handler); + } else { // formatted + BeginSequentialVariableFormattedInputRecord(handler); + } + } +} + bool ExternalFileUnit::AdvanceRecord(IoErrorHandler &handler) { bool ok{true}; - if (isReading_) { + if (direction_ == Direction::Input) { if (access == Access::Sequential) { - if (isUnformatted) { - NextSequentialUnformattedInputRecord(handler); - } else { - NextSequentialFormattedInputRecord(handler); + RUNTIME_CHECK(handler, recordLength.has_value()); + if (isFixedRecordLength) { + frameOffsetInFile_ += recordOffsetInFrame_ + *recordLength; + recordOffsetInFrame_ = 0; + } else if (isUnformatted) { + // Retain footer in frame for more efficient BACKSPACE + frameOffsetInFile_ += recordOffsetInFrame_ + *recordLength; + recordOffsetInFrame_ = sizeof(std::uint32_t); + recordLength.reset(); + } else { // formatted + if (Frame()[recordOffsetInFrame_ + *recordLength] == '\r') { + ++recordOffsetInFrame_; + } + recordOffsetInFrame_ += *recordLength + 1; + RUNTIME_CHECK(handler, Frame()[recordOffsetInFrame_ - 1] == '\n'); + recordLength.reset(); } } - } else if (!isUnformatted) { - if (recordLength.has_value()) { - // fill fixed-size record - if (furthestPositionInRecord < *recordLength) { - WriteFrame(frameOffsetInFile_, *recordLength, handler); - std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord, - ' ', *recordLength - furthestPositionInRecord); + } else { // Direction::Output + if (!isUnformatted) { + if (isFixedRecordLength && recordLength) { + if (furthestPositionInRecord < *recordLength) { + WriteFrame(frameOffsetInFile_, *recordLength, handler); + std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord, + ' ', *recordLength - furthestPositionInRecord); + } + } else { + positionInRecord = furthestPositionInRecord; + ok &= Emit("\n", 1, handler); // TODO: Windows CR+LF } - } else { - positionInRecord = furthestPositionInRecord; - ok &= Emit("\n", 1, handler); // TODO: Windows CR+LF - frameOffsetInFile_ += recordOffsetInFrame_ + furthestPositionInRecord; - recordOffsetInFrame_ = 0; } + frameOffsetInFile_ += + recordOffsetInFrame_ + recordLength.value_or(furthestPositionInRecord); + recordOffsetInFrame_ = 0; + impliedEndfile_ = true; } ++currentRecordNumber; - positionInRecord = 0; - furthestPositionInRecord = 0; - leftTabLimit.reset(); + BeginRecord(); return ok; } void ExternalFileUnit::BackspaceRecord(IoErrorHandler &handler) { - if (!isReading_) { - handler.Crash("ExternalFileUnit::BackspaceRecord() called during writing"); - // TODO: create endfile record, &c. - } - if (access == Access::Sequential) { - if (isUnformatted) { - BackspaceSequentialUnformattedRecord(handler); + if (access != Access::Sequential) { + handler.SignalError(IostatBackspaceNonSequential, + "BACKSPACE(UNIT=%d) on non-sequential file", unitNumber()); + } else { + DoImpliedEndfile(handler); + --currentRecordNumber; + BeginRecord(); + if (isFixedRecordLength) { + BackspaceFixedRecord(handler); + } else if (isUnformatted) { + BackspaceVariableUnformattedRecord(handler); } else { - BackspaceSequentialFormattedRecord(handler); + BackspaceVariableFormattedRecord(handler); } - } else { - // TODO } - positionInRecord = 0; - furthestPositionInRecord = 0; - leftTabLimit.reset(); } void ExternalFileUnit::FlushIfTerminal(IoErrorHandler &handler) { @@ -231,6 +378,30 @@ } } +void ExternalFileUnit::Endfile(IoErrorHandler &handler) { + if (access != Access::Sequential) { + handler.SignalError(IostatEndfileNonSequential, + "ENDFILE(UNIT=%d) on non-sequential file", unitNumber()); + } else if (!mayWrite()) { + handler.SignalError(IostatEndfileUnwritable, + "ENDFILE(UNIT=%d) on read-only file", unitNumber()); + } else { + DoEndfile(handler); + } +} + +void ExternalFileUnit::Rewind(IoErrorHandler &handler) { + if (access == Access::Direct) { + handler.SignalError(IostatRewindNonSequential, + "REWIND(UNIT=%d) on non-sequential file", unitNumber()); + } else { + DoImpliedEndfile(handler); + SetPosition(0); + currentRecordNumber = 1; + // TODO: reset endfileRecordNumber? + } +} + void ExternalFileUnit::EndIoStatement() { frameOffsetInFile_ += recordOffsetInFrame_; recordOffsetInFrame_ = 0; @@ -239,51 +410,36 @@ lock_.Drop(); } -void ExternalFileUnit::NextSequentialUnformattedInputRecord( +void ExternalFileUnit::BeginSequentialVariableUnformattedInputRecord( IoErrorHandler &handler) { std::int32_t header{0}, footer{0}; - // Retain previous footer (if any) in frame for more efficient BACKSPACE - std::size_t retain{sizeof header}; - if (recordLength) { // not first record - advance to next - ++currentRecordNumber; - if (endfileRecordNumber && currentRecordNumber >= *endfileRecordNumber) { - handler.SignalEnd(); - return; - } - frameOffsetInFile_ += - recordOffsetInFrame_ + *recordLength + 2 * sizeof header; - recordOffsetInFrame_ = 0; - } else { - retain = 0; - } - std::size_t need{retain + sizeof header}; - std::size_t got{ReadFrame(frameOffsetInFile_ - retain, need, handler)}; + std::size_t need{recordOffsetInFrame_ + sizeof header}; + std::size_t got{ReadFrame(frameOffsetInFile_, need, handler)}; // Try to emit informative errors to help debug corrupted files. const char *error{nullptr}; if (got < need) { - if (got == retain) { + if (got == recordOffsetInFrame_) { handler.SignalEnd(); } else { - error = "Unformatted sequential file input failed at record #%jd (file " - "offset %jd): truncated record header"; + error = "Unformatted variable-length sequential file input failed at " + "record #%jd (file offset %jd): truncated record header"; } } else { - std::memcpy(&header, Frame() + retain, sizeof header); - need = retain + header + 2 * sizeof header; - got = ReadFrame(frameOffsetInFile_ - retain, - need + sizeof header /* next one */, handler); + std::memcpy(&header, Frame() + recordOffsetInFrame_, sizeof header); + recordLength = sizeof header + header; // does not include footer + need = recordOffsetInFrame_ + *recordLength + sizeof footer; + got = ReadFrame(frameOffsetInFile_, need, handler); if (got < need) { - error = "Unformatted sequential file input failed at record #%jd (file " - "offset %jd): hit EOF reading record with length %jd bytes"; + error = "Unformatted variable-length sequential file input failed at " + "record #%jd (file offset %jd): hit EOF reading record with " + "length %jd bytes"; } else { - const char *start{Frame() + retain + sizeof header}; - std::memcpy(&footer, start + header, sizeof footer); + std::memcpy(&footer, Frame() + recordOffsetInFrame_ + *recordLength, + sizeof footer); if (footer != header) { - error = "Unformatted sequential file input failed at record #%jd (file " - "offset %jd): record header has length %jd that does not match " - "record footer (%jd)"; - } else { - recordLength = header; + error = "Unformatted variable-length sequential file input failed at " + "record #%jd (file offset %jd): record header has length %jd " + "that does not match record footer (%jd)"; } } } @@ -291,70 +447,66 @@ handler.SignalError(error, static_cast(currentRecordNumber), static_cast(frameOffsetInFile_), static_cast(header), static_cast(footer)); + // TODO: error recovery } positionInRecord = sizeof header; } -void ExternalFileUnit::NextSequentialFormattedInputRecord( +void ExternalFileUnit::BeginSequentialVariableFormattedInputRecord( IoErrorHandler &handler) { - static constexpr std::size_t chunk{256}; - std::size_t length{0}; - if (recordLength.has_value()) { - // not first record - advance to next - ++currentRecordNumber; - if (endfileRecordNumber && currentRecordNumber >= *endfileRecordNumber) { - handler.SignalEnd(); - return; - } - if (Frame()[*recordLength] == '\r') { - ++*recordLength; - } - recordOffsetInFrame_ += *recordLength + 1; + if (this == defaultInput && defaultOutput) { + defaultOutput->Flush(handler); } - while (true) { - std::size_t got{ReadFrame( - frameOffsetInFile_, recordOffsetInFrame_ + length + chunk, handler)}; - if (got <= recordOffsetInFrame_ + length) { + std::size_t length{0}; + do { + std::size_t need{recordOffsetInFrame_ + length + 1}; + length = ReadFrame(frameOffsetInFile_, need, handler); + if (length < need) { handler.SignalEnd(); break; } - const char *frame{Frame() + recordOffsetInFrame_}; - if (const char *nl{reinterpret_cast( - std::memchr(frame + length, '\n', chunk))}) { - recordLength = nl - (frame + length) + 1; - if (*recordLength > 0 && frame[*recordLength - 1] == '\r') { - --*recordLength; - } - return; - } - length += got; + } while (!SetSequentialVariableFormattedRecordLength()); +} + +void ExternalFileUnit::BackspaceFixedRecord(IoErrorHandler &handler) { + RUNTIME_CHECK(handler, recordLength.has_value()); + if (frameOffsetInFile_ < *recordLength) { + handler.SignalError(IostatBackspaceAtFirstRecord); + } else { + frameOffsetInFile_ -= *recordLength; } } -void ExternalFileUnit::BackspaceSequentialUnformattedRecord( +void ExternalFileUnit::BackspaceVariableUnformattedRecord( IoErrorHandler &handler) { std::int32_t header{0}, footer{0}; - RUNTIME_CHECK(handler, currentRecordNumber > 1); - --currentRecordNumber; - int overhead{static_cast(2 * sizeof header)}; + auto headerBytes{static_cast(sizeof header)}; + frameOffsetInFile_ += recordOffsetInFrame_; + recordOffsetInFrame_ = 0; + if (frameOffsetInFile_ <= headerBytes) { + handler.SignalError(IostatBackspaceAtFirstRecord); + return; + } // Error conditions here cause crashes, not file format errors, because the // validity of the file structure before the current record will have been - // checked informatively in NextSequentialUnformattedInputRecord(). - RUNTIME_CHECK(handler, frameOffsetInFile_ >= overhead); + // checked informatively in NextSequentialVariableUnformattedInputRecord(). std::size_t got{ - ReadFrame(frameOffsetInFile_ - sizeof footer, sizeof footer, handler)}; + ReadFrame(frameOffsetInFile_ - headerBytes, headerBytes, handler)}; RUNTIME_CHECK(handler, got >= sizeof footer); std::memcpy(&footer, Frame(), sizeof footer); - RUNTIME_CHECK(handler, frameOffsetInFile_ >= footer + overhead); - frameOffsetInFile_ -= footer + 2 * sizeof footer; - auto extra{std::max(sizeof footer, frameOffsetInFile_)}; - std::size_t want{extra + footer + 2 * sizeof footer}; - got = ReadFrame(frameOffsetInFile_ - extra, want, handler); - RUNTIME_CHECK(handler, got >= want); - std::memcpy(&header, Frame() + extra, sizeof header); - RUNTIME_CHECK(handler, header == footer); - positionInRecord = sizeof header; recordLength = footer; + RUNTIME_CHECK(handler, frameOffsetInFile_ >= *recordLength + 2 * headerBytes); + frameOffsetInFile_ -= *recordLength + 2 * headerBytes; + if (frameOffsetInFile_ >= headerBytes) { + frameOffsetInFile_ -= headerBytes; + recordOffsetInFrame_ = headerBytes; + } + auto need{static_cast( + recordOffsetInFrame_ + sizeof header + *recordLength)}; + got = ReadFrame(frameOffsetInFile_, need, handler); + RUNTIME_CHECK(handler, got >= need); + std::memcpy(&header, Frame() + recordOffsetInFrame_, sizeof header); + RUNTIME_CHECK(handler, header == *recordLength); } // There's no portable memrchr(), unfortunately, and strrchr() would @@ -368,50 +520,54 @@ return nullptr; } -void ExternalFileUnit::BackspaceSequentialFormattedRecord( +void ExternalFileUnit::BackspaceVariableFormattedRecord( IoErrorHandler &handler) { - std::int64_t start{frameOffsetInFile_ + recordOffsetInFrame_}; - --currentRecordNumber; - RUNTIME_CHECK(handler, currentRecordNumber > 0); - if (currentRecordNumber == 1) { - // To simplify the code below, treat a backspace to the first record - // as a special case; - RUNTIME_CHECK(handler, start > 0); - *recordLength = start - 1; - frameOffsetInFile_ = 0; - recordOffsetInFrame_ = 0; - ReadFrame(0, *recordLength + 1, handler); - } else { - RUNTIME_CHECK(handler, start > 1); - std::int64_t at{start - 2}; // byte before previous record's newline - while (true) { + // File offset of previous record's newline + auto prevNL{ + frameOffsetInFile_ + static_cast(recordOffsetInFrame_) - 1}; + if (prevNL < 0) { + handler.SignalError(IostatBackspaceAtFirstRecord); + return; + } + while (true) { + if (frameOffsetInFile_ < prevNL) { if (const char *p{ - FindLastNewline(Frame(), at - frameOffsetInFile_ + 1)}) { - // This is the newline that ends the record before the previous one. + FindLastNewline(Frame(), prevNL - 1 - frameOffsetInFile_)}) { recordOffsetInFrame_ = p - Frame() + 1; - *recordLength = start - 1 - (frameOffsetInFile_ + recordOffsetInFrame_); + *recordLength = prevNL - (frameOffsetInFile_ + recordOffsetInFrame_); break; } - RUNTIME_CHECK(handler, frameOffsetInFile_ > 0); - at = frameOffsetInFile_ - 1; - if (auto bytesBefore{BytesBufferedBeforeFrame()}) { - frameOffsetInFile_ = FrameAt() - bytesBefore; - } else { - static constexpr int chunk{1024}; - frameOffsetInFile_ = std::max(0, at - chunk); - } - std::size_t want{static_cast(start - frameOffsetInFile_)}; - std::size_t got{ReadFrame(frameOffsetInFile_, want, handler)}; - RUNTIME_CHECK(handler, got >= want); } + if (frameOffsetInFile_ == 0) { + recordOffsetInFrame_ = 0; + *recordLength = prevNL; + break; + } + frameOffsetInFile_ -= std::min(frameOffsetInFile_, 1024); + auto need{static_cast(prevNL + 1 - frameOffsetInFile_)}; + auto got{ReadFrame(frameOffsetInFile_, need, handler)}; + RUNTIME_CHECK(handler, got >= need); } - std::size_t want{ - static_cast(recordOffsetInFrame_ + *recordLength + 1)}; - RUNTIME_CHECK(handler, FrameLength() >= want); RUNTIME_CHECK(handler, Frame()[recordOffsetInFrame_ + *recordLength] == '\n'); if (*recordLength > 0 && Frame()[recordOffsetInFrame_ + *recordLength - 1] == '\r') { --*recordLength; } } + +void ExternalFileUnit::DoImpliedEndfile(IoErrorHandler &handler) { + if (impliedEndfile_) { + impliedEndfile_ = false; + if (access == Access::Sequential && mayPosition()) { + DoEndfile(handler); + } + } +} + +void ExternalFileUnit::DoEndfile(IoErrorHandler &handler) { + endfileRecordNumber = currentRecordNumber; + Truncate(frameOffsetInFile_ + recordOffsetInFrame_, handler); + BeginRecord(); + impliedEndfile_ = false; +} } // namespace Fortran::runtime::io diff --git a/flang/unittests/Runtime/CMakeLists.txt b/flang/unittests/Runtime/CMakeLists.txt --- a/flang/unittests/Runtime/CMakeLists.txt +++ b/flang/unittests/Runtime/CMakeLists.txt @@ -41,6 +41,18 @@ LLVMSupport ) +add_executable(external-io + external-io.cpp +) + +target_link_libraries(external-io + RuntimeTesting + FortranRuntime + LLVMSupport +) + +add_test(NAME ExternalIO COMMAND external-io) + add_executable(list-input-test list-input.cpp ) diff --git a/flang/unittests/Runtime/external-hello.cpp b/flang/unittests/Runtime/external-hello.cpp --- a/flang/unittests/Runtime/external-hello.cpp +++ b/flang/unittests/Runtime/external-hello.cpp @@ -5,9 +5,8 @@ using namespace Fortran::runtime::io; -int main(int argc, const char *argv[], const char *envp[]) { - RTNAME(ProgramStart)(argc, argv, envp); - auto *io{IONAME(BeginExternalListOutput)()}; +void output1() { + auto io{IONAME(BeginExternalListOutput)()}; const char str[]{"Hello, world!"}; IONAME(OutputAscii)(io, str, std::strlen(str)); IONAME(OutputInteger64)(io, 678); @@ -21,6 +20,31 @@ IONAME(OutputLogical)(io, false); IONAME(OutputLogical)(io, true); IONAME(EndIoStatement)(io); +} + +void input1() { + auto io{IONAME(BeginExternalListOutput)()}; + const char prompt[]{"Enter an integer value:"}; + IONAME(OutputAscii)(io, prompt, std::strlen(prompt)); + IONAME(EndIoStatement)(io); + + io = IONAME(BeginExternalListInput)(); + std::int64_t n{-666}; + IONAME(InputInteger)(io, n); + IONAME(EndIoStatement)(io); + + io = IONAME(BeginExternalListOutput)(); + const char str[]{"Result:"}; + IONAME(OutputAscii)(io, str, std::strlen(str)); + IONAME(OutputInteger64)(io, n); + IONAME(EndIoStatement)(io); +} + +int main(int argc, const char *argv[], const char *envp[]) { + RTNAME(ProgramStart)(argc, argv, envp); + output1(); + input1(); + RTNAME(PauseStatement)(); RTNAME(ProgramEndStatement)(); return 0; } diff --git a/flang/unittests/Runtime/external-io.cpp b/flang/unittests/Runtime/external-io.cpp new file mode 100644 --- /dev/null +++ b/flang/unittests/Runtime/external-io.cpp @@ -0,0 +1,399 @@ +// Sanity test for all external I/O modes + +#include "testing.h" +#include "../../runtime/io-api.h" +#include "../../runtime/main.h" +#include "../../runtime/stop.h" +#include "llvm/Support/raw_ostream.h" +#include + +using namespace Fortran::runtime::io; + +void TestDirectUnformatted() { + llvm::errs() << "begin TestDirectUnformatted()\n"; + // OPEN(NEWUNIT=unit,ACCESS='DIRECT',ACTION='READWRITE',& + // FORM='UNFORMATTED',RECL=8,STATUS='SCRATCH') + auto io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)}; + IONAME(SetAccess)(io, "DIRECT", 6) || (Fail() << "SetAccess(DIRECT)", 0); + IONAME(SetAction) + (io, "READWRITE", 9) || (Fail() << "SetAction(READWRITE)", 0); + IONAME(SetForm) + (io, "UNFORMATTED", 11) || (Fail() << "SetForm(UNFORMATTED)", 0); + std::int64_t buffer; + static constexpr std::size_t recl{sizeof buffer}; + IONAME(SetRecl)(io, recl) || (Fail() << "SetRecl()", 0); + IONAME(SetStatus)(io, "SCRATCH", 7) || (Fail() << "SetStatus(SCRATCH)", 0); + int unit{-1}; + IONAME(GetNewUnit)(io, unit) || (Fail() << "GetNewUnit()", 0); + llvm::errs() << "unit=" << unit << '\n'; + IONAME(EndIoStatement) + (io) == IostatOk || (Fail() << "EndIoStatement() for OpenNewUnit", 0); + static constexpr int records{10}; + for (int j{1}; j <= records; ++j) { + // WRITE(UNIT=unit,REC=j) j + io = IONAME(BeginUnformattedOutput)(unit, __FILE__, __LINE__); + IONAME(SetRec)(io, j) || (Fail() << "SetRec(" << j << ')', 0); + buffer = j; + IONAME(OutputUnformattedBlock) + (io, reinterpret_cast(&buffer), recl) || + (Fail() << "OutputUnformattedBlock()", 0); + IONAME(EndIoStatement) + (io) == IostatOk || + (Fail() << "EndIoStatement() for OutputUnformattedBlock", 0); + } + for (int j{records}; j >= 1; --j) { + // READ(UNIT=unit,REC=j) n + io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__); + IONAME(SetRec) + (io, j) || (Fail() << "SetRec(" << j << ')', 0); + IONAME(InputUnformattedBlock) + (io, reinterpret_cast(&buffer), recl) || + (Fail() << "InputUnformattedBlock()", 0); + IONAME(EndIoStatement) + (io) == IostatOk || + (Fail() << "EndIoStatement() for InputUnformattedBlock", 0); + if (buffer != j) { + Fail() << "Read back " << buffer << " from direct unformatted record " + << j << ", expected " << j << '\n'; + } + } + // CLOSE(UNIT=unit,STATUS='DELETE') + io = IONAME(BeginClose)(unit, __FILE__, __LINE__); + IONAME(SetStatus)(io, "DELETE", 6) || (Fail() << "SetStatus(DELETE)", 0); + IONAME(EndIoStatement) + (io) == IostatOk || (Fail() << "EndIoStatement() for Close", 0); + llvm::errs() << "end TestDirectUnformatted()\n"; +} + +void TestSequentialFixedUnformatted() { + llvm::errs() << "begin TestSequentialFixedUnformatted()\n"; + // OPEN(NEWUNIT=unit,ACCESS='SEQUENTIAL',ACTION='READWRITE',& + // FORM='UNFORMATTED',RECL=8,STATUS='SCRATCH') + auto io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)}; + IONAME(SetAccess) + (io, "SEQUENTIAL", 10) || (Fail() << "SetAccess(SEQUENTIAL)", 0); + IONAME(SetAction) + (io, "READWRITE", 9) || (Fail() << "SetAction(READWRITE)", 0); + IONAME(SetForm) + (io, "UNFORMATTED", 11) || (Fail() << "SetForm(UNFORMATTED)", 0); + std::int64_t buffer; + static constexpr std::size_t recl{sizeof buffer}; + IONAME(SetRecl)(io, recl) || (Fail() << "SetRecl()", 0); + IONAME(SetStatus)(io, "SCRATCH", 7) || (Fail() << "SetStatus(SCRATCH)", 0); + int unit{-1}; + IONAME(GetNewUnit)(io, unit) || (Fail() << "GetNewUnit()", 0); + llvm::errs() << "unit=" << unit << '\n'; + IONAME(EndIoStatement) + (io) == IostatOk || (Fail() << "EndIoStatement() for OpenNewUnit", 0); + static const int records{10}; + for (int j{1}; j <= records; ++j) { + // DO J=1,RECORDS; WRITE(UNIT=unit) j; END DO + io = IONAME(BeginUnformattedOutput)(unit, __FILE__, __LINE__); + buffer = j; + IONAME(OutputUnformattedBlock) + (io, reinterpret_cast(&buffer), recl) || + (Fail() << "OutputUnformattedBlock()", 0); + IONAME(EndIoStatement) + (io) == IostatOk || + (Fail() << "EndIoStatement() for OutputUnformattedBlock", 0); + } + // REWIND(UNIT=unit) + io = IONAME(BeginRewind)(unit, __FILE__, __LINE__); + IONAME(EndIoStatement) + (io) == IostatOk || (Fail() << "EndIoStatement() for Rewind", 0); + for (int j{1}; j <= records; ++j) { + // DO J=1,RECORDS; READ(UNIT=unit) n; check n; END DO + io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__); + IONAME(InputUnformattedBlock) + (io, reinterpret_cast(&buffer), recl) || + (Fail() << "InputUnformattedBlock()", 0); + IONAME(EndIoStatement) + (io) == IostatOk || + (Fail() << "EndIoStatement() for InputUnformattedBlock", 0); + if (buffer != j) { + Fail() << "Read back " << buffer + << " from sequential fixed unformatted record " << j + << ", expected " << j << '\n'; + } + } + for (int j{records}; j >= 1; --j) { + // BACKSPACE(UNIT=unit) + io = IONAME(BeginBackspace)(unit, __FILE__, __LINE__); + IONAME(EndIoStatement) + (io) == IostatOk || + (Fail() << "EndIoStatement() for Backspace (before read)", 0); + // READ(UNIT=unit) n + io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__); + IONAME(InputUnformattedBlock) + (io, reinterpret_cast(&buffer), recl) || + (Fail() << "InputUnformattedBlock()", 0); + IONAME(EndIoStatement) + (io) == IostatOk || + (Fail() << "EndIoStatement() for InputUnformattedBlock", 0); + if (buffer != j) { + Fail() << "Read back " << buffer + << " from sequential fixed unformatted record " << j + << " after backspacing, expected " << j << '\n'; + } + // BACKSPACE(UNIT=unit) + io = IONAME(BeginBackspace)(unit, __FILE__, __LINE__); + IONAME(EndIoStatement) + (io) == IostatOk || + (Fail() << "EndIoStatement() for Backspace (after read)", 0); + } + // CLOSE(UNIT=unit,STATUS='DELETE') + io = IONAME(BeginClose)(unit, __FILE__, __LINE__); + IONAME(SetStatus)(io, "DELETE", 6) || (Fail() << "SetStatus(DELETE)", 0); + IONAME(EndIoStatement) + (io) == IostatOk || (Fail() << "EndIoStatement() for Close", 0); + llvm::errs() << "end TestSequentialFixedUnformatted()\n"; +} + +void TestSequentialVariableUnformatted() { + llvm::errs() << "begin TestSequentialVariableUnformatted()\n"; + // OPEN(NEWUNIT=unit,ACCESS='SEQUENTIAL',ACTION='READWRITE',& + // FORM='UNFORMATTED',STATUS='SCRATCH') + auto io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)}; + IONAME(SetAccess) + (io, "SEQUENTIAL", 10) || (Fail() << "SetAccess(SEQUENTIAL)", 0); + IONAME(SetAction) + (io, "READWRITE", 9) || (Fail() << "SetAction(READWRITE)", 0); + IONAME(SetForm) + (io, "UNFORMATTED", 11) || (Fail() << "SetForm(UNFORMATTED)", 0); + IONAME(SetStatus)(io, "SCRATCH", 7) || (Fail() << "SetStatus(SCRATCH)", 0); + int unit{-1}; + IONAME(GetNewUnit)(io, unit) || (Fail() << "GetNewUnit()", 0); + llvm::errs() << "unit=" << unit << '\n'; + IONAME(EndIoStatement) + (io) == IostatOk || (Fail() << "EndIoStatement() for OpenNewUnit", 0); + static const int records{10}; + std::int64_t buffer[records]; // INTEGER*8 :: BUFFER(0:9) = [(j,j=0,9)] + for (int j{0}; j < records; ++j) { + buffer[j] = j; + } + for (int j{1}; j <= records; ++j) { + // DO J=1,RECORDS; WRITE(UNIT=unit) BUFFER(0:j); END DO + io = IONAME(BeginUnformattedOutput)(unit, __FILE__, __LINE__); + IONAME(OutputUnformattedBlock) + (io, reinterpret_cast(&buffer), j * sizeof *buffer) || + (Fail() << "OutputUnformattedBlock()", 0); + IONAME(EndIoStatement) + (io) == IostatOk || + (Fail() << "EndIoStatement() for OutputUnformattedBlock", 0); + } + // REWIND(UNIT=unit) + io = IONAME(BeginRewind)(unit, __FILE__, __LINE__); + IONAME(EndIoStatement) + (io) == IostatOk || (Fail() << "EndIoStatement() for Rewind", 0); + for (int j{1}; j <= records; ++j) { + // DO J=1,RECORDS; READ(UNIT=unit) n; check n; END DO + io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__); + IONAME(InputUnformattedBlock) + (io, reinterpret_cast(&buffer), j * sizeof *buffer) || + (Fail() << "InputUnformattedBlock()", 0); + IONAME(EndIoStatement) + (io) == IostatOk || + (Fail() << "EndIoStatement() for InputUnformattedBlock", 0); + for (int k{0}; k < j; ++k) { + if (buffer[k] != k) { + Fail() << "Read back [" << k << "]=" << buffer[k] + << " from direct unformatted record " << j << ", expected " << k + << '\n'; + } + } + } + for (int j{records}; j >= 1; --j) { + // BACKSPACE(unit) + io = IONAME(BeginBackspace)(unit, __FILE__, __LINE__); + IONAME(EndIoStatement) + (io) == IostatOk || + (Fail() << "EndIoStatement() for Backspace (before read)", 0); + // READ(unit=unit) n; check + io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__); + IONAME(InputUnformattedBlock) + (io, reinterpret_cast(&buffer), j * sizeof *buffer) || + (Fail() << "InputUnformattedBlock()", 0); + IONAME(EndIoStatement) + (io) == IostatOk || + (Fail() << "EndIoStatement() for InputUnformattedBlock", 0); + for (int k{0}; k < j; ++k) { + if (buffer[k] != k) { + Fail() << "Read back [" << k << "]=" << buffer[k] + << " from sequential variable unformatted record " << j + << ", expected " << k << '\n'; + } + } + // BACKSPACE(unit) + io = IONAME(BeginBackspace)(unit, __FILE__, __LINE__); + IONAME(EndIoStatement) + (io) == IostatOk || + (Fail() << "EndIoStatement() for Backspace (after read)", 0); + } + // CLOSE(UNIT=unit,STATUS='DELETE') + io = IONAME(BeginClose)(unit, __FILE__, __LINE__); + IONAME(SetStatus)(io, "DELETE", 6) || (Fail() << "SetStatus(DELETE)", 0); + IONAME(EndIoStatement) + (io) == IostatOk || (Fail() << "EndIoStatement() for Close", 0); + llvm::errs() << "end TestSequentialVariableUnformatted()\n"; +} + +void TestDirectFormatted() { + llvm::errs() << "begin TestDirectFormatted()\n"; + // OPEN(NEWUNIT=unit,ACCESS='DIRECT',ACTION='READWRITE',& + // FORM='FORMATTED',RECL=8,STATUS='SCRATCH') + auto io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)}; + IONAME(SetAccess)(io, "DIRECT", 6) || (Fail() << "SetAccess(DIRECT)", 0); + IONAME(SetAction) + (io, "READWRITE", 9) || (Fail() << "SetAction(READWRITE)", 0); + IONAME(SetForm) + (io, "FORMATTED", 9) || (Fail() << "SetForm(FORMATTED)", 0); + static constexpr std::size_t recl{8}; + IONAME(SetRecl)(io, recl) || (Fail() << "SetRecl()", 0); + IONAME(SetStatus)(io, "SCRATCH", 7) || (Fail() << "SetStatus(SCRATCH)", 0); + int unit{-1}; + IONAME(GetNewUnit)(io, unit) || (Fail() << "GetNewUnit()", 0); + llvm::errs() << "unit=" << unit << '\n'; + IONAME(EndIoStatement) + (io) == IostatOk || (Fail() << "EndIoStatement() for OpenNewUnit", 0); + static constexpr int records{10}; + static const char fmt[]{"(I4)"}; + for (int j{1}; j <= records; ++j) { + // WRITE(UNIT=unit,FMT=fmt,REC=j) j + io = IONAME(BeginExternalFormattedOutput)( + fmt, sizeof fmt - 1, unit, __FILE__, __LINE__); + IONAME(SetRec)(io, j) || (Fail() << "SetRec(" << j << ')', 0); + IONAME(OutputInteger64)(io, j) || (Fail() << "OutputInteger64()", 0); + IONAME(EndIoStatement) + (io) == IostatOk || (Fail() << "EndIoStatement() for OutputInteger64", 0); + } + for (int j{records}; j >= 1; --j) { + // READ(UNIT=unit,FMT=fmt,REC=j) n + io = IONAME(BeginExternalFormattedInput)( + fmt, sizeof fmt - 1, unit, __FILE__, __LINE__); + IONAME(SetRec)(io, j) || (Fail() << "SetRec(" << j << ')', 0); + std::int64_t buffer; + IONAME(InputInteger)(io, buffer) || (Fail() << "InputInteger()", 0); + IONAME(EndIoStatement) + (io) == IostatOk || (Fail() << "EndIoStatement() for InputInteger", 0); + if (buffer != j) { + Fail() << "Read back " << buffer << " from direct formatted record " << j + << ", expected " << j << '\n'; + } + } + // CLOSE(UNIT=unit,STATUS='DELETE') + io = IONAME(BeginClose)(unit, __FILE__, __LINE__); + IONAME(SetStatus)(io, "DELETE", 6) || (Fail() << "SetStatus(DELETE)", 0); + IONAME(EndIoStatement) + (io) == IostatOk || (Fail() << "EndIoStatement() for Close", 0); + llvm::errs() << "end TestDirectformatted()\n"; +} + +void TestSequentialVariableFormatted() { + llvm::errs() << "begin TestSequentialVariableFormatted()\n"; + // OPEN(NEWUNIT=unit,ACCESS='SEQUENTIAL',ACTION='READWRITE',& + // FORM='FORMATTED',STATUS='SCRATCH') + auto io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)}; + IONAME(SetAccess) + (io, "SEQUENTIAL", 10) || (Fail() << "SetAccess(SEQUENTIAL)", 0); + IONAME(SetAction) + (io, "READWRITE", 9) || (Fail() << "SetAction(READWRITE)", 0); + IONAME(SetForm) + (io, "FORMATTED", 9) || (Fail() << "SetForm(FORMATTED)", 0); + IONAME(SetStatus)(io, "SCRATCH", 7) || (Fail() << "SetStatus(SCRATCH)", 0); + int unit{-1}; + IONAME(GetNewUnit)(io, unit) || (Fail() << "GetNewUnit()", 0); + llvm::errs() << "unit=" << unit << '\n'; + IONAME(EndIoStatement) + (io) == IostatOk || (Fail() << "EndIoStatement() for OpenNewUnit", 0); + static const int records{10}; + std::int64_t buffer[records]; // INTEGER*8 :: BUFFER(0:9) = [(j,j=0,9)] + for (int j{0}; j < records; ++j) { + buffer[j] = j; + } + char fmt[32]; + for (int j{1}; j <= records; ++j) { + std::snprintf(fmt, sizeof fmt, "(%dI4)", j); + // DO J=1,RECORDS; WRITE(UNIT=unit,FMT=fmt) BUFFER(0:j); END DO + io = IONAME(BeginExternalFormattedOutput)( + fmt, std::strlen(fmt), unit, __FILE__, __LINE__); + for (int k{0}; k < j; ++k) { + IONAME(OutputInteger64) + (io, buffer[k]) || (Fail() << "OutputInteger64()", 0); + } + IONAME(EndIoStatement) + (io) == IostatOk || (Fail() << "EndIoStatement() for OutputInteger64", 0); + } + // REWIND(UNIT=unit) + io = IONAME(BeginRewind)(unit, __FILE__, __LINE__); + IONAME(EndIoStatement) + (io) == IostatOk || (Fail() << "EndIoStatement() for Rewind", 0); + for (int j{1}; j <= records; ++j) { + std::snprintf(fmt, sizeof fmt, "(%dI4)", j); + // DO J=1,RECORDS; READ(UNIT=unit,FMT=fmt) n; check n; END DO + io = IONAME(BeginExternalFormattedInput)( + fmt, std::strlen(fmt), unit, __FILE__, __LINE__); + std::int64_t check[records]; + for (int k{0}; k < j; ++k) { + IONAME(InputInteger)(io, check[k]) || (Fail() << "InputInteger()", 0); + } + IONAME(EndIoStatement) + (io) == IostatOk || (Fail() << "EndIoStatement() for InputInteger", 0); + for (int k{0}; k < j; ++k) { + if (buffer[k] != check[k]) { + Fail() << "Read back [" << k << "]=" << check[k] + << " from sequential variable formatted record " << j + << ", expected " << buffer[k] << '\n'; + } + } + } + for (int j{records}; j >= 1; --j) { + // BACKSPACE(unit) + io = IONAME(BeginBackspace)(unit, __FILE__, __LINE__); + IONAME(EndIoStatement) + (io) == IostatOk || + (Fail() << "EndIoStatement() for Backspace (before read)", 0); + std::snprintf(fmt, sizeof fmt, "(%dI4)", j); + // READ(UNIT=unit,FMT=fmt) n; check + io = IONAME(BeginExternalFormattedInput)( + fmt, std::strlen(fmt), unit, __FILE__, __LINE__); + std::int64_t check[records]; + for (int k{0}; k < j; ++k) { + IONAME(InputInteger)(io, check[k]) || (Fail() << "InputInteger()", 0); + } + IONAME(EndIoStatement) + (io) == IostatOk || (Fail() << "EndIoStatement() for InputInteger", 0); + for (int k{0}; k < j; ++k) { + if (buffer[k] != check[k]) { + Fail() << "Read back [" << k << "]=" << buffer[k] + << " from sequential variable formatted record " << j + << ", expected " << buffer[k] << '\n'; + } + } + // BACKSPACE(unit) + io = IONAME(BeginBackspace)(unit, __FILE__, __LINE__); + IONAME(EndIoStatement) + (io) == IostatOk || + (Fail() << "EndIoStatement() for Backspace (after read)", 0); + } + // CLOSE(UNIT=unit,STATUS='DELETE') + io = IONAME(BeginClose)(unit, __FILE__, __LINE__); + IONAME(SetStatus)(io, "DELETE", 6) || (Fail() << "SetStatus(DELETE)", 0); + IONAME(EndIoStatement) + (io) == IostatOk || (Fail() << "EndIoStatement() for Close", 0); + llvm::errs() << "end TestSequentialVariableFormatted()\n"; +} + +void TestStreamUnformatted() { + // TODO +} + +int main() { + StartTests(); + TestDirectUnformatted(); + TestSequentialFixedUnformatted(); + TestSequentialVariableUnformatted(); + TestDirectFormatted(); + TestSequentialVariableFormatted(); + TestStreamUnformatted(); + return EndTests(); +} diff --git a/flang/unittests/Runtime/testing.h b/flang/unittests/Runtime/testing.h --- a/flang/unittests/Runtime/testing.h +++ b/flang/unittests/Runtime/testing.h @@ -12,6 +12,7 @@ llvm::raw_ostream &Fail(); int EndTests(); +// Defines a CHARACTER object with padding when needed void SetCharacter(char *, std::size_t, const char *); #endif // FORTRAN_TEST_RUNTIME_TESTING_H_ diff --git a/flang/unittests/Runtime/testing.cpp b/flang/unittests/Runtime/testing.cpp --- a/flang/unittests/Runtime/testing.cpp +++ b/flang/unittests/Runtime/testing.cpp @@ -9,10 +9,13 @@ static int failures{0}; // Override the Fortran runtime's Crash() for testing purposes -[[noreturn]] static void CatchCrash(const char *message, va_list &ap) { +[[noreturn]] static void CatchCrash( + const char *sourceFile, int sourceLine, const char *message, va_list &ap) { char buffer[1000]; std::vsnprintf(buffer, sizeof buffer, message, ap); va_end(ap); + llvm::errs() << (sourceFile ? sourceFile : "unknown source file") << '(' + << sourceLine << "): CRASH: " << buffer << '\n'; throw std::string{buffer}; }