Revert "[llvm-exegesis] Add in snippet address annotation (#74218)"
[llvm-project.git] / flang / runtime / unit.cpp
blob5fa8565c2f61fc56f3ffb080a4794b8245fdcaa5
1 //===-- runtime/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 //===----------------------------------------------------------------------===//
9 #include "unit.h"
10 #include "io-error.h"
11 #include "lock.h"
12 #include "tools.h"
13 #include "unit-map.h"
14 #include <cstdio>
15 #include <limits>
16 #include <utility>
18 namespace Fortran::runtime::io {
20 // The per-unit data structures are created on demand so that Fortran I/O
21 // should work without a Fortran main program.
22 static Lock unitMapLock;
23 static Lock createOpenLock;
24 static UnitMap *unitMap{nullptr};
25 static ExternalFileUnit *defaultInput{nullptr}; // unit 5
26 static ExternalFileUnit *defaultOutput{nullptr}; // unit 6
27 static ExternalFileUnit *errorOutput{nullptr}; // unit 0 extension
29 void FlushOutputOnCrash(const Terminator &terminator) {
30 if (!defaultOutput && !errorOutput) {
31 return;
33 IoErrorHandler handler{terminator};
34 handler.HasIoStat(); // prevent nested crash if flush has error
35 CriticalSection critical{unitMapLock};
36 if (defaultOutput) {
37 defaultOutput->FlushOutput(handler);
39 if (errorOutput) {
40 errorOutput->FlushOutput(handler);
44 ExternalFileUnit *ExternalFileUnit::LookUp(int unit) {
45 return GetUnitMap().LookUp(unit);
48 ExternalFileUnit *ExternalFileUnit::LookUpOrCreate(
49 int unit, const Terminator &terminator, bool &wasExtant) {
50 return GetUnitMap().LookUpOrCreate(unit, terminator, wasExtant);
53 ExternalFileUnit *ExternalFileUnit::LookUpOrCreateAnonymous(int unit,
54 Direction dir, std::optional<bool> isUnformatted,
55 const Terminator &terminator) {
56 // Make sure that the returned anonymous unit has been opened
57 // not just created in the unitMap.
58 CriticalSection critical{createOpenLock};
59 bool exists{false};
60 ExternalFileUnit *result{
61 GetUnitMap().LookUpOrCreate(unit, terminator, exists)};
62 if (result && !exists) {
63 IoErrorHandler handler{terminator};
64 result->OpenAnonymousUnit(
65 dir == Direction::Input ? OpenStatus::Unknown : OpenStatus::Replace,
66 Action::ReadWrite, Position::Rewind, Convert::Unknown, handler);
67 result->isUnformatted = isUnformatted;
69 return result;
72 ExternalFileUnit *ExternalFileUnit::LookUp(
73 const char *path, std::size_t pathLen) {
74 return GetUnitMap().LookUp(path, pathLen);
77 ExternalFileUnit &ExternalFileUnit::CreateNew(
78 int unit, const Terminator &terminator) {
79 bool wasExtant{false};
80 ExternalFileUnit *result{
81 GetUnitMap().LookUpOrCreate(unit, terminator, wasExtant)};
82 RUNTIME_CHECK(terminator, result && !wasExtant);
83 return *result;
86 ExternalFileUnit *ExternalFileUnit::LookUpForClose(int unit) {
87 return GetUnitMap().LookUpForClose(unit);
90 ExternalFileUnit &ExternalFileUnit::NewUnit(
91 const Terminator &terminator, bool forChildIo) {
92 ExternalFileUnit &unit{GetUnitMap().NewUnit(terminator)};
93 unit.createdForInternalChildIo_ = forChildIo;
94 return unit;
97 bool ExternalFileUnit::OpenUnit(std::optional<OpenStatus> status,
98 std::optional<Action> action, Position position, OwningPtr<char> &&newPath,
99 std::size_t newPathLength, Convert convert, IoErrorHandler &handler) {
100 if (convert == Convert::Unknown) {
101 convert = executionEnvironment.conversion;
103 swapEndianness_ = convert == Convert::Swap ||
104 (convert == Convert::LittleEndian && !isHostLittleEndian) ||
105 (convert == Convert::BigEndian && isHostLittleEndian);
106 bool impliedClose{false};
107 if (IsConnected()) {
108 bool isSamePath{newPath.get() && path() && pathLength() == newPathLength &&
109 std::memcmp(path(), newPath.get(), newPathLength) == 0};
110 if (status && *status != OpenStatus::Old && isSamePath) {
111 handler.SignalError("OPEN statement for connected unit may not have "
112 "explicit STATUS= other than 'OLD'");
113 return impliedClose;
115 if (!newPath.get() || isSamePath) {
116 // OPEN of existing unit, STATUS='OLD' or unspecified, not new FILE=
117 newPath.reset();
118 return impliedClose;
120 // Otherwise, OPEN on open unit with new FILE= implies CLOSE
121 DoImpliedEndfile(handler);
122 FlushOutput(handler);
123 TruncateFrame(0, handler);
124 Close(CloseStatus::Keep, handler);
125 impliedClose = true;
127 if (newPath.get() && newPathLength > 0) {
128 if (const auto *already{
129 GetUnitMap().LookUp(newPath.get(), newPathLength)}) {
130 handler.SignalError(IostatOpenAlreadyConnected,
131 "OPEN(UNIT=%d,FILE='%.*s'): file is already connected to unit %d",
132 unitNumber_, static_cast<int>(newPathLength), newPath.get(),
133 already->unitNumber_);
134 return impliedClose;
137 set_path(std::move(newPath), newPathLength);
138 Open(status.value_or(OpenStatus::Unknown), action, position, handler);
139 auto totalBytes{knownSize()};
140 if (access == Access::Direct) {
141 if (!openRecl) {
142 handler.SignalError(IostatOpenBadRecl,
143 "OPEN(UNIT=%d,ACCESS='DIRECT'): record length is not known",
144 unitNumber());
145 } else if (*openRecl <= 0) {
146 handler.SignalError(IostatOpenBadRecl,
147 "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is invalid",
148 unitNumber(), static_cast<std::intmax_t>(*openRecl));
149 } else if (totalBytes && (*totalBytes % *openRecl != 0)) {
150 handler.SignalError(IostatOpenBadRecl,
151 "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is not an "
152 "even divisor of the file size %jd",
153 unitNumber(), static_cast<std::intmax_t>(*openRecl),
154 static_cast<std::intmax_t>(*totalBytes));
156 recordLength = openRecl;
158 endfileRecordNumber.reset();
159 currentRecordNumber = 1;
160 if (totalBytes && access == Access::Direct && openRecl.value_or(0) > 0) {
161 endfileRecordNumber = 1 + (*totalBytes / *openRecl);
163 if (position == Position::Append) {
164 if (totalBytes) {
165 frameOffsetInFile_ = *totalBytes;
167 if (access != Access::Stream) {
168 if (!endfileRecordNumber) {
169 // Fake it so that we can backspace relative from the end
170 endfileRecordNumber = std::numeric_limits<std::int64_t>::max() - 2;
172 currentRecordNumber = *endfileRecordNumber;
175 return impliedClose;
178 void ExternalFileUnit::OpenAnonymousUnit(std::optional<OpenStatus> status,
179 std::optional<Action> action, Position position, Convert convert,
180 IoErrorHandler &handler) {
181 // I/O to an unconnected unit reads/creates a local file, e.g. fort.7
182 std::size_t pathMaxLen{32};
183 auto path{SizedNew<char>{handler}(pathMaxLen)};
184 std::snprintf(path.get(), pathMaxLen, "fort.%d", unitNumber_);
185 OpenUnit(status, action, position, std::move(path), std::strlen(path.get()),
186 convert, handler);
189 void ExternalFileUnit::CloseUnit(CloseStatus status, IoErrorHandler &handler) {
190 DoImpliedEndfile(handler);
191 FlushOutput(handler);
192 Close(status, handler);
195 void ExternalFileUnit::DestroyClosed() {
196 GetUnitMap().DestroyClosed(*this); // destroys *this
199 Iostat ExternalFileUnit::SetDirection(Direction direction) {
200 if (direction == Direction::Input) {
201 if (mayRead()) {
202 direction_ = Direction::Input;
203 return IostatOk;
204 } else {
205 return IostatReadFromWriteOnly;
207 } else {
208 if (mayWrite()) {
209 direction_ = Direction::Output;
210 return IostatOk;
211 } else {
212 return IostatWriteToReadOnly;
217 UnitMap &ExternalFileUnit::CreateUnitMap() {
218 Terminator terminator{__FILE__, __LINE__};
219 IoErrorHandler handler{terminator};
220 UnitMap &newUnitMap{*New<UnitMap>{terminator}().release()};
222 bool wasExtant{false};
223 ExternalFileUnit &out{*newUnitMap.LookUpOrCreate(6, terminator, wasExtant)};
224 RUNTIME_CHECK(terminator, !wasExtant);
225 out.Predefine(1);
226 handler.SignalError(out.SetDirection(Direction::Output));
227 out.isUnformatted = false;
228 defaultOutput = &out;
230 ExternalFileUnit &in{*newUnitMap.LookUpOrCreate(5, terminator, wasExtant)};
231 RUNTIME_CHECK(terminator, !wasExtant);
232 in.Predefine(0);
233 handler.SignalError(in.SetDirection(Direction::Input));
234 in.isUnformatted = false;
235 defaultInput = &in;
237 ExternalFileUnit &error{*newUnitMap.LookUpOrCreate(0, terminator, wasExtant)};
238 RUNTIME_CHECK(terminator, !wasExtant);
239 error.Predefine(2);
240 handler.SignalError(error.SetDirection(Direction::Output));
241 error.isUnformatted = false;
242 errorOutput = &error;
244 return newUnitMap;
247 // A back-up atexit() handler for programs that don't terminate with a main
248 // program END or a STOP statement or other Fortran-initiated program shutdown,
249 // such as programs with a C main() that terminate normally. It flushes all
250 // external I/O units. It is registered once the first time that any external
251 // I/O is attempted.
252 static void CloseAllExternalUnits() {
253 IoErrorHandler handler{"Fortran program termination"};
254 ExternalFileUnit::CloseAll(handler);
257 UnitMap &ExternalFileUnit::GetUnitMap() {
258 if (unitMap) {
259 return *unitMap;
262 CriticalSection critical{unitMapLock};
263 if (unitMap) {
264 return *unitMap;
266 unitMap = &CreateUnitMap();
268 std::atexit(CloseAllExternalUnits);
269 return *unitMap;
272 void ExternalFileUnit::CloseAll(IoErrorHandler &handler) {
273 CriticalSection critical{unitMapLock};
274 if (unitMap) {
275 unitMap->CloseAll(handler);
276 FreeMemoryAndNullify(unitMap);
278 defaultOutput = nullptr;
279 defaultInput = nullptr;
280 errorOutput = nullptr;
283 void ExternalFileUnit::FlushAll(IoErrorHandler &handler) {
284 CriticalSection critical{unitMapLock};
285 if (unitMap) {
286 unitMap->FlushAll(handler);
290 static inline void SwapEndianness(
291 char *data, std::size_t bytes, std::size_t elementBytes) {
292 if (elementBytes > 1) {
293 auto half{elementBytes >> 1};
294 for (std::size_t j{0}; j + elementBytes <= bytes; j += elementBytes) {
295 for (std::size_t k{0}; k < half; ++k) {
296 std::swap(data[j + k], data[j + elementBytes - 1 - k]);
302 bool ExternalFileUnit::Emit(const char *data, std::size_t bytes,
303 std::size_t elementBytes, IoErrorHandler &handler) {
304 auto furthestAfter{std::max(furthestPositionInRecord,
305 positionInRecord + static_cast<std::int64_t>(bytes))};
306 if (openRecl) {
307 // Check for fixed-length record overrun, but allow for
308 // sequential record termination.
309 int extra{0};
310 int header{0};
311 if (access == Access::Sequential) {
312 if (isUnformatted.value_or(false)) {
313 // record header + footer
314 header = static_cast<int>(sizeof(std::uint32_t));
315 extra = 2 * header;
316 } else {
317 #ifdef _WIN32
318 if (!isWindowsTextFile()) {
319 ++extra; // carriage return (CR)
321 #endif
322 ++extra; // newline (LF)
325 if (furthestAfter > extra + *openRecl) {
326 handler.SignalError(IostatRecordWriteOverrun,
327 "Attempt to write %zd bytes to position %jd in a fixed-size record "
328 "of %jd bytes",
329 bytes, static_cast<std::intmax_t>(positionInRecord - header),
330 static_cast<std::intmax_t>(*openRecl));
331 return false;
334 if (recordLength) {
335 // It is possible for recordLength to have a value now for a
336 // variable-length output record if the previous operation
337 // was a BACKSPACE or non advancing input statement.
338 recordLength.reset();
339 beganReadingRecord_ = false;
341 if (IsAfterEndfile()) {
342 handler.SignalError(IostatWriteAfterEndfile);
343 return false;
345 CheckDirectAccess(handler);
346 WriteFrame(frameOffsetInFile_, recordOffsetInFrame_ + furthestAfter, handler);
347 if (positionInRecord > furthestPositionInRecord) {
348 std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord, ' ',
349 positionInRecord - furthestPositionInRecord);
351 char *to{Frame() + recordOffsetInFrame_ + positionInRecord};
352 std::memcpy(to, data, bytes);
353 if (swapEndianness_) {
354 SwapEndianness(to, bytes, elementBytes);
356 positionInRecord += bytes;
357 furthestPositionInRecord = furthestAfter;
358 return true;
361 bool ExternalFileUnit::Receive(char *data, std::size_t bytes,
362 std::size_t elementBytes, IoErrorHandler &handler) {
363 RUNTIME_CHECK(handler, direction_ == Direction::Input);
364 auto furthestAfter{std::max(furthestPositionInRecord,
365 positionInRecord + static_cast<std::int64_t>(bytes))};
366 if (furthestAfter > recordLength.value_or(furthestAfter)) {
367 handler.SignalError(IostatRecordReadOverrun,
368 "Attempt to read %zd bytes at position %jd in a record of %jd bytes",
369 bytes, static_cast<std::intmax_t>(positionInRecord),
370 static_cast<std::intmax_t>(*recordLength));
371 return false;
373 auto need{recordOffsetInFrame_ + furthestAfter};
374 auto got{ReadFrame(frameOffsetInFile_, need, handler)};
375 if (got >= need) {
376 std::memcpy(data, Frame() + recordOffsetInFrame_ + positionInRecord, bytes);
377 if (swapEndianness_) {
378 SwapEndianness(data, bytes, elementBytes);
380 positionInRecord += bytes;
381 furthestPositionInRecord = furthestAfter;
382 return true;
383 } else {
384 HitEndOnRead(handler);
385 return false;
389 std::size_t ExternalFileUnit::GetNextInputBytes(
390 const char *&p, IoErrorHandler &handler) {
391 RUNTIME_CHECK(handler, direction_ == Direction::Input);
392 std::size_t length{1};
393 if (auto recl{EffectiveRecordLength()}) {
394 if (positionInRecord < *recl) {
395 length = *recl - positionInRecord;
396 } else {
397 p = nullptr;
398 return 0;
401 p = FrameNextInput(handler, length);
402 return p ? length : 0;
405 const char *ExternalFileUnit::FrameNextInput(
406 IoErrorHandler &handler, std::size_t bytes) {
407 RUNTIME_CHECK(handler, isUnformatted.has_value() && !*isUnformatted);
408 if (static_cast<std::int64_t>(positionInRecord + bytes) <=
409 recordLength.value_or(positionInRecord + bytes)) {
410 auto at{recordOffsetInFrame_ + positionInRecord};
411 auto need{static_cast<std::size_t>(at + bytes)};
412 auto got{ReadFrame(frameOffsetInFile_, need, handler)};
413 SetVariableFormattedRecordLength();
414 if (got >= need) {
415 return Frame() + at;
417 HitEndOnRead(handler);
419 return nullptr;
422 bool ExternalFileUnit::SetVariableFormattedRecordLength() {
423 if (recordLength || access == Access::Direct) {
424 return true;
425 } else if (FrameLength() > recordOffsetInFrame_) {
426 const char *record{Frame() + recordOffsetInFrame_};
427 std::size_t bytes{FrameLength() - recordOffsetInFrame_};
428 if (const char *nl{FindCharacter(record, '\n', bytes)}) {
429 recordLength = nl - record;
430 if (*recordLength > 0 && record[*recordLength - 1] == '\r') {
431 --*recordLength;
433 return true;
436 return false;
439 bool ExternalFileUnit::BeginReadingRecord(IoErrorHandler &handler) {
440 RUNTIME_CHECK(handler, direction_ == Direction::Input);
441 if (!beganReadingRecord_) {
442 beganReadingRecord_ = true;
443 if (access == Access::Direct) {
444 CheckDirectAccess(handler);
445 auto need{static_cast<std::size_t>(recordOffsetInFrame_ + *openRecl)};
446 auto got{ReadFrame(frameOffsetInFile_, need, handler)};
447 if (got >= need) {
448 recordLength = openRecl;
449 } else {
450 recordLength.reset();
451 HitEndOnRead(handler);
453 } else {
454 recordLength.reset();
455 if (IsAtEOF()) {
456 handler.SignalEnd();
457 } else {
458 RUNTIME_CHECK(handler, isUnformatted.has_value());
459 if (*isUnformatted) {
460 if (access == Access::Sequential) {
461 BeginSequentialVariableUnformattedInputRecord(handler);
463 } else { // formatted sequential or stream
464 BeginVariableFormattedInputRecord(handler);
469 RUNTIME_CHECK(handler,
470 recordLength.has_value() || !IsRecordFile() || handler.InError());
471 return !handler.InError();
474 void ExternalFileUnit::FinishReadingRecord(IoErrorHandler &handler) {
475 RUNTIME_CHECK(handler, direction_ == Direction::Input && beganReadingRecord_);
476 beganReadingRecord_ = false;
477 if (handler.GetIoStat() == IostatEnd ||
478 (IsRecordFile() && !recordLength.has_value())) {
479 // Avoid bogus crashes in END/ERR circumstances; but
480 // still increment the current record number so that
481 // an attempted read of an endfile record, followed by
482 // a BACKSPACE, will still be at EOF.
483 ++currentRecordNumber;
484 } else if (IsRecordFile()) {
485 recordOffsetInFrame_ += *recordLength;
486 if (access != Access::Direct) {
487 RUNTIME_CHECK(handler, isUnformatted.has_value());
488 recordLength.reset();
489 if (isUnformatted.value_or(false)) {
490 // Retain footer in frame for more efficient BACKSPACE
491 frameOffsetInFile_ += recordOffsetInFrame_;
492 recordOffsetInFrame_ = sizeof(std::uint32_t);
493 } else { // formatted
494 if (FrameLength() > recordOffsetInFrame_ &&
495 Frame()[recordOffsetInFrame_] == '\r') {
496 ++recordOffsetInFrame_;
498 if (FrameLength() > recordOffsetInFrame_ &&
499 Frame()[recordOffsetInFrame_] == '\n') {
500 ++recordOffsetInFrame_;
502 if (!pinnedFrame || mayPosition()) {
503 frameOffsetInFile_ += recordOffsetInFrame_;
504 recordOffsetInFrame_ = 0;
508 ++currentRecordNumber;
509 } else { // unformatted stream
510 furthestPositionInRecord =
511 std::max(furthestPositionInRecord, positionInRecord);
512 frameOffsetInFile_ += recordOffsetInFrame_ + furthestPositionInRecord;
514 BeginRecord();
517 bool ExternalFileUnit::AdvanceRecord(IoErrorHandler &handler) {
518 if (direction_ == Direction::Input) {
519 FinishReadingRecord(handler);
520 return BeginReadingRecord(handler);
521 } else { // Direction::Output
522 bool ok{true};
523 RUNTIME_CHECK(handler, isUnformatted.has_value());
524 positionInRecord = furthestPositionInRecord;
525 if (access == Access::Direct) {
526 if (furthestPositionInRecord <
527 openRecl.value_or(furthestPositionInRecord)) {
528 // Pad remainder of fixed length record
529 WriteFrame(
530 frameOffsetInFile_, recordOffsetInFrame_ + *openRecl, handler);
531 std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord,
532 isUnformatted.value_or(false) ? 0 : ' ',
533 *openRecl - furthestPositionInRecord);
534 furthestPositionInRecord = *openRecl;
536 } else if (*isUnformatted) {
537 if (access == Access::Sequential) {
538 // Append the length of a sequential unformatted variable-length record
539 // as its footer, then overwrite the reserved first four bytes of the
540 // record with its length as its header. These four bytes were skipped
541 // over in BeginUnformattedIO<Output>().
542 // TODO: Break very large records up into subrecords with negative
543 // headers &/or footers
544 std::uint32_t length;
545 length = furthestPositionInRecord - sizeof length;
546 ok = ok &&
547 Emit(reinterpret_cast<const char *>(&length), sizeof length,
548 sizeof length, handler);
549 positionInRecord = 0;
550 ok = ok &&
551 Emit(reinterpret_cast<const char *>(&length), sizeof length,
552 sizeof length, handler);
553 } else {
554 // Unformatted stream: nothing to do
556 } else if (handler.GetIoStat() != IostatOk &&
557 furthestPositionInRecord == 0) {
558 // Error in formatted variable length record, and no output yet; do
559 // nothing, like most other Fortran compilers do.
560 return true;
561 } else {
562 // Terminate formatted variable length record
563 const char *lineEnding{"\n"};
564 std::size_t lineEndingBytes{1};
565 #ifdef _WIN32
566 if (!isWindowsTextFile()) {
567 lineEnding = "\r\n";
568 lineEndingBytes = 2;
570 #endif
571 ok = ok && Emit(lineEnding, lineEndingBytes, 1, handler);
573 leftTabLimit.reset();
574 if (IsAfterEndfile()) {
575 return false;
577 CommitWrites();
578 ++currentRecordNumber;
579 if (access != Access::Direct) {
580 impliedEndfile_ = IsRecordFile();
581 if (IsAtEOF()) {
582 endfileRecordNumber.reset();
585 return ok;
589 void ExternalFileUnit::BackspaceRecord(IoErrorHandler &handler) {
590 if (access == Access::Direct || !IsRecordFile()) {
591 handler.SignalError(IostatBackspaceNonSequential,
592 "BACKSPACE(UNIT=%d) on direct-access file or unformatted stream",
593 unitNumber());
594 } else {
595 if (IsAfterEndfile()) {
596 // BACKSPACE after explicit ENDFILE
597 currentRecordNumber = *endfileRecordNumber;
598 } else if (leftTabLimit) {
599 // BACKSPACE after non-advancing I/O
600 leftTabLimit.reset();
601 } else {
602 DoImpliedEndfile(handler);
603 if (frameOffsetInFile_ + recordOffsetInFrame_ > 0) {
604 --currentRecordNumber;
605 if (openRecl && access == Access::Direct) {
606 BackspaceFixedRecord(handler);
607 } else {
608 RUNTIME_CHECK(handler, isUnformatted.has_value());
609 if (isUnformatted.value_or(false)) {
610 BackspaceVariableUnformattedRecord(handler);
611 } else {
612 BackspaceVariableFormattedRecord(handler);
617 BeginRecord();
621 void ExternalFileUnit::FlushOutput(IoErrorHandler &handler) {
622 if (!mayPosition()) {
623 auto frameAt{FrameAt()};
624 if (frameOffsetInFile_ >= frameAt &&
625 frameOffsetInFile_ <
626 static_cast<std::int64_t>(frameAt + FrameLength())) {
627 // A Flush() that's about to happen to a non-positionable file
628 // needs to advance frameOffsetInFile_ to prevent attempts at
629 // impossible seeks
630 CommitWrites();
631 leftTabLimit.reset();
634 Flush(handler);
637 void ExternalFileUnit::FlushIfTerminal(IoErrorHandler &handler) {
638 if (isTerminal()) {
639 FlushOutput(handler);
643 void ExternalFileUnit::Endfile(IoErrorHandler &handler) {
644 if (access == Access::Direct) {
645 handler.SignalError(IostatEndfileDirect,
646 "ENDFILE(UNIT=%d) on direct-access file", unitNumber());
647 } else if (!mayWrite()) {
648 handler.SignalError(IostatEndfileUnwritable,
649 "ENDFILE(UNIT=%d) on read-only file", unitNumber());
650 } else if (IsAfterEndfile()) {
651 // ENDFILE after ENDFILE
652 } else {
653 DoEndfile(handler);
654 if (IsRecordFile() && access != Access::Direct) {
655 // Explicit ENDFILE leaves position *after* the endfile record
656 RUNTIME_CHECK(handler, endfileRecordNumber.has_value());
657 currentRecordNumber = *endfileRecordNumber + 1;
662 void ExternalFileUnit::Rewind(IoErrorHandler &handler) {
663 if (access == Access::Direct) {
664 handler.SignalError(IostatRewindNonSequential,
665 "REWIND(UNIT=%d) on non-sequential file", unitNumber());
666 } else {
667 SetPosition(0, handler);
668 currentRecordNumber = 1;
669 leftTabLimit.reset();
673 void ExternalFileUnit::SetPosition(std::int64_t pos, IoErrorHandler &handler) {
674 DoImpliedEndfile(handler);
675 frameOffsetInFile_ = pos;
676 recordOffsetInFrame_ = 0;
677 if (access == Access::Direct) {
678 directAccessRecWasSet_ = true;
680 BeginRecord();
683 bool ExternalFileUnit::SetStreamPos(
684 std::int64_t oneBasedPos, IoErrorHandler &handler) {
685 if (access != Access::Stream) {
686 handler.SignalError("POS= may not appear unless ACCESS='STREAM'");
687 return false;
689 if (oneBasedPos < 1) { // POS=1 is beginning of file (12.6.2.11)
690 handler.SignalError(
691 "POS=%zd is invalid", static_cast<std::intmax_t>(oneBasedPos));
692 return false;
694 SetPosition(oneBasedPos - 1, handler);
695 // We no longer know which record we're in. Set currentRecordNumber to
696 // a large value from whence we can both advance and backspace.
697 currentRecordNumber = std::numeric_limits<std::int64_t>::max() / 2;
698 endfileRecordNumber.reset();
699 return true;
702 bool ExternalFileUnit::SetDirectRec(
703 std::int64_t oneBasedRec, IoErrorHandler &handler) {
704 if (access != Access::Direct) {
705 handler.SignalError("REC= may not appear unless ACCESS='DIRECT'");
706 return false;
708 if (!openRecl) {
709 handler.SignalError("RECL= was not specified");
710 return false;
712 if (oneBasedRec < 1) {
713 handler.SignalError(
714 "REC=%zd is invalid", static_cast<std::intmax_t>(oneBasedRec));
715 return false;
717 currentRecordNumber = oneBasedRec;
718 SetPosition((oneBasedRec - 1) * *openRecl, handler);
719 return true;
722 void ExternalFileUnit::EndIoStatement() {
723 io_.reset();
724 u_.emplace<std::monostate>();
725 lock_.Drop();
728 void ExternalFileUnit::BeginSequentialVariableUnformattedInputRecord(
729 IoErrorHandler &handler) {
730 std::int32_t header{0}, footer{0};
731 std::size_t need{recordOffsetInFrame_ + sizeof header};
732 std::size_t got{ReadFrame(frameOffsetInFile_, need, handler)};
733 // Try to emit informative errors to help debug corrupted files.
734 const char *error{nullptr};
735 if (got < need) {
736 if (got == recordOffsetInFrame_) {
737 HitEndOnRead(handler);
738 } else {
739 error = "Unformatted variable-length sequential file input failed at "
740 "record #%jd (file offset %jd): truncated record header";
742 } else {
743 header = ReadHeaderOrFooter(recordOffsetInFrame_);
744 recordLength = sizeof header + header; // does not include footer
745 need = recordOffsetInFrame_ + *recordLength + sizeof footer;
746 got = ReadFrame(frameOffsetInFile_, need, handler);
747 if (got < need) {
748 error = "Unformatted variable-length sequential file input failed at "
749 "record #%jd (file offset %jd): hit EOF reading record with "
750 "length %jd bytes";
751 } else {
752 footer = ReadHeaderOrFooter(recordOffsetInFrame_ + *recordLength);
753 if (footer != header) {
754 error = "Unformatted variable-length sequential file input failed at "
755 "record #%jd (file offset %jd): record header has length %jd "
756 "that does not match record footer (%jd)";
760 if (error) {
761 handler.SignalError(error, static_cast<std::intmax_t>(currentRecordNumber),
762 static_cast<std::intmax_t>(frameOffsetInFile_),
763 static_cast<std::intmax_t>(header), static_cast<std::intmax_t>(footer));
764 // TODO: error recovery
766 positionInRecord = sizeof header;
769 void ExternalFileUnit::BeginVariableFormattedInputRecord(
770 IoErrorHandler &handler) {
771 if (this == defaultInput) {
772 if (defaultOutput) {
773 defaultOutput->FlushOutput(handler);
775 if (errorOutput) {
776 errorOutput->FlushOutput(handler);
779 std::size_t length{0};
780 do {
781 std::size_t need{length + 1};
782 length =
783 ReadFrame(frameOffsetInFile_, recordOffsetInFrame_ + need, handler) -
784 recordOffsetInFrame_;
785 if (length < need) {
786 if (length > 0) {
787 // final record w/o \n
788 recordLength = length;
789 unterminatedRecord = true;
790 } else {
791 HitEndOnRead(handler);
793 break;
795 } while (!SetVariableFormattedRecordLength());
798 void ExternalFileUnit::BackspaceFixedRecord(IoErrorHandler &handler) {
799 RUNTIME_CHECK(handler, openRecl.has_value());
800 if (frameOffsetInFile_ < *openRecl) {
801 handler.SignalError(IostatBackspaceAtFirstRecord);
802 } else {
803 frameOffsetInFile_ -= *openRecl;
807 void ExternalFileUnit::BackspaceVariableUnformattedRecord(
808 IoErrorHandler &handler) {
809 std::int32_t header{0};
810 auto headerBytes{static_cast<std::int64_t>(sizeof header)};
811 frameOffsetInFile_ += recordOffsetInFrame_;
812 recordOffsetInFrame_ = 0;
813 if (frameOffsetInFile_ <= headerBytes) {
814 handler.SignalError(IostatBackspaceAtFirstRecord);
815 return;
817 // Error conditions here cause crashes, not file format errors, because the
818 // validity of the file structure before the current record will have been
819 // checked informatively in NextSequentialVariableUnformattedInputRecord().
820 std::size_t got{
821 ReadFrame(frameOffsetInFile_ - headerBytes, headerBytes, handler)};
822 if (static_cast<std::int64_t>(got) < headerBytes) {
823 handler.SignalError(IostatShortRead);
824 return;
826 recordLength = ReadHeaderOrFooter(0);
827 if (frameOffsetInFile_ < *recordLength + 2 * headerBytes) {
828 handler.SignalError(IostatBadUnformattedRecord);
829 return;
831 frameOffsetInFile_ -= *recordLength + 2 * headerBytes;
832 auto need{static_cast<std::size_t>(
833 recordOffsetInFrame_ + sizeof header + *recordLength)};
834 got = ReadFrame(frameOffsetInFile_, need, handler);
835 if (got < need) {
836 handler.SignalError(IostatShortRead);
837 return;
839 header = ReadHeaderOrFooter(recordOffsetInFrame_);
840 if (header != *recordLength) {
841 handler.SignalError(IostatBadUnformattedRecord);
842 return;
846 // There's no portable memrchr(), unfortunately, and strrchr() would
847 // fail on a record with a NUL, so we have to do it the hard way.
848 static const char *FindLastNewline(const char *str, std::size_t length) {
849 for (const char *p{str + length}; p >= str; p--) {
850 if (*p == '\n') {
851 return p;
854 return nullptr;
857 void ExternalFileUnit::BackspaceVariableFormattedRecord(
858 IoErrorHandler &handler) {
859 // File offset of previous record's newline
860 auto prevNL{
861 frameOffsetInFile_ + static_cast<std::int64_t>(recordOffsetInFrame_) - 1};
862 if (prevNL < 0) {
863 handler.SignalError(IostatBackspaceAtFirstRecord);
864 return;
866 while (true) {
867 if (frameOffsetInFile_ < prevNL) {
868 if (const char *p{
869 FindLastNewline(Frame(), prevNL - 1 - frameOffsetInFile_)}) {
870 recordOffsetInFrame_ = p - Frame() + 1;
871 recordLength = prevNL - (frameOffsetInFile_ + recordOffsetInFrame_);
872 break;
875 if (frameOffsetInFile_ == 0) {
876 recordOffsetInFrame_ = 0;
877 recordLength = prevNL;
878 break;
880 frameOffsetInFile_ -= std::min<std::int64_t>(frameOffsetInFile_, 1024);
881 auto need{static_cast<std::size_t>(prevNL + 1 - frameOffsetInFile_)};
882 auto got{ReadFrame(frameOffsetInFile_, need, handler)};
883 if (got < need) {
884 handler.SignalError(IostatShortRead);
885 return;
888 if (Frame()[recordOffsetInFrame_ + *recordLength] != '\n') {
889 handler.SignalError(IostatMissingTerminator);
890 return;
892 if (*recordLength > 0 &&
893 Frame()[recordOffsetInFrame_ + *recordLength - 1] == '\r') {
894 --*recordLength;
898 void ExternalFileUnit::DoImpliedEndfile(IoErrorHandler &handler) {
899 if (!impliedEndfile_ && direction_ == Direction::Output && IsRecordFile() &&
900 access != Access::Direct && leftTabLimit) {
901 // Complete partial record after non-advancing write before
902 // positioning or closing the unit. Usually sets impliedEndfile_.
903 AdvanceRecord(handler);
905 if (impliedEndfile_) {
906 impliedEndfile_ = false;
907 if (access != Access::Direct && IsRecordFile() && mayPosition()) {
908 DoEndfile(handler);
913 void ExternalFileUnit::DoEndfile(IoErrorHandler &handler) {
914 if (IsRecordFile() && access != Access::Direct) {
915 furthestPositionInRecord =
916 std::max(positionInRecord, furthestPositionInRecord);
917 if (leftTabLimit) {
918 // Last read/write was non-advancing, so AdvanceRecord() was not called.
919 leftTabLimit.reset();
920 ++currentRecordNumber;
922 endfileRecordNumber = currentRecordNumber;
924 frameOffsetInFile_ += recordOffsetInFrame_ + furthestPositionInRecord;
925 recordOffsetInFrame_ = 0;
926 FlushOutput(handler);
927 Truncate(frameOffsetInFile_, handler);
928 TruncateFrame(frameOffsetInFile_, handler);
929 BeginRecord();
930 impliedEndfile_ = false;
933 void ExternalFileUnit::CommitWrites() {
934 frameOffsetInFile_ +=
935 recordOffsetInFrame_ + recordLength.value_or(furthestPositionInRecord);
936 recordOffsetInFrame_ = 0;
937 BeginRecord();
940 bool ExternalFileUnit::CheckDirectAccess(IoErrorHandler &handler) {
941 if (access == Access::Direct) {
942 RUNTIME_CHECK(handler, openRecl);
943 if (!directAccessRecWasSet_) {
944 handler.SignalError(
945 "No REC= was specified for a data transfer with ACCESS='DIRECT'");
946 return false;
949 return true;
952 void ExternalFileUnit::HitEndOnRead(IoErrorHandler &handler) {
953 handler.SignalEnd();
954 if (IsRecordFile() && access != Access::Direct) {
955 endfileRecordNumber = currentRecordNumber;
959 ChildIo &ExternalFileUnit::PushChildIo(IoStatementState &parent) {
960 OwningPtr<ChildIo> current{std::move(child_)};
961 Terminator &terminator{parent.GetIoErrorHandler()};
962 OwningPtr<ChildIo> next{New<ChildIo>{terminator}(parent, std::move(current))};
963 child_.reset(next.release());
964 return *child_;
967 void ExternalFileUnit::PopChildIo(ChildIo &child) {
968 if (child_.get() != &child) {
969 child.parent().GetIoErrorHandler().Crash(
970 "ChildIo being popped is not top of stack");
972 child_.reset(child.AcquirePrevious().release()); // deletes top child
975 int ExternalFileUnit::GetAsynchronousId(IoErrorHandler &handler) {
976 if (!mayAsynchronous()) {
977 handler.SignalError(IostatBadAsynchronous);
978 return -1;
979 } else if (auto least{asyncIdAvailable_.LeastElement()}) {
980 asyncIdAvailable_.reset(*least);
981 return static_cast<int>(*least);
982 } else {
983 handler.SignalError(IostatTooManyAsyncOps);
984 return -1;
988 bool ExternalFileUnit::Wait(int id) {
989 if (static_cast<std::size_t>(id) >= asyncIdAvailable_.size() ||
990 asyncIdAvailable_.test(id)) {
991 return false;
992 } else {
993 if (id == 0) { // means "all IDs"
994 asyncIdAvailable_.set();
995 asyncIdAvailable_.reset(0);
996 } else {
997 asyncIdAvailable_.set(id);
999 return true;
1003 std::int32_t ExternalFileUnit::ReadHeaderOrFooter(std::int64_t frameOffset) {
1004 std::int32_t word;
1005 char *wordPtr{reinterpret_cast<char *>(&word)};
1006 std::memcpy(wordPtr, Frame() + frameOffset, sizeof word);
1007 if (swapEndianness_) {
1008 SwapEndianness(wordPtr, sizeof word, sizeof word);
1010 return word;
1013 void ChildIo::EndIoStatement() {
1014 io_.reset();
1015 u_.emplace<std::monostate>();
1018 Iostat ChildIo::CheckFormattingAndDirection(
1019 bool unformatted, Direction direction) {
1020 bool parentIsInput{!parent_.get_if<IoDirectionState<Direction::Output>>()};
1021 bool parentIsFormatted{parentIsInput
1022 ? parent_.get_if<FormattedIoStatementState<Direction::Input>>() !=
1023 nullptr
1024 : parent_.get_if<FormattedIoStatementState<Direction::Output>>() !=
1025 nullptr};
1026 bool parentIsUnformatted{!parentIsFormatted};
1027 if (unformatted != parentIsUnformatted) {
1028 return unformatted ? IostatUnformattedChildOnFormattedParent
1029 : IostatFormattedChildOnUnformattedParent;
1030 } else if (parentIsInput != (direction == Direction::Input)) {
1031 return parentIsInput ? IostatChildOutputToInputParent
1032 : IostatChildInputFromOutputParent;
1033 } else {
1034 return IostatOk;
1038 } // namespace Fortran::runtime::io