diff --git a/flang/runtime/io-api.cpp b/flang/runtime/io-api.cpp index 18c3f8241f08..304c40e871f4 100644 --- a/flang/runtime/io-api.cpp +++ b/flang/runtime/io-api.cpp @@ -1,1072 +1,1096 @@ //===-- runtime/io-api.cpp --------------------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // Implements the I/O statement API #include "io-api.h" #include "descriptor-io.h" #include "descriptor.h" #include "edit-input.h" #include "edit-output.h" #include "environment.h" #include "format.h" #include "io-stmt.h" #include "memory.h" #include "terminator.h" #include "tools.h" #include "unit.h" #include #include 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*/, const char *sourceFile, int sourceLine) { Terminator oom{sourceFile, sourceLine}; return &New>{oom}( descriptor, sourceFile, sourceLine) .release() ->ioStatementState(); } Cookie IONAME(BeginInternalArrayListOutput)(const Descriptor &descriptor, void **scratchArea, std::size_t scratchBytes, const char *sourceFile, int sourceLine) { return BeginInternalArrayListIO( descriptor, scratchArea, scratchBytes, sourceFile, sourceLine); } Cookie IONAME(BeginInternalArrayListInput)(const Descriptor &descriptor, void **scratchArea, std::size_t scratchBytes, const char *sourceFile, int sourceLine) { return BeginInternalArrayListIO( descriptor, scratchArea, scratchBytes, sourceFile, sourceLine); } template Cookie BeginInternalArrayFormattedIO(const Descriptor &descriptor, 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) .release() ->ioStatementState(); } Cookie IONAME(BeginInternalArrayFormattedOutput)(const Descriptor &descriptor, const char *format, std::size_t formatLength, void **scratchArea, std::size_t scratchBytes, const char *sourceFile, int sourceLine) { return BeginInternalArrayFormattedIO(descriptor, format, formatLength, scratchArea, scratchBytes, sourceFile, sourceLine); } Cookie IONAME(BeginInternalArrayFormattedInput)(const Descriptor &descriptor, const char *format, std::size_t formatLength, void **scratchArea, std::size_t scratchBytes, const char *sourceFile, int sourceLine) { return BeginInternalArrayFormattedIO(descriptor, format, formatLength, scratchArea, scratchBytes, sourceFile, sourceLine); } template Cookie BeginInternalListIO( std::conditional_t *internal, std::size_t internalLength, void ** /*scratchArea*/, std::size_t /*scratchBytes*/, const char *sourceFile, int sourceLine) { Terminator oom{sourceFile, sourceLine}; return &New>{oom}( internal, internalLength, sourceFile, sourceLine) .release() ->ioStatementState(); } Cookie IONAME(BeginInternalListOutput)(char *internal, std::size_t internalLength, void **scratchArea, std::size_t scratchBytes, const char *sourceFile, int sourceLine) { return BeginInternalListIO(internal, internalLength, scratchArea, scratchBytes, sourceFile, sourceLine); } Cookie IONAME(BeginInternalListInput)(const char *internal, std::size_t internalLength, void **scratchArea, std::size_t scratchBytes, const char *sourceFile, int sourceLine) { return BeginInternalListIO(internal, internalLength, scratchArea, scratchBytes, sourceFile, sourceLine); } template Cookie BeginInternalFormattedIO( std::conditional_t *internal, std::size_t internalLength, 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}( internal, internalLength, format, formatLength, sourceFile, sourceLine) .release() ->ioStatementState(); } Cookie IONAME(BeginInternalFormattedOutput)(char *internal, std::size_t internalLength, const char *format, std::size_t formatLength, void **scratchArea, std::size_t scratchBytes, const char *sourceFile, int sourceLine) { return BeginInternalFormattedIO(internal, internalLength, format, formatLength, scratchArea, scratchBytes, sourceFile, sourceLine); } Cookie IONAME(BeginInternalFormattedInput)(const char *internal, std::size_t internalLength, const char *format, std::size_t formatLength, void **scratchArea, std::size_t scratchBytes, const char *sourceFile, int sourceLine) { return BeginInternalFormattedIO(internal, internalLength, format, formatLength, scratchArea, scratchBytes, sourceFile, sourceLine); } template Cookie BeginExternalListIO( ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { Terminator terminator{sourceFile, sourceLine}; if (unitNumber == DefaultUnit) { unitNumber = DIR == Direction::Input ? 5 : 6; } ExternalFileUnit &unit{ExternalFileUnit::LookUpOrCreateAnonymous( unitNumber, DIR, false /*formatted*/, terminator)}; if (unit.access == Access::Direct) { terminator.Crash("List-directed I/O attempted on direct access file"); return nullptr; } if (unit.isUnformatted) { 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)}; return &io; } Cookie IONAME(BeginExternalListOutput)( ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { return BeginExternalListIO( unitNumber, sourceFile, sourceLine); } Cookie IONAME(BeginExternalListInput)( ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { return BeginExternalListIO( unitNumber, sourceFile, sourceLine); } template Cookie BeginExternalFormattedIO(const char *format, std::size_t formatLength, ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { Terminator terminator{sourceFile, sourceLine}; if (unitNumber == DefaultUnit) { unitNumber = DIR == Direction::Input ? 5 : 6; } ExternalFileUnit &unit{ExternalFileUnit::LookUpOrCreateAnonymous( unitNumber, DIR, false /*formatted*/, terminator)}; if (unit.isUnformatted) { 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)}; return &io; } Cookie IONAME(BeginExternalFormattedOutput)(const char *format, std::size_t formatLength, ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { return BeginExternalFormattedIO( format, formatLength, unitNumber, sourceFile, sourceLine); } Cookie IONAME(BeginExternalFormattedInput)(const char *format, std::size_t formatLength, ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { return BeginExternalFormattedIO( format, formatLength, unitNumber, sourceFile, sourceLine); } template Cookie BeginUnformattedIO( ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { Terminator terminator{sourceFile, sourceLine}; ExternalFileUnit &unit{ExternalFileUnit::LookUpOrCreateAnonymous( unitNumber, DIR, true /*unformatted*/, terminator)}; if (!unit.isUnformatted) { terminator.Crash("Unformatted output attempted on formatted file"); } IoStatementState &io{unit.BeginIoStatement>( unit, sourceFile, sourceLine)}; IoErrorHandler handler{terminator}; unit.SetDirection(DIR, handler); if constexpr (DIR == Direction::Output) { 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 } } return &io; } Cookie IONAME(BeginUnformattedOutput)( ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { return BeginUnformattedIO( unitNumber, sourceFile, sourceLine); } Cookie IONAME(BeginUnformattedInput)( ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { return BeginUnformattedIO( unitNumber, sourceFile, sourceLine); } Cookie IONAME(BeginOpenUnit)( // OPEN(without NEWUNIT=) ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { bool wasExtant{false}; Terminator terminator{sourceFile, sourceLine}; ExternalFileUnit &unit{ ExternalFileUnit::LookUpOrCreate(unitNumber, terminator, wasExtant)}; return &unit.BeginIoStatement( unit, wasExtant, sourceFile, sourceLine); } Cookie IONAME(BeginOpenNewUnit)( // OPEN(NEWUNIT=j) const char *sourceFile, int sourceLine) { Terminator terminator{sourceFile, sourceLine}; bool ignored{false}; ExternalFileUnit &unit{ExternalFileUnit::LookUpOrCreate( ExternalFileUnit::NewUnit(terminator), terminator, ignored)}; return &unit.BeginIoStatement( unit, false /*was an existing file*/, sourceFile, sourceLine); } Cookie IONAME(BeginClose)( ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { if (ExternalFileUnit * unit{ExternalFileUnit::LookUpForClose(unitNumber)}) { return &unit->BeginIoStatement( *unit, sourceFile, sourceLine); } else { // CLOSE(UNIT=bad unit) is just a no-op Terminator oom{sourceFile, sourceLine}; 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::LookUpOrCreateAnonymous( unitNumber, Direction::Output, true /*formatted*/, 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::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, bool hasEnd, bool hasEor, bool hasIoMsg) { IoErrorHandler &handler{cookie->GetIoErrorHandler()}; if (hasIoStat) { handler.HasIoStat(); } if (hasErr) { handler.HasErrLabel(); } if (hasEnd) { handler.HasEndLabel(); } if (hasEor) { handler.HasEorLabel(); } if (hasIoMsg) { handler.HasIoMsg(); } } static bool YesOrNo(const char *keyword, std::size_t length, const char *what, IoErrorHandler &handler) { static const char *keywords[]{"YES", "NO", nullptr}; switch (IdentifyValue(keyword, length, keywords)) { case 0: return true; case 1: return false; default: handler.SignalError(IostatErrorInKeyword, "Invalid %s='%.*s'", what, static_cast(length), keyword); return false; } } bool IONAME(SetAdvance)( Cookie cookie, const char *keyword, std::size_t length) { IoStatementState &io{*cookie}; ConnectionState &connection{io.GetConnectionState()}; connection.nonAdvancing = !YesOrNo(keyword, length, "ADVANCE", io.GetIoErrorHandler()); if (connection.nonAdvancing && connection.access == Access::Direct) { io.GetIoErrorHandler().SignalError( "Non-advancing I/O attempted on direct access file"); } return true; } bool IONAME(SetBlank)(Cookie cookie, const char *keyword, std::size_t length) { IoStatementState &io{*cookie}; ConnectionState &connection{io.GetConnectionState()}; static const char *keywords[]{"NULL", "ZERO", nullptr}; switch (IdentifyValue(keyword, length, keywords)) { case 0: connection.modes.editingFlags &= ~blankZero; return true; case 1: connection.modes.editingFlags |= blankZero; return true; default: io.GetIoErrorHandler().SignalError(IostatErrorInKeyword, "Invalid BLANK='%.*s'", static_cast(length), keyword); return false; } } bool IONAME(SetDecimal)( Cookie cookie, const char *keyword, std::size_t length) { IoStatementState &io{*cookie}; ConnectionState &connection{io.GetConnectionState()}; static const char *keywords[]{"COMMA", "POINT", nullptr}; switch (IdentifyValue(keyword, length, keywords)) { case 0: connection.modes.editingFlags |= decimalComma; return true; case 1: connection.modes.editingFlags &= ~decimalComma; return true; default: io.GetIoErrorHandler().SignalError(IostatErrorInKeyword, "Invalid DECIMAL='%.*s'", static_cast(length), keyword); return false; } } bool IONAME(SetDelim)(Cookie cookie, const char *keyword, std::size_t length) { IoStatementState &io{*cookie}; ConnectionState &connection{io.GetConnectionState()}; static const char *keywords[]{"APOSTROPHE", "QUOTE", "NONE", nullptr}; switch (IdentifyValue(keyword, length, keywords)) { case 0: connection.modes.delim = '\''; return true; case 1: connection.modes.delim = '"'; return true; case 2: connection.modes.delim = '\0'; return true; default: io.GetIoErrorHandler().SignalError(IostatErrorInKeyword, "Invalid DELIM='%.*s'", static_cast(length), keyword); return false; } } bool IONAME(SetPad)(Cookie cookie, const char *keyword, std::size_t length) { IoStatementState &io{*cookie}; ConnectionState &connection{io.GetConnectionState()}; connection.modes.pad = YesOrNo(keyword, length, "PAD", io.GetIoErrorHandler()); return true; } bool IONAME(SetPos)(Cookie cookie, std::int64_t pos) { IoStatementState &io{*cookie}; ConnectionState &connection{io.GetConnectionState()}; if (connection.access != Access::Stream) { io.GetIoErrorHandler().SignalError( "REC= may not appear unless ACCESS='STREAM'"); return false; } if (pos < 1) { io.GetIoErrorHandler().SignalError( "POS=%zd is invalid", static_cast(pos)); return false; } if (auto *unit{io.GetExternalFileUnit()}) { unit->SetPosition(pos); return true; } io.GetIoErrorHandler().Crash("SetPos() on internal unit"); return false; } bool IONAME(SetRec)(Cookie cookie, std::int64_t rec) { IoStatementState &io{*cookie}; ConnectionState &connection{io.GetConnectionState()}; if (connection.access != Access::Direct) { io.GetIoErrorHandler().SignalError( "REC= may not appear unless ACCESS='DIRECT'"); return false; } if (!connection.isFixedRecordLength || !connection.recordLength) { io.GetIoErrorHandler().SignalError("RECL= was not specified"); return false; } if (rec < 1) { io.GetIoErrorHandler().SignalError( "REC=%zd is invalid", static_cast(rec)); return false; } connection.currentRecordNumber = rec; if (auto *unit{io.GetExternalFileUnit()}) { unit->SetPosition(rec * *connection.recordLength); } return true; } bool IONAME(SetRound)(Cookie cookie, const char *keyword, std::size_t length) { IoStatementState &io{*cookie}; ConnectionState &connection{io.GetConnectionState()}; static const char *keywords[]{"UP", "DOWN", "ZERO", "NEAREST", "COMPATIBLE", "PROCESSOR_DEFINED", nullptr}; switch (IdentifyValue(keyword, length, keywords)) { case 0: connection.modes.round = decimal::RoundUp; return true; case 1: connection.modes.round = decimal::RoundDown; return true; case 2: connection.modes.round = decimal::RoundToZero; return true; case 3: connection.modes.round = decimal::RoundNearest; return true; case 4: connection.modes.round = decimal::RoundCompatible; return true; case 5: connection.modes.round = executionEnvironment.defaultOutputRoundingMode; return true; default: io.GetIoErrorHandler().SignalError(IostatErrorInKeyword, "Invalid ROUND='%.*s'", static_cast(length), keyword); return false; } } bool IONAME(SetSign)(Cookie cookie, const char *keyword, std::size_t length) { IoStatementState &io{*cookie}; ConnectionState &connection{io.GetConnectionState()}; static const char *keywords[]{"PLUS", "YES", "PROCESSOR_DEFINED", nullptr}; switch (IdentifyValue(keyword, length, keywords)) { case 0: connection.modes.editingFlags |= signPlus; return true; case 1: case 2: // processor default is SS connection.modes.editingFlags &= ~signPlus; return true; default: io.GetIoErrorHandler().SignalError(IostatErrorInKeyword, "Invalid SIGN='%.*s'", static_cast(length), keyword); return false; } } bool IONAME(SetAccess)(Cookie cookie, const char *keyword, std::size_t length) { IoStatementState &io{*cookie}; auto *open{io.get_if()}; if (!open) { io.GetIoErrorHandler().Crash( "SetAccess() called when not in an OPEN statement"); } static const char *keywords[]{"SEQUENTIAL", "DIRECT", "STREAM", nullptr}; switch (IdentifyValue(keyword, length, keywords)) { case 0: open->set_access(Access::Sequential); break; case 1: open->set_access(Access::Direct); break; case 2: open->set_access(Access::Stream); break; default: open->SignalError(IostatErrorInKeyword, "Invalid ACCESS='%.*s'", static_cast(length), keyword); } return true; } bool IONAME(SetAction)(Cookie cookie, const char *keyword, std::size_t length) { IoStatementState &io{*cookie}; auto *open{io.get_if()}; if (!open) { io.GetIoErrorHandler().Crash( "SetAction() called when not in an OPEN statement"); } std::optional action; static const char *keywords[]{"READ", "WRITE", "READWRITE", nullptr}; switch (IdentifyValue(keyword, length, keywords)) { case 0: action = Action::Read; break; case 1: action = Action::Write; break; case 2: action = Action::ReadWrite; break; default: open->SignalError(IostatErrorInKeyword, "Invalid ACTION='%.*s'", static_cast(length), keyword); return false; } RUNTIME_CHECK(io.GetIoErrorHandler(), action.has_value()); if (open->wasExtant()) { if ((*action != Action::Write) != open->unit().mayRead() || (*action != Action::Read) != open->unit().mayWrite()) { open->SignalError("ACTION= may not be changed on an open unit"); } } open->set_action(*action); return true; } bool IONAME(SetAsynchronous)( Cookie cookie, const char *keyword, std::size_t length) { IoStatementState &io{*cookie}; auto *open{io.get_if()}; if (!open) { io.GetIoErrorHandler().Crash( "SetAsynchronous() called when not in an OPEN statement"); } static const char *keywords[]{"YES", "NO", nullptr}; switch (IdentifyValue(keyword, length, keywords)) { case 0: open->unit().set_mayAsynchronous(true); return true; case 1: open->unit().set_mayAsynchronous(false); return true; default: open->SignalError(IostatErrorInKeyword, "Invalid ASYNCHRONOUS='%.*s'", static_cast(length), keyword); return false; } } bool IONAME(SetCarriagecontrol)( Cookie cookie, const char *keyword, std::size_t length) { IoStatementState &io{*cookie}; auto *open{io.get_if()}; if (!open) { io.GetIoErrorHandler().Crash( "SetCarriageControl() called when not in an OPEN statement"); } static const char *keywords[]{"LIST", "FORTRAN", "NONE", nullptr}; switch (IdentifyValue(keyword, length, keywords)) { case 0: return true; case 1: case 2: open->SignalError(IostatErrorInKeyword, "Unimplemented CARRIAGECONTROL='%.*s'", static_cast(length), keyword); return false; default: open->SignalError(IostatErrorInKeyword, "Invalid CARRIAGECONTROL='%.*s'", static_cast(length), keyword); return false; } } bool IONAME(SetConvert)( Cookie cookie, const char *keyword, std::size_t length) { IoStatementState &io{*cookie}; auto *open{io.get_if()}; if (!open) { io.GetIoErrorHandler().Crash( "SetConvert() called when not in an OPEN statement"); } if (auto convert{GetConvertFromString(keyword, length)}) { open->set_convert(*convert); return true; } else { open->SignalError(IostatErrorInKeyword, "Invalid CONVERT='%.*s'", static_cast(length), keyword); return false; } } bool IONAME(SetEncoding)( Cookie cookie, const char *keyword, std::size_t length) { IoStatementState &io{*cookie}; auto *open{io.get_if()}; if (!open) { io.GetIoErrorHandler().Crash( "SetEncoding() called when not in an OPEN statement"); } bool isUTF8{false}; static const char *keywords[]{"UTF-8", "DEFAULT", nullptr}; switch (IdentifyValue(keyword, length, keywords)) { case 0: isUTF8 = true; break; case 1: isUTF8 = false; break; default: open->SignalError(IostatErrorInKeyword, "Invalid ENCODING='%.*s'", static_cast(length), keyword); } if (isUTF8 != open->unit().isUTF8) { if (open->wasExtant()) { open->SignalError("ENCODING= may not be changed on an open unit"); } open->unit().isUTF8 = isUTF8; } return true; } bool IONAME(SetForm)(Cookie cookie, const char *keyword, std::size_t length) { IoStatementState &io{*cookie}; auto *open{io.get_if()}; if (!open) { io.GetIoErrorHandler().Crash( "SetForm() called when not in an OPEN statement"); } static const char *keywords[]{"FORMATTED", "UNFORMATTED", nullptr}; switch (IdentifyValue(keyword, length, keywords)) { case 0: open->set_isUnformatted(false); break; case 1: open->set_isUnformatted(true); break; default: open->SignalError(IostatErrorInKeyword, "Invalid FORM='%.*s'", static_cast(length), keyword); } return true; } bool IONAME(SetPosition)( Cookie cookie, const char *keyword, std::size_t length) { IoStatementState &io{*cookie}; auto *open{io.get_if()}; if (!open) { io.GetIoErrorHandler().Crash( "SetPosition() called when not in an OPEN statement"); } static const char *positions[]{"ASIS", "REWIND", "APPEND", nullptr}; switch (IdentifyValue(keyword, length, positions)) { case 0: open->set_position(Position::AsIs); return true; case 1: open->set_position(Position::Rewind); return true; case 2: open->set_position(Position::Append); return true; default: io.GetIoErrorHandler().SignalError(IostatErrorInKeyword, "Invalid POSITION='%.*s'", static_cast(length), keyword); } return true; } bool IONAME(SetRecl)(Cookie cookie, std::size_t n) { IoStatementState &io{*cookie}; auto *open{io.get_if()}; if (!open) { io.GetIoErrorHandler().Crash( "SetRecl() called when not in an OPEN statement"); } if (n <= 0) { io.GetIoErrorHandler().SignalError("RECL= must be greater than zero"); } 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; } bool IONAME(SetStatus)(Cookie cookie, const char *keyword, std::size_t length) { IoStatementState &io{*cookie}; if (auto *open{io.get_if()}) { static const char *statuses[]{ "OLD", "NEW", "SCRATCH", "REPLACE", "UNKNOWN", nullptr}; switch (IdentifyValue(keyword, length, statuses)) { case 0: open->set_status(OpenStatus::Old); return true; case 1: open->set_status(OpenStatus::New); return true; case 2: open->set_status(OpenStatus::Scratch); return true; case 3: open->set_status(OpenStatus::Replace); return true; case 4: open->set_status(OpenStatus::Unknown); return true; default: io.GetIoErrorHandler().SignalError(IostatErrorInKeyword, "Invalid STATUS='%.*s'", static_cast(length), keyword); } return false; } if (auto *close{io.get_if()}) { static const char *statuses[]{"KEEP", "DELETE", nullptr}; switch (IdentifyValue(keyword, length, statuses)) { case 0: close->set_status(CloseStatus::Keep); return true; case 1: close->set_status(CloseStatus::Delete); return true; default: io.GetIoErrorHandler().SignalError(IostatErrorInKeyword, "Invalid STATUS='%.*s'", static_cast(length), keyword); } return false; } if (io.get_if()) { return true; // don't bother validating STATUS= in a no-op CLOSE } io.GetIoErrorHandler().Crash( "SetStatus() called when not in an OPEN or CLOSE statement"); } 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); return true; } io.GetIoErrorHandler().Crash( "SetFile() called when not in an OPEN statement"); return false; } template static bool SetInteger(INT &x, int kind, std::int64_t value) { switch (kind) { case 1: reinterpret_cast(x) = value; return true; case 2: reinterpret_cast(x) = value; return true; case 4: reinterpret_cast(x) = value; return true; case 8: reinterpret_cast(x) = value; return true; default: return false; } } bool IONAME(GetNewUnit)(Cookie cookie, int &unit, int kind) { IoStatementState &io{*cookie}; auto *open{io.get_if()}; if (!open) { io.GetIoErrorHandler().Crash( "GetNewUnit() called when not in an OPEN statement"); } if (!SetInteger(unit, kind, open->unit().unitNumber())) { open->SignalError("GetNewUnit(): Bad INTEGER kind(%d) for result"); } return true; } // Data transfers bool IONAME(OutputDescriptor)(Cookie cookie, const Descriptor &descriptor) { return descr::DescriptorIO(*cookie, descriptor); } bool IONAME(InputDescriptor)(Cookie cookie, const Descriptor &descriptor) { return descr::DescriptorIO(*cookie, descriptor); } bool IONAME(OutputUnformattedBlock)(Cookie cookie, const char *x, std::size_t length, std::size_t elementBytes) { IoStatementState &io{*cookie}; if (auto *unf{io.get_if>()}) { return unf->Emit(x, length, elementBytes); } 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, std::size_t elementBytes) { IoStatementState &io{*cookie}; io.BeginReadingRecord(); if (auto *unf{io.get_if>()}) { return unf->Receive(x, length, elementBytes); } io.GetIoErrorHandler().Crash("InputUnformattedBlock() called for an I/O " "statement that is not unformatted output"); return false; } bool IONAME(OutputInteger64)(Cookie cookie, std::int64_t n) { + cookie->CheckFormattedStmtType("OutputInteger64"); StaticDescriptor staticDescriptor; Descriptor &descriptor{staticDescriptor.descriptor()}; descriptor.Establish( - TypeCategory::Integer, 8, reinterpret_cast(&n), 0); + TypeCategory::Integer, sizeof n, reinterpret_cast(&n), 0); return descr::DescriptorIO(*cookie, descriptor); } bool IONAME(InputInteger)(Cookie cookie, std::int64_t &n, int kind) { + cookie->CheckFormattedStmtType("InputInteger"); StaticDescriptor staticDescriptor; Descriptor &descriptor{staticDescriptor.descriptor()}; descriptor.Establish( TypeCategory::Integer, kind, reinterpret_cast(&n), 0); return descr::DescriptorIO(*cookie, descriptor); } bool IONAME(OutputReal32)(Cookie cookie, float x) { + cookie->CheckFormattedStmtType("OutputReal32"); StaticDescriptor staticDescriptor; Descriptor &descriptor{staticDescriptor.descriptor()}; descriptor.Establish(TypeCategory::Real, 4, reinterpret_cast(&x), 0); return descr::DescriptorIO(*cookie, descriptor); } bool IONAME(OutputReal64)(Cookie cookie, double x) { + cookie->CheckFormattedStmtType("OutputReal64"); StaticDescriptor staticDescriptor; Descriptor &descriptor{staticDescriptor.descriptor()}; descriptor.Establish(TypeCategory::Real, 8, reinterpret_cast(&x), 0); return descr::DescriptorIO(*cookie, descriptor); } bool IONAME(InputReal32)(Cookie cookie, float &x) { + cookie->CheckFormattedStmtType("InputReal32"); StaticDescriptor staticDescriptor; Descriptor &descriptor{staticDescriptor.descriptor()}; descriptor.Establish(TypeCategory::Real, 4, reinterpret_cast(&x), 0); return descr::DescriptorIO(*cookie, descriptor); } bool IONAME(InputReal64)(Cookie cookie, double &x) { + cookie->CheckFormattedStmtType("InputReal64"); StaticDescriptor staticDescriptor; Descriptor &descriptor{staticDescriptor.descriptor()}; descriptor.Establish(TypeCategory::Real, 8, reinterpret_cast(&x), 0); return descr::DescriptorIO(*cookie, descriptor); } bool IONAME(OutputComplex32)(Cookie cookie, float r, float i) { + cookie->CheckFormattedStmtType("OutputComplex32"); float z[2]{r, i}; StaticDescriptor staticDescriptor; Descriptor &descriptor{staticDescriptor.descriptor()}; descriptor.Establish( TypeCategory::Complex, 4, reinterpret_cast(&z), 0); return descr::DescriptorIO(*cookie, descriptor); } bool IONAME(OutputComplex64)(Cookie cookie, double r, double i) { + cookie->CheckFormattedStmtType("OutputComplex64"); double z[2]{r, i}; StaticDescriptor staticDescriptor; Descriptor &descriptor{staticDescriptor.descriptor()}; descriptor.Establish( TypeCategory::Complex, 8, reinterpret_cast(&z), 0); return descr::DescriptorIO(*cookie, descriptor); } bool IONAME(InputComplex32)(Cookie cookie, float z[2]) { + cookie->CheckFormattedStmtType("InputComplex32"); StaticDescriptor staticDescriptor; Descriptor &descriptor{staticDescriptor.descriptor()}; descriptor.Establish( TypeCategory::Complex, 4, reinterpret_cast(z), 0); return descr::DescriptorIO(*cookie, descriptor); } bool IONAME(InputComplex64)(Cookie cookie, double z[2]) { + cookie->CheckFormattedStmtType("InputComplex64"); StaticDescriptor staticDescriptor; Descriptor &descriptor{staticDescriptor.descriptor()}; descriptor.Establish( TypeCategory::Complex, 8, reinterpret_cast(z), 0); return descr::DescriptorIO(*cookie, descriptor); } -bool IONAME(OutputAscii)(Cookie cookie, const char *x, std::size_t length) { +bool IONAME(OutputCharacter)( + Cookie cookie, const char *x, std::size_t length, int kind) { + cookie->CheckFormattedStmtType("OutputCharacter"); StaticDescriptor staticDescriptor; Descriptor &descriptor{staticDescriptor.descriptor()}; descriptor.Establish( - 1, length, reinterpret_cast(const_cast(x)), 0); + kind, length, reinterpret_cast(const_cast(x)), 0); return descr::DescriptorIO(*cookie, descriptor); } -bool IONAME(InputAscii)(Cookie cookie, char *x, std::size_t length) { +bool IONAME(OutputAscii)(Cookie cookie, const char *x, std::size_t length) { + return IONAME(OutputCharacter(cookie, x, length, 1)); +} + +bool IONAME(InputCharacter)( + Cookie cookie, char *x, std::size_t length, int kind) { + cookie->CheckFormattedStmtType("InputCharacter"); StaticDescriptor staticDescriptor; Descriptor &descriptor{staticDescriptor.descriptor()}; - descriptor.Establish(1, length, reinterpret_cast(x), 0); + descriptor.Establish(kind, length, reinterpret_cast(x), 0); return descr::DescriptorIO(*cookie, descriptor); } +bool IONAME(InputAscii)(Cookie cookie, char *x, std::size_t length) { + return IONAME(InputCharacter(cookie, x, length, 1)); +} + bool IONAME(OutputLogical)(Cookie cookie, bool truth) { + cookie->CheckFormattedStmtType("OutputLogical"); StaticDescriptor staticDescriptor; Descriptor &descriptor{staticDescriptor.descriptor()}; descriptor.Establish( - TypeCategory::Logical, 1, reinterpret_cast(&truth), 0); + TypeCategory::Logical, sizeof truth, reinterpret_cast(&truth), 0); return descr::DescriptorIO(*cookie, descriptor); } bool IONAME(InputLogical)(Cookie cookie, bool &truth) { + cookie->CheckFormattedStmtType("InputLogical"); StaticDescriptor staticDescriptor; Descriptor &descriptor{staticDescriptor.descriptor()}; descriptor.Establish( - TypeCategory::Logical, 1, reinterpret_cast(&truth), 0); + TypeCategory::Logical, sizeof truth, reinterpret_cast(&truth), 0); return descr::DescriptorIO(*cookie, descriptor); } void IONAME(GetIoMsg)(Cookie cookie, char *msg, std::size_t length) { IoErrorHandler &handler{cookie->GetIoErrorHandler()}; if (handler.GetIoStat()) { // leave "msg" alone when no error handler.GetIoMsg(msg, length); } } 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()); } } // namespace Fortran::runtime::io diff --git a/flang/runtime/io-api.h b/flang/runtime/io-api.h index 369013fee8bc..80a6de95069c 100644 --- a/flang/runtime/io-api.h +++ b/flang/runtime/io-api.h @@ -1,322 +1,326 @@ //===-- runtime/io-api.h ----------------------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // Defines API between compiled code and I/O runtime library. #ifndef FORTRAN_RUNTIME_IO_API_H_ #define FORTRAN_RUNTIME_IO_API_H_ #include "entry-names.h" #include "iostat.h" #include #include namespace Fortran::runtime { class Descriptor; class NamelistGroup; } // namespace Fortran::runtime namespace Fortran::runtime::io { class IoStatementState; using Cookie = IoStatementState *; using ExternalUnit = int; 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) // These functions initiate data transfer statements (READ, WRITE, PRINT). // Example: PRINT *, 666 is implemented as the series of calls: // Cookie cookie{BeginExternalListOutput(DefaultUnit,__FILE__,__LINE__)}; // OutputInteger64(cookie, 666); // EndIoStatement(cookie); // Internal I/O initiation // Internal I/O can loan the runtime library an optional block of memory // in which the library can maintain state across the calls that implement // the internal transfer; use of these blocks can reduce the need for dynamic // memory allocation &/or thread-local storage. The block must be sufficiently // aligned to hold a pointer. constexpr std::size_t RecommendedInternalIoScratchAreaBytes( int maxFormatParenthesesNestingDepth) { return 32 + 8 * maxFormatParenthesesNestingDepth; } // Internal I/O to/from character arrays &/or non-default-kind character // requires a descriptor, which is copied. Cookie IONAME(BeginInternalArrayListOutput)(const Descriptor &, void **scratchArea = nullptr, std::size_t scratchBytes = 0, const char *sourceFile = nullptr, int sourceLine = 0); Cookie IONAME(BeginInternalArrayListInput)(const Descriptor &, void **scratchArea = nullptr, std::size_t scratchBytes = 0, const char *sourceFile = nullptr, int sourceLine = 0); Cookie IONAME(BeginInternalArrayFormattedOutput)(const Descriptor &, const char *format, std::size_t formatLength, void **scratchArea = nullptr, std::size_t scratchBytes = 0, const char *sourceFile = nullptr, int sourceLine = 0); Cookie IONAME(BeginInternalArrayFormattedInput)(const Descriptor &, const char *format, std::size_t formatLength, void **scratchArea = nullptr, std::size_t scratchBytes = 0, const char *sourceFile = nullptr, int sourceLine = 0); // Internal I/O to/from a default-kind character scalar can avoid a // descriptor. Cookie IONAME(BeginInternalListOutput)(char *internal, std::size_t internalLength, void **scratchArea = nullptr, std::size_t scratchBytes = 0, const char *sourceFile = nullptr, int sourceLine = 0); Cookie IONAME(BeginInternalListInput)(const char *internal, std::size_t internalLength, void **scratchArea = nullptr, std::size_t scratchBytes = 0, const char *sourceFile = nullptr, int sourceLine = 0); Cookie IONAME(BeginInternalFormattedOutput)(char *internal, std::size_t internalLength, const char *format, std::size_t formatLength, void **scratchArea = nullptr, std::size_t scratchBytes = 0, const char *sourceFile = nullptr, int sourceLine = 0); Cookie IONAME(BeginInternalFormattedInput)(const char *internal, std::size_t internalLength, const char *format, std::size_t formatLength, void **scratchArea = nullptr, std::size_t scratchBytes = 0, const char *sourceFile = nullptr, int sourceLine = 0); // Internal namelist I/O Cookie IONAME(BeginInternalNamelistOutput)(const Descriptor &, const NamelistGroup &, void **scratchArea = nullptr, std::size_t scratchBytes = 0, const char *sourceFile = nullptr, int sourceLine = 0); Cookie IONAME(BeginInternalNamelistInput)(const Descriptor &, const NamelistGroup &, void **scratchArea = nullptr, std::size_t scratchBytes = 0, const char *sourceFile = nullptr, int sourceLine = 0); // External synchronous I/O initiation Cookie IONAME(BeginExternalListOutput)(ExternalUnit = DefaultUnit, const char *sourceFile = nullptr, int sourceLine = 0); Cookie IONAME(BeginExternalListInput)(ExternalUnit = DefaultUnit, const char *sourceFile = nullptr, int sourceLine = 0); Cookie IONAME(BeginExternalFormattedOutput)(const char *format, std::size_t, ExternalUnit = DefaultUnit, const char *sourceFile = nullptr, int sourceLine = 0); Cookie IONAME(BeginExternalFormattedInput)(const char *format, std::size_t, ExternalUnit = DefaultUnit, const char *sourceFile = nullptr, int sourceLine = 0); Cookie IONAME(BeginUnformattedOutput)(ExternalUnit = DefaultUnit, const char *sourceFile = nullptr, int sourceLine = 0); Cookie IONAME(BeginUnformattedInput)(ExternalUnit = DefaultUnit, const char *sourceFile = nullptr, int sourceLine = 0); Cookie IONAME(BeginExternalNamelistOutput)(const NamelistGroup &, ExternalUnit = DefaultUnit, const char *sourceFile = nullptr, int sourceLine = 0); Cookie IONAME(BeginExternalNamelistInput)(const NamelistGroup &, ExternalUnit = DefaultUnit, const char *sourceFile = nullptr, int sourceLine = 0); // Asynchronous I/O is supported (at most) for unformatted direct access // block transfers. AsynchronousId IONAME(BeginAsynchronousOutput)(ExternalUnit, std::int64_t REC, const char *, std::size_t, const char *sourceFile = nullptr, int sourceLine = 0); AsynchronousId IONAME(BeginAsynchronousInput)(ExternalUnit, std::int64_t REC, char *, std::size_t, const char *sourceFile = nullptr, int sourceLine = 0); Cookie IONAME(BeginWait)(ExternalUnit, AsynchronousId); Cookie IONAME(BeginWaitAll)(ExternalUnit); // Other I/O statements Cookie IONAME(BeginClose)( ExternalUnit, const char *sourceFile = nullptr, int sourceLine = 0); Cookie IONAME(BeginFlush)( ExternalUnit, const char *sourceFile = nullptr, int sourceLine = 0); Cookie IONAME(BeginBackspace)( ExternalUnit, const char *sourceFile = nullptr, int sourceLine = 0); Cookie IONAME(BeginEndfile)( ExternalUnit, const char *sourceFile = nullptr, int sourceLine = 0); Cookie IONAME(BeginRewind)( ExternalUnit, const char *sourceFile = nullptr, int sourceLine = 0); // OPEN(UNIT=) and OPEN(NEWUNIT=) have distinct interfaces. Cookie IONAME(BeginOpenUnit)( ExternalUnit, const char *sourceFile = nullptr, int sourceLine = 0); Cookie IONAME(BeginOpenNewUnit)( const char *sourceFile = nullptr, int sourceLine = 0); // The variant forms of INQUIRE() statements have distinct interfaces. // 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, const char *sourceFile = nullptr, int sourceLine = 0); Cookie IONAME(BeginInquireIoLength)( const char *sourceFile = nullptr, int sourceLine = 0); // If an I/O statement has any IOSTAT=, ERR=, END=, or EOR= specifiers, // call EnableHandlers() immediately after the Begin...() call. // An output or OPEN statement may not enable HasEnd or HasEor. // This call makes the runtime library defer those particular error/end // conditions to the EndIoStatement() call rather than terminating // the image. E.g., for READ(*,*,END=666) A, B, (C(J),J=1,N) // Cookie cookie{BeginExternalListInput(DefaultUnit,__FILE__,__LINE__)}; // EnableHandlers(cookie, false, false, true /*END=*/, false); // if (InputReal64(cookie, &A)) { // if (InputReal64(cookie, &B)) { // for (int J{1}; J<=N; ++J) { // if (!InputReal64(cookie, &C[J])) break; // } // } // } // if (EndIoStatement(cookie) == FORTRAN_RUTIME_IOSTAT_END) goto label666; void IONAME(EnableHandlers)(Cookie, bool hasIoStat = false, bool hasErr = false, bool hasEnd = false, bool hasEor = false, bool hasIoMsg = false); // Control list options. These return false on a error that the // Begin...() call has specified will be handled by the caller. // The interfaces that pass a default-kind CHARACTER argument // are limited to passing specific case-insensitive keyword values. // ADVANCE=YES, NO bool IONAME(SetAdvance)(Cookie, const char *, std::size_t); // BLANK=NULL, ZERO bool IONAME(SetBlank)(Cookie, const char *, std::size_t); // DECIMAL=COMMA, POINT bool IONAME(SetDecimal)(Cookie, const char *, std::size_t); // DELIM=APOSTROPHE, QUOTE, NONE bool IONAME(SetDelim)(Cookie, const char *, std::size_t); // PAD=YES, NO bool IONAME(SetPad)(Cookie, const char *, std::size_t); bool IONAME(SetPos)(Cookie, std::int64_t); bool IONAME(SetRec)(Cookie, std::int64_t); // ROUND=UP, DOWN, ZERO, NEAREST, COMPATIBLE, PROCESSOR_DEFINED bool IONAME(SetRound)(Cookie, const char *, std::size_t); // SIGN=PLUS, SUPPRESS, PROCESSOR_DEFINED bool IONAME(SetSign)(Cookie, const char *, std::size_t); // Data item transfer for modes other than namelist. // Any data object that can be passed as an actual argument without the // use of a temporary can be transferred by means of a descriptor; // vector-valued subscripts and coindexing will require elementwise // transfers &/or data copies. Unformatted transfers to/from contiguous // blocks of local image memory can avoid the descriptor, and there // are specializations for the most common scalar types. // // These functions return false when the I/O statement has encountered an // error or end-of-file/record condition that the caller has indicated // should not cause termination of the image by the runtime library. // Once the statement has encountered an error, all following items will be // ignored and also return false; but compiled code should check for errors // and avoid the following items when they might crash. bool IONAME(OutputDescriptor)(Cookie, const Descriptor &); bool IONAME(InputDescriptor)(Cookie, const Descriptor &); +// Contiguous transfers for unformatted I/O bool IONAME(OutputUnformattedBlock)( Cookie, const char *, std::size_t, std::size_t elementBytes); bool IONAME(InputUnformattedBlock)( Cookie, char *, std::size_t, std::size_t elementBytes); +// Formatted (including list directed) I/O data items bool IONAME(OutputInteger64)(Cookie, std::int64_t); bool IONAME(InputInteger)(Cookie, std::int64_t &, int kind = 8); bool IONAME(OutputReal32)(Cookie, float); bool IONAME(InputReal32)(Cookie, float &); bool IONAME(OutputReal64)(Cookie, double); bool IONAME(InputReal64)(Cookie, double &); bool IONAME(OutputComplex32)(Cookie, float, float); bool IONAME(InputComplex32)(Cookie, float[2]); bool IONAME(OutputComplex64)(Cookie, double, double); bool IONAME(InputComplex64)(Cookie, double[2]); +bool IONAME(OutputCharacter)(Cookie, const char *, std::size_t, int kind = 1); bool IONAME(OutputAscii)(Cookie, const char *, std::size_t); +bool IONAME(InputCharacter)(Cookie, char *, std::size_t, int kind = 1); bool IONAME(InputAscii)(Cookie, char *, std::size_t); bool IONAME(OutputLogical)(Cookie, bool); bool IONAME(InputLogical)(Cookie, bool &); // Additional specifier interfaces for the connection-list of // on OPEN statement (only). SetBlank(), SetDecimal(), // SetDelim(), GetIoMsg(), SetPad(), SetRound(), & SetSign() // are also acceptable for OPEN. // ACCESS=SEQUENTIAL, DIRECT, STREAM bool IONAME(SetAccess)(Cookie, const char *, std::size_t); // ACTION=READ, WRITE, or READWRITE bool IONAME(SetAction)(Cookie, const char *, std::size_t); // ASYNCHRONOUS=YES, NO bool IONAME(SetAsynchronous)(Cookie, const char *, std::size_t); // CARRIAGECONTROL=LIST, FORTRAN, NONE bool IONAME(SetCarriagecontrol)(Cookie, const char *, std::size_t); // CONVERT=NATIVE, LITTLE_ENDIAN, BIG_ENDIAN, or SWAP bool IONAME(SetConvert)(Cookie, const char *, std::size_t); // ENCODING=UTF-8, DEFAULT bool IONAME(SetEncoding)(Cookie, const char *, std::size_t); // FORM=FORMATTED, UNFORMATTED bool IONAME(SetForm)(Cookie, const char *, std::size_t); // POSITION=ASIS, REWIND, APPEND bool IONAME(SetPosition)(Cookie, const char *, std::size_t); bool IONAME(SetRecl)(Cookie, std::size_t); // RECL= // STATUS can be set during an OPEN or CLOSE statement. // For OPEN: STATUS=OLD, NEW, SCRATCH, REPLACE, UNKNOWN // For CLOSE: STATUS=KEEP, DELETE bool IONAME(SetStatus)(Cookie, const char *, std::size_t); 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); // READ(SIZE=), after all input items bool IONAME(GetSize)(Cookie, std::int64_t, int kind = 8); // INQUIRE(IOLENGTH=), after all output items bool IONAME(GetIoLength)(Cookie, std::int64_t, int kind = 8); // GetIoMsg() does not modify its argument unless an error or // end-of-record/file condition is present. void IONAME(GetIoMsg)(Cookie, char *, std::size_t); // IOMSG= // INQUIRE() specifiers are mostly identified by their NUL-terminated // case-insensitive names. // 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, InquiryKeywordHash, char *, std::size_t); // EXIST, NAMED, OPENED, and PENDING (without ID): 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, 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 // by the runtime library to serve a later I/O statement. // The return value can be used to implement IOSTAT=, ERR=, END=, & EOR=; // store it into the IOSTAT= variable if there is one, and test // it to implement the various branches. The error condition // returned is guaranteed to only be one of the problems that the // EnableHandlers() call has indicated should be handled in compiled code // rather than by terminating the image. enum Iostat IONAME(EndIoStatement)(Cookie); } // extern "C" } // namespace Fortran::runtime::io #endif diff --git a/flang/runtime/io-stmt.h b/flang/runtime/io-stmt.h index 3c82dc8b1b0a..343619bc121c 100644 --- a/flang/runtime/io-stmt.h +++ b/flang/runtime/io-stmt.h @@ -1,460 +1,472 @@ //===-- runtime/io-stmt.h ---------------------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // Representations of the state of an I/O statement in progress #ifndef FORTRAN_RUNTIME_IO_STMT_H_ #define FORTRAN_RUNTIME_IO_STMT_H_ #include "connection.h" #include "descriptor.h" #include "file.h" #include "format.h" #include "internal-unit.h" #include "io-api.h" #include "io-error.h" #include #include #include namespace Fortran::runtime::io { class ExternalFileUnit; class OpenStatementState; class InquireUnitState; class InquireNoUnitState; class InquireUnconnectedFileState; class InquireIOLengthState; class ExternalMiscIoStatementState; class CloseStatementState; class NoopCloseStatementState; template class InternalFormattedIoStatementState; template class InternalListIoStatementState; template class ExternalFormattedIoStatementState; template class ExternalListIoStatementState; template class UnformattedIoStatementState; +struct InputStatementState {}; +struct OutputStatementState {}; +template +using IoDirectionState = std::conditional_t; +struct FormattedIoStatementState {}; + // The Cookie type in the I/O API is a pointer (for C) to this class. class IoStatementState { public: template explicit IoStatementState(A &x) : u_{x} {} // These member functions each project themselves into the active alternative. // They're used by per-data-item routines in the I/O API (e.g., OutputReal64) // to interact with the state of the I/O statement in progress. // This design avoids virtual member functions and function pointers, // which may not have good support in some runtime environments. std::optional GetNextDataEdit(int = 1); bool Emit(const char *, std::size_t, std::size_t elementBytes = 0); std::optional GetCurrentChar(); // vacant after end of record bool AdvanceRecord(int = 1); void BackspaceRecord(); void HandleRelativePosition(std::int64_t); int EndIoStatement(); ConnectionState &GetConnectionState(); IoErrorHandler &GetIoErrorHandler() const; ExternalFileUnit *GetExternalFileUnit() const; // null if internal unit MutableModes &mutableModes(); void BeginReadingRecord(); void FinishReadingRecord(); 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 { return std::visit( [](auto &x) -> A * { if constexpr (std::is_convertible_v) { return &x.get(); } return nullptr; }, u_); } bool EmitRepeated(char, std::size_t); bool EmitField(const char *, std::size_t length, std::size_t width); std::optional SkipSpaces(std::optional &remaining); std::optional NextInField(std::optional &remaining); std::optional GetNextNonBlank(); // can advance record + template void CheckFormattedStmtType(const char *name) { + if (!get_if() || + !get_if>()) { + GetIoErrorHandler().Crash( + "%s called for I/O statement that is not formatted %s", name, + D == Direction::Output ? "output" : "input"); + } + } + private: std::variant, std::reference_wrapper, std::reference_wrapper, std::reference_wrapper< InternalFormattedIoStatementState>, std::reference_wrapper< InternalFormattedIoStatementState>, std::reference_wrapper>, std::reference_wrapper>, std::reference_wrapper< ExternalFormattedIoStatementState>, std::reference_wrapper< ExternalFormattedIoStatementState>, std::reference_wrapper>, 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_; }; // Base class for all per-I/O statement state classes. // Inherits IoErrorHandler from its base. struct IoStatementBase : public DefaultFormatControlCallbacks { using DefaultFormatControlCallbacks::DefaultFormatControlCallbacks; int EndIoStatement(); std::optional GetNextDataEdit(IoStatementState &, int = 1); ExternalFileUnit *GetExternalFileUnit() const { return nullptr; } void BeginReadingRecord() {} void FinishReadingRecord() {} 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 {}; -struct OutputStatementState {}; -template -using IoDirectionState = std::conditional_t; - -struct FormattedStatementState {}; - // Common state for list-directed internal & external I/O -template struct ListDirectedStatementState {}; -template <> struct ListDirectedStatementState { +template struct ListDirectedStatementState; +template <> +struct ListDirectedStatementState + : public FormattedIoStatementState { static std::size_t RemainingSpaceInRecord(const ConnectionState &); bool NeedAdvance(const ConnectionState &, std::size_t) const; bool EmitLeadingSpaceOrAdvance( IoStatementState &, std::size_t, bool isCharacter = false); std::optional GetNextDataEdit( IoStatementState &, int maxRepeat = 1); bool lastWasUndelimitedCharacter{false}; }; -template <> class ListDirectedStatementState { +template <> +class ListDirectedStatementState + : public FormattedIoStatementState { public: // Skips value separators, handles repetition and null values. // Vacant when '/' appears; present with descriptor == ListDirectedNullValue // when a null value appears. std::optional GetNextDataEdit( IoStatementState &, int maxRepeat = 1); private: int remaining_{0}; // for "r*" repetition std::int64_t initialRecordNumber_; std::int64_t initialPositionInRecord_; bool isFirstItem_{true}; // leading separator implies null first item bool hitSlash_{false}; // once '/' is seen, nullify further items bool realPart_{false}; bool imaginaryPart_{false}; }; template class InternalIoStatementState : public IoStatementBase, public IoDirectionState { public: using CharType = CHAR; using Buffer = std::conditional_t; InternalIoStatementState(Buffer, std::size_t, const char *sourceFile = nullptr, int sourceLine = 0); InternalIoStatementState( const Descriptor &, const char *sourceFile = nullptr, int sourceLine = 0); int EndIoStatement(); bool Emit(const CharType *, std::size_t chars /* not necessarily bytes */, std::size_t elementBytes = 0); std::optional GetCurrentChar(); bool AdvanceRecord(int = 1); void BackspaceRecord(); ConnectionState &GetConnectionState() { return unit_; } MutableModes &mutableModes() { return unit_.modes; } void HandleRelativePosition(std::int64_t); void HandleAbsolutePosition(std::int64_t); protected: bool free_{true}; InternalDescriptorUnit unit_; }; template class InternalFormattedIoStatementState : public InternalIoStatementState, - public FormattedStatementState { + public FormattedIoStatementState { public: using CharType = CHAR; using typename InternalIoStatementState::Buffer; InternalFormattedIoStatementState(Buffer internal, std::size_t internalLength, const CharType *format, std::size_t formatLength, const char *sourceFile = nullptr, int sourceLine = 0); InternalFormattedIoStatementState(const Descriptor &, const CharType *format, std::size_t formatLength, const char *sourceFile = nullptr, int sourceLine = 0); IoStatementState &ioStatementState() { return ioStatementState_; } int EndIoStatement(); std::optional GetNextDataEdit( IoStatementState &, int maxRepeat = 1) { return format_.GetNextDataEdit(*this, maxRepeat); } private: IoStatementState ioStatementState_; // points to *this using InternalIoStatementState::unit_; // format_ *must* be last; it may be partial someday FormatControl format_; }; template class InternalListIoStatementState : public InternalIoStatementState, public ListDirectedStatementState { public: using CharType = CHAR; using typename InternalIoStatementState::Buffer; InternalListIoStatementState(Buffer internal, std::size_t internalLength, const char *sourceFile = nullptr, int sourceLine = 0); InternalListIoStatementState( const Descriptor &, const char *sourceFile = nullptr, int sourceLine = 0); IoStatementState &ioStatementState() { return ioStatementState_; } using ListDirectedStatementState::GetNextDataEdit; private: IoStatementState ioStatementState_; // points to *this using InternalIoStatementState::unit_; }; class ExternalIoStatementBase : public IoStatementBase { public: ExternalIoStatementBase( ExternalFileUnit &, const char *sourceFile = nullptr, int sourceLine = 0); ExternalFileUnit &unit() { return unit_; } MutableModes &mutableModes(); ConnectionState &GetConnectionState(); int EndIoStatement(); ExternalFileUnit *GetExternalFileUnit() { return &unit_; } private: ExternalFileUnit &unit_; }; template class ExternalIoStatementState : public ExternalIoStatementBase, public IoDirectionState { public: using ExternalIoStatementBase::ExternalIoStatementBase; int EndIoStatement(); bool Emit(const char *, std::size_t, std::size_t elementBytes = 0); 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(); bool AdvanceRecord(int = 1); void BackspaceRecord(); void HandleRelativePosition(std::int64_t); void HandleAbsolutePosition(std::int64_t); void BeginReadingRecord(); void FinishReadingRecord(); }; template class ExternalFormattedIoStatementState : public ExternalIoStatementState, - public FormattedStatementState { + public FormattedIoStatementState { public: using CharType = CHAR; ExternalFormattedIoStatementState(ExternalFileUnit &, const CharType *format, std::size_t formatLength, const char *sourceFile = nullptr, int sourceLine = 0); MutableModes &mutableModes() { return mutableModes_; } int EndIoStatement(); std::optional GetNextDataEdit( IoStatementState &, int maxRepeat = 1) { return format_.GetNextDataEdit(*this, maxRepeat); } private: // These are forked from ConnectionState's modes at the beginning // of each formatted I/O statement so they may be overridden by control // edit descriptors during the statement. MutableModes mutableModes_; FormatControl format_; }; template class ExternalListIoStatementState : public ExternalIoStatementState, public ListDirectedStatementState { public: using ExternalIoStatementState::ExternalIoStatementState; using ListDirectedStatementState::GetNextDataEdit; }; template class UnformattedIoStatementState : public ExternalIoStatementState { public: using ExternalIoStatementState::ExternalIoStatementState; bool Receive(char *, std::size_t, std::size_t elementBytes = 0); bool Emit(const char *, std::size_t, std::size_t elementBytes = 0); int EndIoStatement(); }; class OpenStatementState : public ExternalIoStatementBase { public: OpenStatementState(ExternalFileUnit &unit, bool wasExtant, const char *sourceFile = nullptr, int sourceLine = 0) : ExternalIoStatementBase{unit, sourceFile, sourceLine}, wasExtant_{ wasExtant} {} bool wasExtant() const { return wasExtant_; } void set_status(OpenStatus status) { status_ = status; } // STATUS= 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: bool wasExtant_; std::optional status_; Position position_{Position::AsIs}; std::optional action_; Convert convert_{Convert::Native}; OwningPtr path_; std::size_t pathLength_; std::optional isUnformatted_; std::optional access_; }; class CloseStatementState : public ExternalIoStatementBase { public: CloseStatementState(ExternalFileUnit &unit, const char *sourceFile = nullptr, int sourceLine = 0) : ExternalIoStatementBase{unit, sourceFile, sourceLine} {} void set_status(CloseStatus status) { status_ = status; } int EndIoStatement(); private: CloseStatus status_{CloseStatus::Keep}; }; // For CLOSE(bad unit) and INQUIRE(unconnected unit) class NoUnitIoStatementState : public IoStatementBase { public: IoStatementState &ioStatementState() { return ioStatementState_; } 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; extern template class InternalFormattedIoStatementState; extern template class InternalListIoStatementState; extern template class InternalListIoStatementState; extern template class ExternalIoStatementState; extern template class ExternalIoStatementState; extern template class ExternalFormattedIoStatementState; extern template class ExternalFormattedIoStatementState; extern template class ExternalListIoStatementState; extern template class ExternalListIoStatementState; extern template class UnformattedIoStatementState; extern template class UnformattedIoStatementState; extern template class FormatControl< InternalFormattedIoStatementState>; extern template class FormatControl< InternalFormattedIoStatementState>; extern template class FormatControl< ExternalFormattedIoStatementState>; 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 }; 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/type-code.cpp b/flang/runtime/type-code.cpp index 3fda906516ed..19de2ef2e58e 100644 --- a/flang/runtime/type-code.cpp +++ b/flang/runtime/type-code.cpp @@ -1,152 +1,152 @@ //===-- runtime/type-code.cpp ---------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "type-code.h" namespace Fortran::runtime { TypeCode::TypeCode(TypeCategory f, int kind) { switch (f) { case TypeCategory::Integer: switch (kind) { case 1: raw_ = CFI_type_int8_t; break; case 2: raw_ = CFI_type_int16_t; break; case 4: raw_ = CFI_type_int32_t; break; case 8: raw_ = CFI_type_int64_t; break; case 16: raw_ = CFI_type_int128_t; break; } break; case TypeCategory::Real: switch (kind) { case 4: raw_ = CFI_type_float; break; case 8: raw_ = CFI_type_double; break; case 10: case 16: raw_ = CFI_type_long_double; break; } break; case TypeCategory::Complex: switch (kind) { case 4: raw_ = CFI_type_float_Complex; break; case 8: raw_ = CFI_type_double_Complex; break; case 10: case 16: raw_ = CFI_type_long_double_Complex; break; } break; case TypeCategory::Character: switch (kind) { case 1: raw_ = CFI_type_char; break; case 2: raw_ = CFI_type_char16_t; break; case 4: raw_ = CFI_type_char32_t; break; } break; case TypeCategory::Logical: switch (kind) { case 1: raw_ = CFI_type_Bool; break; case 2: - raw_ = CFI_type_int16_t; + raw_ = CFI_type_int_fast16_t; break; case 4: - raw_ = CFI_type_int32_t; + raw_ = CFI_type_int_fast32_t; break; case 8: - raw_ = CFI_type_int64_t; + raw_ = CFI_type_int_fast64_t; break; } break; case TypeCategory::Derived: raw_ = CFI_type_struct; break; } } std::optional> TypeCode::GetCategoryAndKind() const { switch (raw_) { case CFI_type_int8_t: return std::make_pair(TypeCategory::Integer, 1); case CFI_type_int16_t: return std::make_pair(TypeCategory::Integer, 2); case CFI_type_int32_t: return std::make_pair(TypeCategory::Integer, 4); case CFI_type_int64_t: return std::make_pair(TypeCategory::Integer, 8); case CFI_type_int128_t: return std::make_pair(TypeCategory::Integer, 16); case CFI_type_float: return std::make_pair(TypeCategory::Real, 4); case CFI_type_double: return std::make_pair(TypeCategory::Real, 8); case CFI_type_long_double: #if __x86_64__ return std::make_pair(TypeCategory::Real, 10); #else return std::make_pair(TypeCategory::Real, 16); #endif case CFI_type_float_Complex: return std::make_pair(TypeCategory::Complex, 4); case CFI_type_double_Complex: return std::make_pair(TypeCategory::Complex, 8); case CFI_type_long_double_Complex: #if __x86_64__ return std::make_pair(TypeCategory::Complex, 10); #else return std::make_pair(TypeCategory::Complex, 16); #endif case CFI_type_char: return std::make_pair(TypeCategory::Character, 1); case CFI_type_char16_t: return std::make_pair(TypeCategory::Character, 2); case CFI_type_char32_t: return std::make_pair(TypeCategory::Character, 4); case CFI_type_Bool: return std::make_pair(TypeCategory::Logical, 1); case CFI_type_int_fast8_t: return std::make_pair(TypeCategory::Logical, 1); case CFI_type_int_fast16_t: return std::make_pair(TypeCategory::Logical, 2); case CFI_type_int_fast32_t: return std::make_pair(TypeCategory::Logical, 4); case CFI_type_int_fast64_t: return std::make_pair(TypeCategory::Logical, 8); case CFI_type_struct: return std::make_pair(TypeCategory::Derived, 0); default: return std::nullopt; } } } // namespace Fortran::runtime diff --git a/flang/runtime/unit.cpp b/flang/runtime/unit.cpp index 8170fbc696c2..bcb8a478ad59 100644 --- a/flang/runtime/unit.cpp +++ b/flang/runtime/unit.cpp @@ -1,644 +1,644 @@ //===-- runtime/unit.cpp ----------------------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "unit.h" #include "environment.h" #include "io-error.h" #include "lock.h" #include "unit-map.h" #include #include namespace Fortran::runtime::io { // The per-unit data structures are created on demand so that Fortran I/O // 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) { if (!defaultOutput) { return; } CriticalSection critical{unitMapLock}; if (defaultOutput) { IoErrorHandler handler{terminator}; handler.HasIoStat(); // prevent nested crash if flush has error defaultOutput->Flush(handler); } } ExternalFileUnit *ExternalFileUnit::LookUp(int unit) { return GetUnitMap().LookUp(unit); } ExternalFileUnit &ExternalFileUnit::LookUpOrCrash( int unit, const Terminator &terminator) { ExternalFileUnit *file{LookUp(unit)}; if (!file) { terminator.Crash("Not an open I/O unit number: %d", unit); } return *file; } ExternalFileUnit &ExternalFileUnit::LookUpOrCreate( int unit, const Terminator &terminator, bool &wasExtant) { return GetUnitMap().LookUpOrCreate(unit, terminator, wasExtant); } ExternalFileUnit &ExternalFileUnit::LookUpOrCreateAnonymous( int unit, Direction dir, bool isUnformatted, const Terminator &terminator) { bool exists{false}; ExternalFileUnit &result{ GetUnitMap().LookUpOrCreate(unit, terminator, exists)}; if (!exists) { IoErrorHandler handler{terminator}; 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}; ExternalFileUnit &result{ GetUnitMap().LookUpOrCreate(unit, terminator, wasExtant)}; RUNTIME_CHECK(terminator, !wasExtant); return result; } ExternalFileUnit *ExternalFileUnit::LookUpForClose(int unit) { return GetUnitMap().LookUpForClose(unit); } int ExternalFileUnit::NewUnit(const Terminator &terminator) { return GetUnitMap().NewUnit(terminator).unitNumber(); } void ExternalFileUnit::OpenUnit(OpenStatus status, std::optional action, Position position, OwningPtr &&newPath, std::size_t newPathLength, Convert convert, IoErrorHandler &handler) { if (executionEnvironment.conversion != Convert::Unknown) { convert = executionEnvironment.conversion; } swapEndianness_ = convert == Convert::Swap || (convert == Convert::LittleEndian && !isHostLittleEndian) || (convert == Convert::BigEndian && isHostLittleEndian); if (IsOpen()) { if (status == OpenStatus::Old && (!newPath.get() || (path() && pathLength() == newPathLength && std::memcmp(path(), newPath.get(), newPathLength) == 0))) { // OPEN of existing unit, STATUS='OLD', not new FILE= newPath.reset(); 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, action, 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 && (*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::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); Close(status, handler); } void ExternalFileUnit::DestroyClosed() { 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; } CriticalSection critical{unitMapLock}; if (unitMap) { return *unitMap; } Terminator terminator{__FILE__, __LINE__}; IoErrorHandler handler{terminator}; unitMap = New{terminator}().release(); ExternalFileUnit &out{ExternalFileUnit::CreateNew(6, terminator)}; out.Predefine(1); out.SetDirection(Direction::Output, handler); defaultOutput = &out; ExternalFileUnit &in{ExternalFileUnit::CreateNew(5, terminator)}; in.Predefine(0); in.SetDirection(Direction::Input, handler); defaultInput = ∈ // TODO: Set UTF-8 mode from the environment return *unitMap; } void ExternalFileUnit::CloseAll(IoErrorHandler &handler) { CriticalSection critical{unitMapLock}; if (unitMap) { unitMap->CloseAll(handler); FreeMemoryAndNullify(unitMap); } defaultOutput = nullptr; } void ExternalFileUnit::FlushAll(IoErrorHandler &handler) { CriticalSection critical{unitMapLock}; if (unitMap) { unitMap->FlushAll(handler); } } static void SwapEndianness( char *data, std::size_t bytes, std::size_t elementBytes) { if (elementBytes > 1) { auto half{elementBytes >> 1}; for (std::size_t j{0}; j + elementBytes <= bytes; j += elementBytes) { for (std::size_t k{0}; k < half; ++k) { std::swap(data[j + k], data[j + elementBytes - 1 - k]); } } } } bool ExternalFileUnit::Emit(const char *data, std::size_t bytes, std::size_t elementBytes, IoErrorHandler &handler) { auto furthestAfter{std::max(furthestPositionInRecord, positionInRecord + static_cast(bytes))}; if (furthestAfter > recordLength.value_or(furthestAfter)) { 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() + recordOffsetInFrame_ + furthestPositionInRecord, ' ', positionInRecord - furthestPositionInRecord); } char *to{Frame() + recordOffsetInFrame_ + positionInRecord}; std::memcpy(to, data, bytes); if (swapEndianness_) { SwapEndianness(to, bytes, elementBytes); } positionInRecord += bytes; furthestPositionInRecord = furthestAfter; return true; } bool ExternalFileUnit::Receive(char *data, std::size_t bytes, std::size_t elementBytes, 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); if (swapEndianness_) { SwapEndianness(data, bytes, elementBytes); } positionInRecord += bytes; furthestPositionInRecord = furthestAfter; return true; } else { - handler.SignalEnd(); + // EOF or error: can be handled & has been signaled endfileRecordNumber = currentRecordNumber; return false; } } std::optional ExternalFileUnit::GetCurrentChar( IoErrorHandler &handler) { 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; } 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(); endfileRecordNumber = currentRecordNumber; } 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 false; } void ExternalFileUnit::SetLeftTabLimit() { leftTabLimit = furthestPositionInRecord; positionInRecord = furthestPositionInRecord; } void ExternalFileUnit::BeginReadingRecord(IoErrorHandler &handler) { RUNTIME_CHECK(handler, direction_ == Direction::Input); if (beganReadingRecord_) { return; } beganReadingRecord_ = true; 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); } } } void ExternalFileUnit::FinishReadingRecord(IoErrorHandler &handler) { RUNTIME_CHECK(handler, direction_ == Direction::Input && beganReadingRecord_); beganReadingRecord_ = false; if (access == Access::Sequential) { 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(); } } ++currentRecordNumber; BeginRecord(); } bool ExternalFileUnit::AdvanceRecord(IoErrorHandler &handler) { bool ok{true}; if (direction_ == Direction::Input) { FinishReadingRecord(handler); BeginReadingRecord(handler); } 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, 1, handler); // TODO: Windows CR+LF } } frameOffsetInFile_ += recordOffsetInFrame_ + recordLength.value_or(furthestPositionInRecord); recordOffsetInFrame_ = 0; impliedEndfile_ = true; ++currentRecordNumber; BeginRecord(); } return ok; } void ExternalFileUnit::BackspaceRecord(IoErrorHandler &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 { BackspaceVariableFormattedRecord(handler); } } } void ExternalFileUnit::FlushIfTerminal(IoErrorHandler &handler) { if (isTerminal()) { Flush(handler); } } 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; io_.reset(); u_.emplace(); lock_.Drop(); } void ExternalFileUnit::BeginSequentialVariableUnformattedInputRecord( IoErrorHandler &handler) { std::int32_t header{0}, footer{0}; 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 == recordOffsetInFrame_) { handler.SignalEnd(); } else { error = "Unformatted variable-length sequential file input failed at " "record #%jd (file offset %jd): truncated record header"; } } else { 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 variable-length sequential file input failed at " "record #%jd (file offset %jd): hit EOF reading record with " "length %jd bytes"; } else { std::memcpy(&footer, Frame() + recordOffsetInFrame_ + *recordLength, sizeof footer); if (footer != 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)"; } } } if (error) { handler.SignalError(error, static_cast(currentRecordNumber), static_cast(frameOffsetInFile_), static_cast(header), static_cast(footer)); // TODO: error recovery } positionInRecord = sizeof header; } void ExternalFileUnit::BeginSequentialVariableFormattedInputRecord( IoErrorHandler &handler) { if (this == defaultInput && defaultOutput) { defaultOutput->Flush(handler); } std::size_t length{0}; do { std::size_t need{recordOffsetInFrame_ + length + 1}; length = ReadFrame(frameOffsetInFile_, need, handler); if (length < need) { handler.SignalEnd(); break; } } while (!SetSequentialVariableFormattedRecordLength()); } void ExternalFileUnit::BackspaceFixedRecord(IoErrorHandler &handler) { RUNTIME_CHECK(handler, recordLength.has_value()); if (frameOffsetInFile_ < *recordLength) { handler.SignalError(IostatBackspaceAtFirstRecord); } else { frameOffsetInFile_ -= *recordLength; } } void ExternalFileUnit::BackspaceVariableUnformattedRecord( IoErrorHandler &handler) { std::int32_t header{0}, footer{0}; 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 NextSequentialVariableUnformattedInputRecord(). std::size_t got{ ReadFrame(frameOffsetInFile_ - headerBytes, headerBytes, handler)}; RUNTIME_CHECK(handler, got >= sizeof footer); std::memcpy(&footer, Frame(), sizeof footer); 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 // fail on a record with a NUL, so we have to do it the hard way. static const char *FindLastNewline(const char *str, std::size_t length) { for (const char *p{str + length}; p-- > str;) { if (*p == '\n') { return p; } } return nullptr; } void ExternalFileUnit::BackspaceVariableFormattedRecord( IoErrorHandler &handler) { // 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(), prevNL - 1 - frameOffsetInFile_)}) { recordOffsetInFrame_ = p - Frame() + 1; *recordLength = prevNL - (frameOffsetInFile_ + recordOffsetInFrame_); break; } } 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); } 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