[Support] Remove unused includes (NFC) (#116752)
[llvm-project.git] / flang / runtime / external-unit.cpp
blobd17a92622f84486036e858dd7082cc6832ca3c27
1 //===-- runtime/external-unit.cpp -----------------------------------------===//
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 // Implemenation of ExternalFileUnit for RT_USE_PSEUDO_FILE_UNIT=0.
11 //===----------------------------------------------------------------------===//
13 #include "io-error.h"
14 #include "lock.h"
15 #include "tools.h"
16 #include "unit-map.h"
17 #include "unit.h"
19 // NOTE: the header files above may define OpenMP declare target
20 // variables, so they have to be included unconditionally
21 // so that the offload entries are consistent between host and device.
22 #if !defined(RT_USE_PSEUDO_FILE_UNIT)
24 #include <cstdio>
25 #include <limits>
27 namespace Fortran::runtime::io {
29 // The per-unit data structures are created on demand so that Fortran I/O
30 // should work without a Fortran main program.
31 static Lock unitMapLock;
32 static Lock createOpenLock;
33 static UnitMap *unitMap{nullptr};
35 void FlushOutputOnCrash(const Terminator &terminator) {
36 if (!defaultOutput && !errorOutput) {
37 return;
39 IoErrorHandler handler{terminator};
40 handler.HasIoStat(); // prevent nested crash if flush has error
41 CriticalSection critical{unitMapLock};
42 if (defaultOutput) {
43 defaultOutput->FlushOutput(handler);
45 if (errorOutput) {
46 errorOutput->FlushOutput(handler);
50 ExternalFileUnit *ExternalFileUnit::LookUp(int unit) {
51 return GetUnitMap().LookUp(unit);
54 ExternalFileUnit *ExternalFileUnit::LookUpOrCreate(
55 int unit, const Terminator &terminator, bool &wasExtant) {
56 return GetUnitMap().LookUpOrCreate(unit, terminator, wasExtant);
59 ExternalFileUnit *ExternalFileUnit::LookUpOrCreateAnonymous(int unit,
60 Direction dir, Fortran::common::optional<bool> isUnformatted,
61 IoErrorHandler &handler) {
62 // Make sure that the returned anonymous unit has been opened,
63 // not just created in the unitMap.
64 CriticalSection critical{createOpenLock};
65 bool exists{false};
66 ExternalFileUnit *result{GetUnitMap().LookUpOrCreate(unit, handler, exists)};
67 if (result && !exists) {
68 common::optional<Action> action;
69 if (dir == Direction::Output) {
70 action = Action::ReadWrite;
72 if (!result->OpenAnonymousUnit(
73 dir == Direction::Input ? OpenStatus::Unknown : OpenStatus::Replace,
74 action, Position::Rewind, Convert::Unknown, handler)) {
75 // fort.N isn't a writable file
76 if (ExternalFileUnit * closed{LookUpForClose(result->unitNumber())}) {
77 closed->DestroyClosed();
79 result = nullptr;
80 } else {
81 result->isUnformatted = isUnformatted;
84 return result;
87 ExternalFileUnit *ExternalFileUnit::LookUp(
88 const char *path, std::size_t pathLen) {
89 return GetUnitMap().LookUp(path, pathLen);
92 ExternalFileUnit &ExternalFileUnit::CreateNew(
93 int unit, const Terminator &terminator) {
94 bool wasExtant{false};
95 ExternalFileUnit *result{
96 GetUnitMap().LookUpOrCreate(unit, terminator, wasExtant)};
97 RUNTIME_CHECK(terminator, result && !wasExtant);
98 return *result;
101 ExternalFileUnit *ExternalFileUnit::LookUpForClose(int unit) {
102 return GetUnitMap().LookUpForClose(unit);
105 ExternalFileUnit &ExternalFileUnit::NewUnit(
106 const Terminator &terminator, bool forChildIo) {
107 ExternalFileUnit &unit{GetUnitMap().NewUnit(terminator)};
108 unit.createdForInternalChildIo_ = forChildIo;
109 return unit;
112 bool ExternalFileUnit::OpenUnit(Fortran::common::optional<OpenStatus> status,
113 Fortran::common::optional<Action> action, Position position,
114 OwningPtr<char> &&newPath, std::size_t newPathLength, Convert convert,
115 IoErrorHandler &handler) {
116 if (convert == Convert::Unknown) {
117 convert = executionEnvironment.conversion;
119 swapEndianness_ = convert == Convert::Swap ||
120 (convert == Convert::LittleEndian && !isHostLittleEndian) ||
121 (convert == Convert::BigEndian && isHostLittleEndian);
122 bool impliedClose{false};
123 if (IsConnected()) {
124 bool isSamePath{newPath.get() && path() && pathLength() == newPathLength &&
125 std::memcmp(path(), newPath.get(), newPathLength) == 0};
126 if (status && *status != OpenStatus::Old && isSamePath) {
127 handler.SignalError("OPEN statement for connected unit may not have "
128 "explicit STATUS= other than 'OLD'");
129 return impliedClose;
131 if (!newPath.get() || isSamePath) {
132 // OPEN of existing unit, STATUS='OLD' or unspecified, not new FILE=
133 newPath.reset();
134 return impliedClose;
136 // Otherwise, OPEN on open unit with new FILE= implies CLOSE
137 DoImpliedEndfile(handler);
138 FlushOutput(handler);
139 TruncateFrame(0, handler);
140 Close(CloseStatus::Keep, handler);
141 impliedClose = true;
143 if (newPath.get() && newPathLength > 0) {
144 if (const auto *already{
145 GetUnitMap().LookUp(newPath.get(), newPathLength)}) {
146 handler.SignalError(IostatOpenAlreadyConnected,
147 "OPEN(UNIT=%d,FILE='%.*s'): file is already connected to unit %d",
148 unitNumber_, static_cast<int>(newPathLength), newPath.get(),
149 already->unitNumber_);
150 return impliedClose;
153 set_path(std::move(newPath), newPathLength);
154 Open(status.value_or(OpenStatus::Unknown), action, position, handler);
155 if (handler.InError()) {
156 return impliedClose;
158 auto totalBytes{knownSize()};
159 if (access == Access::Direct) {
160 if (!openRecl) {
161 handler.SignalError(IostatOpenBadRecl,
162 "OPEN(UNIT=%d,ACCESS='DIRECT'): record length is not known",
163 unitNumber());
164 } else if (*openRecl <= 0) {
165 handler.SignalError(IostatOpenBadRecl,
166 "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is invalid",
167 unitNumber(), static_cast<std::intmax_t>(*openRecl));
168 } else if (totalBytes && (*totalBytes % *openRecl != 0)) {
169 handler.SignalError(IostatOpenBadRecl,
170 "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is not an "
171 "even divisor of the file size %jd",
172 unitNumber(), static_cast<std::intmax_t>(*openRecl),
173 static_cast<std::intmax_t>(*totalBytes));
175 recordLength = openRecl;
177 endfileRecordNumber.reset();
178 currentRecordNumber = 1;
179 if (totalBytes && access == Access::Direct && openRecl.value_or(0) > 0) {
180 endfileRecordNumber = 1 + (*totalBytes / *openRecl);
182 if (position == Position::Append) {
183 if (totalBytes) {
184 frameOffsetInFile_ = *totalBytes;
186 if (access != Access::Stream) {
187 if (!endfileRecordNumber) {
188 // Fake it so that we can backspace relative from the end
189 endfileRecordNumber = std::numeric_limits<std::int64_t>::max() - 2;
191 currentRecordNumber = *endfileRecordNumber;
194 return impliedClose;
197 bool ExternalFileUnit::OpenAnonymousUnit(
198 Fortran::common::optional<OpenStatus> status,
199 Fortran::common::optional<Action> action, Position position,
200 Convert convert, IoErrorHandler &handler) {
201 // I/O to an unconnected unit reads/creates a local file, e.g. fort.7
202 std::size_t pathMaxLen{32};
203 auto path{SizedNew<char>{handler}(pathMaxLen)};
204 std::snprintf(path.get(), pathMaxLen, "fort.%d", unitNumber_);
205 OpenUnit(status, action, position, std::move(path), std::strlen(path.get()),
206 convert, handler);
207 return IsConnected();
210 void ExternalFileUnit::CloseUnit(CloseStatus status, IoErrorHandler &handler) {
211 DoImpliedEndfile(handler);
212 FlushOutput(handler);
213 Close(status, handler);
216 void ExternalFileUnit::DestroyClosed() {
217 GetUnitMap().DestroyClosed(*this); // destroys *this
220 Iostat ExternalFileUnit::SetDirection(Direction direction) {
221 if (direction == Direction::Input) {
222 if (mayRead()) {
223 direction_ = Direction::Input;
224 return IostatOk;
225 } else {
226 return IostatReadFromWriteOnly;
228 } else {
229 if (mayWrite()) {
230 if (direction_ == Direction::Input) {
231 // Don't retain any input data from previous record, like a
232 // variable-length unformatted record footer, in the frame,
233 // since we're going start writing frames.
234 frameOffsetInFile_ += recordOffsetInFrame_;
235 recordOffsetInFrame_ = 0;
237 direction_ = Direction::Output;
238 return IostatOk;
239 } else {
240 return IostatWriteToReadOnly;
245 UnitMap &ExternalFileUnit::CreateUnitMap() {
246 Terminator terminator{__FILE__, __LINE__};
247 IoErrorHandler handler{terminator};
248 UnitMap &newUnitMap{*New<UnitMap>{terminator}().release()};
250 bool wasExtant{false};
251 ExternalFileUnit &out{*newUnitMap.LookUpOrCreate(
252 FORTRAN_DEFAULT_OUTPUT_UNIT, terminator, wasExtant)};
253 RUNTIME_CHECK(terminator, !wasExtant);
254 out.Predefine(1);
255 handler.SignalError(out.SetDirection(Direction::Output));
256 out.isUnformatted = false;
257 defaultOutput = &out;
259 ExternalFileUnit &in{*newUnitMap.LookUpOrCreate(
260 FORTRAN_DEFAULT_INPUT_UNIT, terminator, wasExtant)};
261 RUNTIME_CHECK(terminator, !wasExtant);
262 in.Predefine(0);
263 handler.SignalError(in.SetDirection(Direction::Input));
264 in.isUnformatted = false;
265 defaultInput = &in;
267 ExternalFileUnit &error{
268 *newUnitMap.LookUpOrCreate(FORTRAN_ERROR_UNIT, terminator, wasExtant)};
269 RUNTIME_CHECK(terminator, !wasExtant);
270 error.Predefine(2);
271 handler.SignalError(error.SetDirection(Direction::Output));
272 error.isUnformatted = false;
273 errorOutput = &error;
275 return newUnitMap;
278 // A back-up atexit() handler for programs that don't terminate with a main
279 // program END or a STOP statement or other Fortran-initiated program shutdown,
280 // such as programs with a C main() that terminate normally. It flushes all
281 // external I/O units. It is registered once the first time that any external
282 // I/O is attempted.
283 static void CloseAllExternalUnits() {
284 IoErrorHandler handler{"Fortran program termination"};
285 ExternalFileUnit::CloseAll(handler);
288 UnitMap &ExternalFileUnit::GetUnitMap() {
289 if (unitMap) {
290 return *unitMap;
293 CriticalSection critical{unitMapLock};
294 if (unitMap) {
295 return *unitMap;
297 unitMap = &CreateUnitMap();
299 std::atexit(CloseAllExternalUnits);
300 return *unitMap;
303 void ExternalFileUnit::CloseAll(IoErrorHandler &handler) {
304 CriticalSection critical{unitMapLock};
305 if (unitMap) {
306 unitMap->CloseAll(handler);
307 FreeMemoryAndNullify(unitMap);
309 defaultOutput = nullptr;
310 defaultInput = nullptr;
311 errorOutput = nullptr;
314 void ExternalFileUnit::FlushAll(IoErrorHandler &handler) {
315 CriticalSection critical{unitMapLock};
316 if (unitMap) {
317 unitMap->FlushAll(handler);
321 int ExternalFileUnit::GetAsynchronousId(IoErrorHandler &handler) {
322 if (!mayAsynchronous()) {
323 handler.SignalError(IostatBadAsynchronous);
324 return -1;
325 } else {
326 for (int j{0}; 64 * j < maxAsyncIds; ++j) {
327 if (auto least{asyncIdAvailable_[j].LeastElement()}) {
328 asyncIdAvailable_[j].reset(*least);
329 return 64 * j + static_cast<int>(*least);
332 handler.SignalError(IostatTooManyAsyncOps);
333 return -1;
337 bool ExternalFileUnit::Wait(int id) {
338 if (static_cast<std::size_t>(id) >= maxAsyncIds ||
339 asyncIdAvailable_[id / 64].test(id % 64)) {
340 return false;
341 } else {
342 if (id == 0) { // means "all IDs"
343 for (int j{0}; 64 * j < maxAsyncIds; ++j) {
344 asyncIdAvailable_[j].set();
346 asyncIdAvailable_[0].reset(0);
347 } else {
348 asyncIdAvailable_[id / 64].set(id % 64);
350 return true;
354 } // namespace Fortran::runtime::io
355 #endif // !defined(RT_USE_PSEUDO_FILE_UNIT)