| 1 | //===-- lib/runtime/unit.h --------------------------------------*- C++ -*-===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | // Fortran external I/O units |
| 10 | |
| 11 | #ifndef FLANG_RT_RUNTIME_UNIT_H_ |
| 12 | #define FLANG_RT_RUNTIME_UNIT_H_ |
| 13 | |
| 14 | #include "flang-rt/runtime/buffer.h" |
| 15 | #include "flang-rt/runtime/connection.h" |
| 16 | #include "flang-rt/runtime/environment.h" |
| 17 | #include "flang-rt/runtime/file.h" |
| 18 | #include "flang-rt/runtime/format.h" |
| 19 | #include "flang-rt/runtime/io-error.h" |
| 20 | #include "flang-rt/runtime/io-stmt.h" |
| 21 | #include "flang-rt/runtime/lock.h" |
| 22 | #include "flang-rt/runtime/memory.h" |
| 23 | #include "flang-rt/runtime/terminator.h" |
| 24 | #include "flang/Common/constexpr-bitset.h" |
| 25 | #include "flang/Common/optional.h" |
| 26 | #include <cstdlib> |
| 27 | #include <cstring> |
| 28 | #include <flang/Common/variant.h> |
| 29 | |
| 30 | namespace Fortran::runtime::io { |
| 31 | |
| 32 | class UnitMap; |
| 33 | class ChildIo; |
| 34 | class ExternalFileUnit; |
| 35 | |
| 36 | enum FseekWhence { |
| 37 | FseekSet = 0, |
| 38 | FseekCurrent = 1, |
| 39 | FseekEnd = 2, |
| 40 | }; |
| 41 | |
| 42 | RT_OFFLOAD_VAR_GROUP_BEGIN |
| 43 | // Predefined file units. |
| 44 | extern RT_VAR_ATTRS ExternalFileUnit *defaultInput; // unit 5 |
| 45 | extern RT_VAR_ATTRS ExternalFileUnit *defaultOutput; // unit 6 |
| 46 | extern RT_VAR_ATTRS ExternalFileUnit *errorOutput; // unit 0 extension |
| 47 | RT_OFFLOAD_VAR_GROUP_END |
| 48 | |
| 49 | #if defined(RT_USE_PSEUDO_FILE_UNIT) |
| 50 | // A flavor of OpenFile class that pretends to be a terminal, |
| 51 | // and only provides basic buffering of the output |
| 52 | // in an internal buffer, and Write's the output |
| 53 | // using std::printf(). Since it does not rely on file system |
| 54 | // APIs, it can be used to implement external output |
| 55 | // for offload devices. |
| 56 | class PseudoOpenFile { |
| 57 | public: |
| 58 | using FileOffset = std::int64_t; |
| 59 | |
| 60 | RT_API_ATTRS const char *path() const { return nullptr; } |
| 61 | RT_API_ATTRS std::size_t pathLength() const { return 0; } |
| 62 | RT_API_ATTRS void set_path(OwningPtr<char> &&, std::size_t bytes) {} |
| 63 | RT_API_ATTRS bool mayRead() const { return false; } |
| 64 | RT_API_ATTRS bool mayWrite() const { return true; } |
| 65 | RT_API_ATTRS bool mayPosition() const { return false; } |
| 66 | RT_API_ATTRS bool mayAsynchronous() const { return false; } |
| 67 | RT_API_ATTRS void set_mayAsynchronous(bool yes); |
| 68 | // Pretend to be a terminal to force the output |
| 69 | // at the end of IO statement. |
| 70 | RT_API_ATTRS bool isTerminal() const { return true; } |
| 71 | RT_API_ATTRS bool isWindowsTextFile() const { return false; } |
| 72 | RT_API_ATTRS Fortran::common::optional<FileOffset> knownSize() const; |
| 73 | RT_API_ATTRS bool IsConnected() const { return false; } |
| 74 | RT_API_ATTRS void Open(OpenStatus, Fortran::common::optional<Action>, |
| 75 | Position, IoErrorHandler &); |
| 76 | RT_API_ATTRS void Predefine(int fd) {} |
| 77 | RT_API_ATTRS void Close(CloseStatus, IoErrorHandler &); |
| 78 | RT_API_ATTRS std::size_t Read(FileOffset, char *, std::size_t minBytes, |
| 79 | std::size_t maxBytes, IoErrorHandler &); |
| 80 | RT_API_ATTRS std::size_t Write( |
| 81 | FileOffset, const char *, std::size_t, IoErrorHandler &); |
| 82 | RT_API_ATTRS void Truncate(FileOffset, IoErrorHandler &); |
| 83 | RT_API_ATTRS int ReadAsynchronously( |
| 84 | FileOffset, char *, std::size_t, IoErrorHandler &); |
| 85 | RT_API_ATTRS int WriteAsynchronously( |
| 86 | FileOffset, const char *, std::size_t, IoErrorHandler &); |
| 87 | RT_API_ATTRS void Wait(int id, IoErrorHandler &); |
| 88 | RT_API_ATTRS void WaitAll(IoErrorHandler &); |
| 89 | RT_API_ATTRS Position InquirePosition() const; |
| 90 | }; |
| 91 | #endif // defined(RT_USE_PSEUDO_FILE_UNIT) |
| 92 | |
| 93 | #if !defined(RT_USE_PSEUDO_FILE_UNIT) |
| 94 | using OpenFileClass = OpenFile; |
| 95 | using FileFrameClass = FileFrame<ExternalFileUnit>; |
| 96 | #else // defined(RT_USE_PSEUDO_FILE_UNIT) |
| 97 | using OpenFileClass = PseudoOpenFile; |
| 98 | // Use not so big buffer for the pseudo file unit frame. |
| 99 | using FileFrameClass = FileFrame<ExternalFileUnit, 1024>; |
| 100 | #endif // defined(RT_USE_PSEUDO_FILE_UNIT) |
| 101 | |
| 102 | class ExternalFileUnit : public ConnectionState, |
| 103 | public OpenFileClass, |
| 104 | public FileFrameClass { |
| 105 | public: |
| 106 | static constexpr int maxAsyncIds{64 * 16}; |
| 107 | |
| 108 | explicit RT_API_ATTRS ExternalFileUnit(int unitNumber) |
| 109 | : unitNumber_{unitNumber} { |
| 110 | isUTF8 = executionEnvironment.defaultUTF8; |
| 111 | for (int j{0}; 64 * j < maxAsyncIds; ++j) { |
| 112 | asyncIdAvailable_[j].set(); |
| 113 | } |
| 114 | asyncIdAvailable_[0].reset(0); |
| 115 | } |
| 116 | RT_API_ATTRS ~ExternalFileUnit() {} |
| 117 | |
| 118 | RT_API_ATTRS int unitNumber() const { return unitNumber_; } |
| 119 | RT_API_ATTRS bool swapEndianness() const { return swapEndianness_; } |
| 120 | RT_API_ATTRS bool createdForInternalChildIo() const { |
| 121 | return createdForInternalChildIo_; |
| 122 | } |
| 123 | |
| 124 | static RT_API_ATTRS ExternalFileUnit *LookUp(int unit); |
| 125 | static RT_API_ATTRS ExternalFileUnit *LookUpOrCreate( |
| 126 | int unit, const Terminator &, bool &wasExtant); |
| 127 | static RT_API_ATTRS ExternalFileUnit *LookUpOrCreateAnonymous(int unit, |
| 128 | Direction, Fortran::common::optional<bool> isUnformatted, |
| 129 | IoErrorHandler &); |
| 130 | static RT_API_ATTRS ExternalFileUnit *LookUp( |
| 131 | const char *path, std::size_t pathLen); |
| 132 | static RT_API_ATTRS ExternalFileUnit &CreateNew(int unit, const Terminator &); |
| 133 | static RT_API_ATTRS ExternalFileUnit *LookUpForClose(int unit); |
| 134 | static RT_API_ATTRS ExternalFileUnit &NewUnit( |
| 135 | const Terminator &, bool forChildIo); |
| 136 | static RT_API_ATTRS void CloseAll(IoErrorHandler &); |
| 137 | static RT_API_ATTRS void FlushAll(IoErrorHandler &); |
| 138 | |
| 139 | // Returns true if an existing unit was closed |
| 140 | RT_API_ATTRS bool OpenUnit(Fortran::common::optional<OpenStatus>, |
| 141 | Fortran::common::optional<Action>, Position, OwningPtr<char> &&path, |
| 142 | std::size_t pathLength, Convert, IoErrorHandler &); |
| 143 | RT_API_ATTRS bool OpenAnonymousUnit(Fortran::common::optional<OpenStatus>, |
| 144 | Fortran::common::optional<Action>, Position, Convert, IoErrorHandler &); |
| 145 | RT_API_ATTRS void CloseUnit(CloseStatus, IoErrorHandler &); |
| 146 | RT_API_ATTRS void DestroyClosed(); |
| 147 | |
| 148 | RT_API_ATTRS Iostat SetDirection(Direction); |
| 149 | |
| 150 | template <typename A, typename... X> |
| 151 | RT_API_ATTRS IoStatementState &BeginIoStatement( |
| 152 | const Terminator &terminator, X &&...xs) { |
| 153 | // Take lock_ and hold it until EndIoStatement(). |
| 154 | #if USE_PTHREADS |
| 155 | if (!lock_.TakeIfNoDeadlock()) { |
| 156 | terminator.Crash("Recursive I/O attempted on unit %d" , unitNumber_); |
| 157 | } |
| 158 | #else |
| 159 | lock_.Take(); |
| 160 | #endif |
| 161 | A &state{u_.emplace<A>(std::forward<X>(xs)...)}; |
| 162 | if constexpr (!std::is_same_v<A, OpenStatementState>) { |
| 163 | state.mutableModes() = ConnectionState::modes; |
| 164 | } |
| 165 | directAccessRecWasSet_ = false; |
| 166 | io_.emplace(state); |
| 167 | return *io_; |
| 168 | } |
| 169 | |
| 170 | RT_API_ATTRS bool Emit( |
| 171 | const char *, std::size_t, std::size_t elementBytes, IoErrorHandler &); |
| 172 | RT_API_ATTRS bool Receive( |
| 173 | char *, std::size_t, std::size_t elementBytes, IoErrorHandler &); |
| 174 | RT_API_ATTRS std::size_t GetNextInputBytes(const char *&, IoErrorHandler &); |
| 175 | RT_API_ATTRS std::size_t ViewBytesInRecord(const char *&, bool forward) const; |
| 176 | RT_API_ATTRS bool BeginReadingRecord(IoErrorHandler &); |
| 177 | RT_API_ATTRS void FinishReadingRecord(IoErrorHandler &); |
| 178 | RT_API_ATTRS bool AdvanceRecord(IoErrorHandler &); |
| 179 | RT_API_ATTRS void BackspaceRecord(IoErrorHandler &); |
| 180 | RT_API_ATTRS void FlushOutput(IoErrorHandler &); |
| 181 | RT_API_ATTRS void FlushIfTerminal(IoErrorHandler &); |
| 182 | RT_API_ATTRS void Endfile(IoErrorHandler &); |
| 183 | RT_API_ATTRS void Rewind(IoErrorHandler &); |
| 184 | RT_API_ATTRS void EndIoStatement(); |
| 185 | RT_API_ATTRS bool SetStreamPos(std::int64_t oneBasedPos, IoErrorHandler &); |
| 186 | RT_API_ATTRS bool Fseek( |
| 187 | std::int64_t zeroBasedPos, enum FseekWhence, IoErrorHandler &); |
| 188 | RT_API_ATTRS bool SetDirectRec( |
| 189 | std::int64_t, IoErrorHandler &); // one-based, for REC= |
| 190 | RT_API_ATTRS std::int64_t InquirePos() const { |
| 191 | // 12.6.2.11 defines POS=1 as the beginning of file |
| 192 | return frameOffsetInFile_ + recordOffsetInFrame_ + positionInRecord + 1; |
| 193 | } |
| 194 | |
| 195 | RT_API_ATTRS ChildIo *GetChildIo() { return child_.get(); } |
| 196 | RT_API_ATTRS ChildIo &PushChildIo(IoStatementState &); |
| 197 | RT_API_ATTRS void PopChildIo(ChildIo &); |
| 198 | |
| 199 | RT_API_ATTRS int GetAsynchronousId(IoErrorHandler &); |
| 200 | RT_API_ATTRS bool Wait(int); |
| 201 | |
| 202 | private: |
| 203 | static RT_API_ATTRS UnitMap &CreateUnitMap(); |
| 204 | static RT_API_ATTRS UnitMap &GetUnitMap(); |
| 205 | RT_API_ATTRS const char *FrameNextInput(IoErrorHandler &, std::size_t); |
| 206 | RT_API_ATTRS void SetPosition(std::int64_t zeroBasedPos); |
| 207 | RT_API_ATTRS void Sought(std::int64_t zeroBasedPos); |
| 208 | RT_API_ATTRS void BeginSequentialVariableUnformattedInputRecord( |
| 209 | IoErrorHandler &); |
| 210 | RT_API_ATTRS void BeginVariableFormattedInputRecord(IoErrorHandler &); |
| 211 | RT_API_ATTRS void BackspaceFixedRecord(IoErrorHandler &); |
| 212 | RT_API_ATTRS void BackspaceVariableUnformattedRecord(IoErrorHandler &); |
| 213 | RT_API_ATTRS void BackspaceVariableFormattedRecord(IoErrorHandler &); |
| 214 | RT_API_ATTRS bool SetVariableFormattedRecordLength(); |
| 215 | RT_API_ATTRS void DoImpliedEndfile(IoErrorHandler &); |
| 216 | template <bool ANY_DIR = true, Direction DIR = Direction::Output> |
| 217 | RT_API_ATTRS void DoEndfile(IoErrorHandler &); |
| 218 | RT_API_ATTRS void CommitWrites(); |
| 219 | RT_API_ATTRS bool CheckDirectAccess(IoErrorHandler &); |
| 220 | RT_API_ATTRS void HitEndOnRead(IoErrorHandler &); |
| 221 | RT_API_ATTRS std::uint32_t ReadHeaderOrFooter(std::int64_t frameOffset); |
| 222 | |
| 223 | Lock lock_; |
| 224 | |
| 225 | int unitNumber_{-1}; |
| 226 | Direction direction_{Direction::Output}; |
| 227 | bool impliedEndfile_{false}; // sequential/stream output has taken place |
| 228 | bool beganReadingRecord_{false}; |
| 229 | bool anyWriteSinceLastPositioning_{false}; |
| 230 | bool directAccessRecWasSet_{false}; // REC= appeared |
| 231 | // Subtle: The beginning of the frame can't be allowed to advance |
| 232 | // during a single list-directed READ due to the possibility of a |
| 233 | // multi-record CHARACTER value with a "r*" repeat count. So we |
| 234 | // manage the frame and the current record therein separately. |
| 235 | std::int64_t frameOffsetInFile_{0}; |
| 236 | std::size_t recordOffsetInFrame_{0}; // of currentRecordNumber |
| 237 | bool swapEndianness_{false}; |
| 238 | bool createdForInternalChildIo_{false}; |
| 239 | common::BitSet<64> asyncIdAvailable_[maxAsyncIds / 64]; |
| 240 | |
| 241 | // When a synchronous I/O statement is in progress on this unit, holds its |
| 242 | // state. |
| 243 | std::variant<std::monostate, OpenStatementState, CloseStatementState, |
| 244 | ExternalFormattedIoStatementState<Direction::Output>, |
| 245 | ExternalFormattedIoStatementState<Direction::Input>, |
| 246 | ExternalListIoStatementState<Direction::Output>, |
| 247 | ExternalListIoStatementState<Direction::Input>, |
| 248 | ExternalUnformattedIoStatementState<Direction::Output>, |
| 249 | ExternalUnformattedIoStatementState<Direction::Input>, InquireUnitState, |
| 250 | ExternalMiscIoStatementState, ErroneousIoStatementState> |
| 251 | u_; |
| 252 | |
| 253 | // Points to the active alternative (if any) in u_ for use as a Cookie |
| 254 | Fortran::common::optional<IoStatementState> io_; |
| 255 | |
| 256 | // A stack of child I/O pseudo-units for defined I/O that have this |
| 257 | // unit number. |
| 258 | OwningPtr<ChildIo> child_; |
| 259 | }; |
| 260 | |
| 261 | // A pseudo-unit for child I/O statements in defined I/O subroutines; |
| 262 | // it forwards operations to the parent I/O statement, which might also |
| 263 | // be a child I/O statement. |
| 264 | class ChildIo { |
| 265 | public: |
| 266 | RT_API_ATTRS ChildIo(IoStatementState &parent, OwningPtr<ChildIo> &&previous) |
| 267 | : parent_{parent}, previous_{std::move(previous)} {} |
| 268 | |
| 269 | RT_API_ATTRS IoStatementState &parent() const { return parent_; } |
| 270 | |
| 271 | RT_API_ATTRS void EndIoStatement(); |
| 272 | |
| 273 | template <typename A, typename... X> |
| 274 | RT_API_ATTRS IoStatementState &BeginIoStatement(X &&...xs) { |
| 275 | A &state{u_.emplace<A>(std::forward<X>(xs)...)}; |
| 276 | io_.emplace(state); |
| 277 | return *io_; |
| 278 | } |
| 279 | |
| 280 | RT_API_ATTRS OwningPtr<ChildIo> AcquirePrevious() { |
| 281 | return std::move(previous_); |
| 282 | } |
| 283 | |
| 284 | RT_API_ATTRS Iostat CheckFormattingAndDirection(bool unformatted, Direction); |
| 285 | |
| 286 | private: |
| 287 | IoStatementState &parent_; |
| 288 | OwningPtr<ChildIo> previous_; |
| 289 | std::variant<std::monostate, |
| 290 | ChildFormattedIoStatementState<Direction::Output>, |
| 291 | ChildFormattedIoStatementState<Direction::Input>, |
| 292 | ChildListIoStatementState<Direction::Output>, |
| 293 | ChildListIoStatementState<Direction::Input>, |
| 294 | ChildUnformattedIoStatementState<Direction::Output>, |
| 295 | ChildUnformattedIoStatementState<Direction::Input>, InquireUnitState, |
| 296 | ErroneousIoStatementState, ExternalMiscIoStatementState> |
| 297 | u_; |
| 298 | Fortran::common::optional<IoStatementState> io_; |
| 299 | }; |
| 300 | |
| 301 | } // namespace Fortran::runtime::io |
| 302 | #endif // FLANG_RT_RUNTIME_UNIT_H_ |
| 303 | |