1 //===-- runtime/external-unit.cpp -----------------------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 // Implemenation of ExternalFileUnit for RT_USE_PSEUDO_FILE_UNIT=0.
11 //===----------------------------------------------------------------------===//
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)
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
) {
39 IoErrorHandler handler
{terminator
};
40 handler
.HasIoStat(); // prevent nested crash if flush has error
41 CriticalSection critical
{unitMapLock
};
43 defaultOutput
->FlushOutput(handler
);
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
};
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();
81 result
->isUnformatted
= isUnformatted
;
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
);
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
;
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};
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'");
131 if (!newPath
.get() || isSamePath
) {
132 // OPEN of existing unit, STATUS='OLD' or unspecified, not new FILE=
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
);
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_
);
153 set_path(std::move(newPath
), newPathLength
);
154 Open(status
.value_or(OpenStatus::Unknown
), action
, position
, handler
);
155 if (handler
.InError()) {
158 auto totalBytes
{knownSize()};
159 if (access
== Access::Direct
) {
161 handler
.SignalError(IostatOpenBadRecl
,
162 "OPEN(UNIT=%d,ACCESS='DIRECT'): record length is not known",
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
) {
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
;
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()),
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
) {
223 direction_
= Direction::Input
;
226 return IostatReadFromWriteOnly
;
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
;
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
);
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
);
263 handler
.SignalError(in
.SetDirection(Direction::Input
));
264 in
.isUnformatted
= false;
267 ExternalFileUnit
&error
{
268 *newUnitMap
.LookUpOrCreate(FORTRAN_ERROR_UNIT
, terminator
, wasExtant
)};
269 RUNTIME_CHECK(terminator
, !wasExtant
);
271 handler
.SignalError(error
.SetDirection(Direction::Output
));
272 error
.isUnformatted
= false;
273 errorOutput
= &error
;
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
283 static void CloseAllExternalUnits() {
284 IoErrorHandler handler
{"Fortran program termination"};
285 ExternalFileUnit::CloseAll(handler
);
288 UnitMap
&ExternalFileUnit::GetUnitMap() {
293 CriticalSection critical
{unitMapLock
};
297 unitMap
= &CreateUnitMap();
299 std::atexit(CloseAllExternalUnits
);
303 void ExternalFileUnit::CloseAll(IoErrorHandler
&handler
) {
304 CriticalSection critical
{unitMapLock
};
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
};
317 unitMap
->FlushAll(handler
);
321 int ExternalFileUnit::GetAsynchronousId(IoErrorHandler
&handler
) {
322 if (!mayAsynchronous()) {
323 handler
.SignalError(IostatBadAsynchronous
);
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
);
337 bool ExternalFileUnit::Wait(int id
) {
338 if (static_cast<std::size_t>(id
) >= maxAsyncIds
||
339 asyncIdAvailable_
[id
/ 64].test(id
% 64)) {
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);
348 asyncIdAvailable_
[id
/ 64].set(id
% 64);
354 } // namespace Fortran::runtime::io
355 #endif // !defined(RT_USE_PSEUDO_FILE_UNIT)