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 if (!result
->OpenAnonymousUnit(
69 dir
== Direction::Input
? OpenStatus::Unknown
: OpenStatus::Replace
,
70 Action::ReadWrite
, Position::Rewind
, Convert::Unknown
, handler
)) {
71 // fort.N isn't a writable file
72 if (ExternalFileUnit
* closed
{LookUpForClose(result
->unitNumber())}) {
73 closed
->DestroyClosed();
77 result
->isUnformatted
= isUnformatted
;
83 ExternalFileUnit
*ExternalFileUnit::LookUp(
84 const char *path
, std::size_t pathLen
) {
85 return GetUnitMap().LookUp(path
, pathLen
);
88 ExternalFileUnit
&ExternalFileUnit::CreateNew(
89 int unit
, const Terminator
&terminator
) {
90 bool wasExtant
{false};
91 ExternalFileUnit
*result
{
92 GetUnitMap().LookUpOrCreate(unit
, terminator
, wasExtant
)};
93 RUNTIME_CHECK(terminator
, result
&& !wasExtant
);
97 ExternalFileUnit
*ExternalFileUnit::LookUpForClose(int unit
) {
98 return GetUnitMap().LookUpForClose(unit
);
101 ExternalFileUnit
&ExternalFileUnit::NewUnit(
102 const Terminator
&terminator
, bool forChildIo
) {
103 ExternalFileUnit
&unit
{GetUnitMap().NewUnit(terminator
)};
104 unit
.createdForInternalChildIo_
= forChildIo
;
108 bool ExternalFileUnit::OpenUnit(Fortran::common::optional
<OpenStatus
> status
,
109 Fortran::common::optional
<Action
> action
, Position position
,
110 OwningPtr
<char> &&newPath
, std::size_t newPathLength
, Convert convert
,
111 IoErrorHandler
&handler
) {
112 if (convert
== Convert::Unknown
) {
113 convert
= executionEnvironment
.conversion
;
115 swapEndianness_
= convert
== Convert::Swap
||
116 (convert
== Convert::LittleEndian
&& !isHostLittleEndian
) ||
117 (convert
== Convert::BigEndian
&& isHostLittleEndian
);
118 bool impliedClose
{false};
120 bool isSamePath
{newPath
.get() && path() && pathLength() == newPathLength
&&
121 std::memcmp(path(), newPath
.get(), newPathLength
) == 0};
122 if (status
&& *status
!= OpenStatus::Old
&& isSamePath
) {
123 handler
.SignalError("OPEN statement for connected unit may not have "
124 "explicit STATUS= other than 'OLD'");
127 if (!newPath
.get() || isSamePath
) {
128 // OPEN of existing unit, STATUS='OLD' or unspecified, not new FILE=
132 // Otherwise, OPEN on open unit with new FILE= implies CLOSE
133 DoImpliedEndfile(handler
);
134 FlushOutput(handler
);
135 TruncateFrame(0, handler
);
136 Close(CloseStatus::Keep
, handler
);
139 if (newPath
.get() && newPathLength
> 0) {
140 if (const auto *already
{
141 GetUnitMap().LookUp(newPath
.get(), newPathLength
)}) {
142 handler
.SignalError(IostatOpenAlreadyConnected
,
143 "OPEN(UNIT=%d,FILE='%.*s'): file is already connected to unit %d",
144 unitNumber_
, static_cast<int>(newPathLength
), newPath
.get(),
145 already
->unitNumber_
);
149 set_path(std::move(newPath
), newPathLength
);
150 Open(status
.value_or(OpenStatus::Unknown
), action
, position
, handler
);
151 if (handler
.InError()) {
154 auto totalBytes
{knownSize()};
155 if (access
== Access::Direct
) {
157 handler
.SignalError(IostatOpenBadRecl
,
158 "OPEN(UNIT=%d,ACCESS='DIRECT'): record length is not known",
160 } else if (*openRecl
<= 0) {
161 handler
.SignalError(IostatOpenBadRecl
,
162 "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is invalid",
163 unitNumber(), static_cast<std::intmax_t>(*openRecl
));
164 } else if (totalBytes
&& (*totalBytes
% *openRecl
!= 0)) {
165 handler
.SignalError(IostatOpenBadRecl
,
166 "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is not an "
167 "even divisor of the file size %jd",
168 unitNumber(), static_cast<std::intmax_t>(*openRecl
),
169 static_cast<std::intmax_t>(*totalBytes
));
171 recordLength
= openRecl
;
173 endfileRecordNumber
.reset();
174 currentRecordNumber
= 1;
175 if (totalBytes
&& access
== Access::Direct
&& openRecl
.value_or(0) > 0) {
176 endfileRecordNumber
= 1 + (*totalBytes
/ *openRecl
);
178 if (position
== Position::Append
) {
180 frameOffsetInFile_
= *totalBytes
;
182 if (access
!= Access::Stream
) {
183 if (!endfileRecordNumber
) {
184 // Fake it so that we can backspace relative from the end
185 endfileRecordNumber
= std::numeric_limits
<std::int64_t>::max() - 2;
187 currentRecordNumber
= *endfileRecordNumber
;
193 bool ExternalFileUnit::OpenAnonymousUnit(
194 Fortran::common::optional
<OpenStatus
> status
,
195 Fortran::common::optional
<Action
> action
, Position position
,
196 Convert convert
, IoErrorHandler
&handler
) {
197 // I/O to an unconnected unit reads/creates a local file, e.g. fort.7
198 std::size_t pathMaxLen
{32};
199 auto path
{SizedNew
<char>{handler
}(pathMaxLen
)};
200 std::snprintf(path
.get(), pathMaxLen
, "fort.%d", unitNumber_
);
201 OpenUnit(status
, action
, position
, std::move(path
), std::strlen(path
.get()),
203 return IsConnected();
206 void ExternalFileUnit::CloseUnit(CloseStatus status
, IoErrorHandler
&handler
) {
207 DoImpliedEndfile(handler
);
208 FlushOutput(handler
);
209 Close(status
, handler
);
212 void ExternalFileUnit::DestroyClosed() {
213 GetUnitMap().DestroyClosed(*this); // destroys *this
216 Iostat
ExternalFileUnit::SetDirection(Direction direction
) {
217 if (direction
== Direction::Input
) {
219 direction_
= Direction::Input
;
222 return IostatReadFromWriteOnly
;
226 if (direction_
== Direction::Input
) {
227 // Don't retain any input data from previous record, like a
228 // variable-length unformatted record footer, in the frame,
229 // since we're going start writing frames.
230 frameOffsetInFile_
+= recordOffsetInFrame_
;
231 recordOffsetInFrame_
= 0;
233 direction_
= Direction::Output
;
236 return IostatWriteToReadOnly
;
241 UnitMap
&ExternalFileUnit::CreateUnitMap() {
242 Terminator terminator
{__FILE__
, __LINE__
};
243 IoErrorHandler handler
{terminator
};
244 UnitMap
&newUnitMap
{*New
<UnitMap
>{terminator
}().release()};
246 bool wasExtant
{false};
247 ExternalFileUnit
&out
{*newUnitMap
.LookUpOrCreate(
248 FORTRAN_DEFAULT_OUTPUT_UNIT
, terminator
, wasExtant
)};
249 RUNTIME_CHECK(terminator
, !wasExtant
);
251 handler
.SignalError(out
.SetDirection(Direction::Output
));
252 out
.isUnformatted
= false;
253 defaultOutput
= &out
;
255 ExternalFileUnit
&in
{*newUnitMap
.LookUpOrCreate(
256 FORTRAN_DEFAULT_INPUT_UNIT
, terminator
, wasExtant
)};
257 RUNTIME_CHECK(terminator
, !wasExtant
);
259 handler
.SignalError(in
.SetDirection(Direction::Input
));
260 in
.isUnformatted
= false;
263 ExternalFileUnit
&error
{
264 *newUnitMap
.LookUpOrCreate(FORTRAN_ERROR_UNIT
, terminator
, wasExtant
)};
265 RUNTIME_CHECK(terminator
, !wasExtant
);
267 handler
.SignalError(error
.SetDirection(Direction::Output
));
268 error
.isUnformatted
= false;
269 errorOutput
= &error
;
274 // A back-up atexit() handler for programs that don't terminate with a main
275 // program END or a STOP statement or other Fortran-initiated program shutdown,
276 // such as programs with a C main() that terminate normally. It flushes all
277 // external I/O units. It is registered once the first time that any external
279 static void CloseAllExternalUnits() {
280 IoErrorHandler handler
{"Fortran program termination"};
281 ExternalFileUnit::CloseAll(handler
);
284 UnitMap
&ExternalFileUnit::GetUnitMap() {
289 CriticalSection critical
{unitMapLock
};
293 unitMap
= &CreateUnitMap();
295 std::atexit(CloseAllExternalUnits
);
299 void ExternalFileUnit::CloseAll(IoErrorHandler
&handler
) {
300 CriticalSection critical
{unitMapLock
};
302 unitMap
->CloseAll(handler
);
303 FreeMemoryAndNullify(unitMap
);
305 defaultOutput
= nullptr;
306 defaultInput
= nullptr;
307 errorOutput
= nullptr;
310 void ExternalFileUnit::FlushAll(IoErrorHandler
&handler
) {
311 CriticalSection critical
{unitMapLock
};
313 unitMap
->FlushAll(handler
);
317 int ExternalFileUnit::GetAsynchronousId(IoErrorHandler
&handler
) {
318 if (!mayAsynchronous()) {
319 handler
.SignalError(IostatBadAsynchronous
);
322 for (int j
{0}; 64 * j
< maxAsyncIds
; ++j
) {
323 if (auto least
{asyncIdAvailable_
[j
].LeastElement()}) {
324 asyncIdAvailable_
[j
].reset(*least
);
325 return 64 * j
+ static_cast<int>(*least
);
328 handler
.SignalError(IostatTooManyAsyncOps
);
333 bool ExternalFileUnit::Wait(int id
) {
334 if (static_cast<std::size_t>(id
) >= maxAsyncIds
||
335 asyncIdAvailable_
[id
/ 64].test(id
% 64)) {
338 if (id
== 0) { // means "all IDs"
339 for (int j
{0}; 64 * j
< maxAsyncIds
; ++j
) {
340 asyncIdAvailable_
[j
].set();
342 asyncIdAvailable_
[0].reset(0);
344 asyncIdAvailable_
[id
/ 64].set(id
% 64);
350 } // namespace Fortran::runtime::io
351 #endif // !defined(RT_USE_PSEUDO_FILE_UNIT)