1 | //===-- lib/runtime/external-unit.cpp ---------------------------*- 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 | // Implemenation of ExternalFileUnit for RT_USE_PSEUDO_FILE_UNIT=0. |
10 | // |
11 | //===----------------------------------------------------------------------===// |
12 | |
13 | #include "unit-map.h" |
14 | #include "unit.h" |
15 | #include "flang-rt/runtime/io-error.h" |
16 | #include "flang-rt/runtime/lock.h" |
17 | #include "flang-rt/runtime/tools.h" |
18 | |
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) |
23 | |
24 | #include <cstdio> |
25 | #include <limits> |
26 | |
27 | namespace Fortran::runtime::io { |
28 | |
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}; |
34 | |
35 | void FlushOutputOnCrash(const Terminator &terminator) { |
36 | if (!defaultOutput && !errorOutput) { |
37 | return; |
38 | } |
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); |
44 | } |
45 | if (errorOutput) { |
46 | errorOutput->FlushOutput(handler); |
47 | } |
48 | } |
49 | |
50 | ExternalFileUnit *ExternalFileUnit::LookUp(int unit) { |
51 | return GetUnitMap().LookUp(unit); |
52 | } |
53 | |
54 | ExternalFileUnit *ExternalFileUnit::LookUpOrCreate( |
55 | int unit, const Terminator &terminator, bool &wasExtant) { |
56 | return GetUnitMap().LookUpOrCreate(unit, terminator, wasExtant); |
57 | } |
58 | |
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; |
71 | } |
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(); |
78 | } |
79 | result = nullptr; |
80 | } else { |
81 | result->isUnformatted = isUnformatted; |
82 | } |
83 | } |
84 | return result; |
85 | } |
86 | |
87 | ExternalFileUnit *ExternalFileUnit::LookUp( |
88 | const char *path, std::size_t pathLen) { |
89 | return GetUnitMap().LookUp(path, pathLen); |
90 | } |
91 | |
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; |
99 | } |
100 | |
101 | ExternalFileUnit *ExternalFileUnit::LookUpForClose(int unit) { |
102 | return GetUnitMap().LookUpForClose(unit); |
103 | } |
104 | |
105 | ExternalFileUnit &ExternalFileUnit::NewUnit( |
106 | const Terminator &terminator, bool forChildIo) { |
107 | ExternalFileUnit &unit{GetUnitMap().NewUnit(terminator)}; |
108 | unit.createdForInternalChildIo_ = forChildIo; |
109 | return unit; |
110 | } |
111 | |
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; |
118 | } |
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; |
130 | } |
131 | if (!newPath.get() || isSamePath) { |
132 | // OPEN of existing unit, STATUS='OLD' or unspecified, not new FILE= |
133 | newPath.reset(); |
134 | return impliedClose; |
135 | } |
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; |
142 | } |
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; |
151 | } |
152 | } |
153 | set_path(std::move(newPath), newPathLength); |
154 | Open(status.value_or(OpenStatus::Unknown), action, position, handler); |
155 | if (handler.InError()) { |
156 | return impliedClose; |
157 | } |
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)); |
174 | } |
175 | recordLength = openRecl; |
176 | } |
177 | endfileRecordNumber.reset(); |
178 | currentRecordNumber = 1; |
179 | if (totalBytes && access == Access::Direct && openRecl.value_or(0) > 0) { |
180 | endfileRecordNumber = 1 + (*totalBytes / *openRecl); |
181 | } |
182 | if (position == Position::Append) { |
183 | if (totalBytes) { |
184 | frameOffsetInFile_ = *totalBytes; |
185 | } |
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; |
190 | } |
191 | currentRecordNumber = *endfileRecordNumber; |
192 | } |
193 | } |
194 | return impliedClose; |
195 | } |
196 | |
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(); |
208 | } |
209 | |
210 | void ExternalFileUnit::CloseUnit(CloseStatus status, IoErrorHandler &handler) { |
211 | DoImpliedEndfile(handler); |
212 | FlushOutput(handler); |
213 | Close(status, handler); |
214 | } |
215 | |
216 | void ExternalFileUnit::DestroyClosed() { |
217 | GetUnitMap().DestroyClosed(*this); // destroys *this |
218 | } |
219 | |
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; |
227 | } |
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; |
236 | } |
237 | direction_ = Direction::Output; |
238 | return IostatOk; |
239 | } else { |
240 | return IostatWriteToReadOnly; |
241 | } |
242 | } |
243 | } |
244 | |
245 | UnitMap &ExternalFileUnit::CreateUnitMap() { |
246 | Terminator terminator{__FILE__, __LINE__}; |
247 | IoErrorHandler handler{terminator}; |
248 | UnitMap &newUnitMap{*New<UnitMap>{terminator}().release()}; |
249 | |
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; |
258 | |
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 = ∈ |
266 | |
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; |
274 | |
275 | return newUnitMap; |
276 | } |
277 | |
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); |
286 | } |
287 | |
288 | UnitMap &ExternalFileUnit::GetUnitMap() { |
289 | if (unitMap) { |
290 | return *unitMap; |
291 | } |
292 | { |
293 | CriticalSection critical{unitMapLock}; |
294 | if (unitMap) { |
295 | return *unitMap; |
296 | } |
297 | unitMap = &CreateUnitMap(); |
298 | } |
299 | std::atexit(func: CloseAllExternalUnits); |
300 | return *unitMap; |
301 | } |
302 | |
303 | void ExternalFileUnit::CloseAll(IoErrorHandler &handler) { |
304 | CriticalSection critical{unitMapLock}; |
305 | if (unitMap) { |
306 | unitMap->CloseAll(handler); |
307 | FreeMemoryAndNullify(unitMap); |
308 | } |
309 | defaultOutput = nullptr; |
310 | defaultInput = nullptr; |
311 | errorOutput = nullptr; |
312 | } |
313 | |
314 | void ExternalFileUnit::FlushAll(IoErrorHandler &handler) { |
315 | CriticalSection critical{unitMapLock}; |
316 | if (unitMap) { |
317 | unitMap->FlushAll(handler); |
318 | } |
319 | } |
320 | |
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); |
330 | } |
331 | } |
332 | handler.SignalError(IostatTooManyAsyncOps); |
333 | return -1; |
334 | } |
335 | } |
336 | |
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(); |
345 | } |
346 | asyncIdAvailable_[0].reset(0); |
347 | } else { |
348 | asyncIdAvailable_[id / 64].set(id % 64); |
349 | } |
350 | return true; |
351 | } |
352 | } |
353 | |
354 | } // namespace Fortran::runtime::io |
355 | #endif // !defined(RT_USE_PSEUDO_FILE_UNIT) |
356 | |