[AArch64,ELF] Restrict MOVZ/MOVK to non-PIC large code model (#70178)
[llvm-project.git] / flang / runtime / unit.cpp
blobdec8d8032f6155ea7bb1346fa45658244c760584
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 UnitMap *unitMap{nullptr};
24 static ExternalFileUnit *defaultInput{nullptr}; // unit 5
25 static ExternalFileUnit *defaultOutput{nullptr}; // unit 6
26 static ExternalFileUnit *errorOutput{nullptr}; // unit 0 extension
28 void FlushOutputOnCrash(const Terminator &terminator) {
29 if (!defaultOutput && !errorOutput) {
30 return;
32 IoErrorHandler handler{terminator};
33 handler.HasIoStat(); // prevent nested crash if flush has error
34 CriticalSection critical{unitMapLock};
35 if (defaultOutput) {
36 defaultOutput->FlushOutput(handler);
38 if (errorOutput) {
39 errorOutput->FlushOutput(handler);
43 ExternalFileUnit *ExternalFileUnit::LookUp(int unit) {
44 return GetUnitMap().LookUp(unit);
47 ExternalFileUnit *ExternalFileUnit::LookUpOrCreate(
48 int unit, const Terminator &terminator, bool &wasExtant) {
49 return GetUnitMap().LookUpOrCreate(unit, terminator, wasExtant);
52 ExternalFileUnit *ExternalFileUnit::LookUpOrCreateAnonymous(int unit,
53 Direction dir, std::optional<bool> isUnformatted,
54 const Terminator &terminator) {
55 bool exists{false};
56 ExternalFileUnit *result{
57 GetUnitMap().LookUpOrCreate(unit, terminator, exists)};
58 if (result && !exists) {
59 IoErrorHandler handler{terminator};
60 result->OpenAnonymousUnit(
61 dir == Direction::Input ? OpenStatus::Unknown : OpenStatus::Replace,
62 Action::ReadWrite, Position::Rewind, Convert::Unknown, handler);
63 result->isUnformatted = isUnformatted;
65 return result;
68 ExternalFileUnit *ExternalFileUnit::LookUp(
69 const char *path, std::size_t pathLen) {
70 return GetUnitMap().LookUp(path, pathLen);
73 ExternalFileUnit &ExternalFileUnit::CreateNew(
74 int unit, const Terminator &terminator) {
75 bool wasExtant{false};
76 ExternalFileUnit *result{
77 GetUnitMap().LookUpOrCreate(unit, terminator, wasExtant)};
78 RUNTIME_CHECK(terminator, result && !wasExtant);
79 return *result;
82 ExternalFileUnit *ExternalFileUnit::LookUpForClose(int unit) {
83 return GetUnitMap().LookUpForClose(unit);
86 ExternalFileUnit &ExternalFileUnit::NewUnit(
87 const Terminator &terminator, bool forChildIo) {
88 ExternalFileUnit &unit{GetUnitMap().NewUnit(terminator)};
89 unit.createdForInternalChildIo_ = forChildIo;
90 return unit;
93 bool ExternalFileUnit::OpenUnit(std::optional<OpenStatus> status,
94 std::optional<Action> action, Position position, OwningPtr<char> &&newPath,
95 std::size_t newPathLength, Convert convert, IoErrorHandler &handler) {
96 if (convert == Convert::Unknown) {
97 convert = executionEnvironment.conversion;
99 swapEndianness_ = convert == Convert::Swap ||
100 (convert == Convert::LittleEndian && !isHostLittleEndian) ||
101 (convert == Convert::BigEndian && isHostLittleEndian);
102 bool impliedClose{false};
103 if (IsConnected()) {
104 bool isSamePath{newPath.get() && path() && pathLength() == newPathLength &&
105 std::memcmp(path(), newPath.get(), newPathLength) == 0};
106 if (status && *status != OpenStatus::Old && isSamePath) {
107 handler.SignalError("OPEN statement for connected unit may not have "
108 "explicit STATUS= other than 'OLD'");
109 return impliedClose;
111 if (!newPath.get() || isSamePath) {
112 // OPEN of existing unit, STATUS='OLD' or unspecified, not new FILE=
113 newPath.reset();
114 return impliedClose;
116 // Otherwise, OPEN on open unit with new FILE= implies CLOSE
117 DoImpliedEndfile(handler);
118 FlushOutput(handler);
119 TruncateFrame(0, handler);
120 Close(CloseStatus::Keep, handler);
121 impliedClose = true;
123 if (newPath.get() && newPathLength > 0) {
124 if (const auto *already{
125 GetUnitMap().LookUp(newPath.get(), newPathLength)}) {
126 handler.SignalError(IostatOpenAlreadyConnected,
127 "OPEN(UNIT=%d,FILE='%.*s'): file is already connected to unit %d",
128 unitNumber_, static_cast<int>(newPathLength), newPath.get(),
129 already->unitNumber_);
130 return impliedClose;
133 set_path(std::move(newPath), newPathLength);
134 Open(status.value_or(OpenStatus::Unknown), action, position, handler);
135 auto totalBytes{knownSize()};
136 if (access == Access::Direct) {
137 if (!openRecl) {
138 handler.SignalError(IostatOpenBadRecl,
139 "OPEN(UNIT=%d,ACCESS='DIRECT'): record length is not known",
140 unitNumber());
141 } else if (*openRecl <= 0) {
142 handler.SignalError(IostatOpenBadRecl,
143 "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is invalid",
144 unitNumber(), static_cast<std::intmax_t>(*openRecl));
145 } else if (totalBytes && (*totalBytes % *openRecl != 0)) {
146 handler.SignalError(IostatOpenBadRecl,
147 "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is not an "
148 "even divisor of the file size %jd",
149 unitNumber(), static_cast<std::intmax_t>(*openRecl),
150 static_cast<std::intmax_t>(*totalBytes));
152 recordLength = openRecl;
154 endfileRecordNumber.reset();
155 currentRecordNumber = 1;
156 if (totalBytes && access == Access::Direct && openRecl.value_or(0) > 0) {
157 endfileRecordNumber = 1 + (*totalBytes / *openRecl);
159 if (position == Position::Append) {
160 if (totalBytes) {
161 frameOffsetInFile_ = *totalBytes;
163 if (access != Access::Stream) {
164 if (!endfileRecordNumber) {
165 // Fake it so that we can backspace relative from the end
166 endfileRecordNumber = std::numeric_limits<std::int64_t>::max() - 2;
168 currentRecordNumber = *endfileRecordNumber;
171 return impliedClose;
174 void ExternalFileUnit::OpenAnonymousUnit(std::optional<OpenStatus> status,
175 std::optional<Action> action, Position position, Convert convert,
176 IoErrorHandler &handler) {
177 // I/O to an unconnected unit reads/creates a local file, e.g. fort.7
178 std::size_t pathMaxLen{32};
179 auto path{SizedNew<char>{handler}(pathMaxLen)};
180 std::snprintf(path.get(), pathMaxLen, "fort.%d", unitNumber_);
181 OpenUnit(status, action, position, std::move(path), std::strlen(path.get()),
182 convert, handler);
185 void ExternalFileUnit::CloseUnit(CloseStatus status, IoErrorHandler &handler) {
186 DoImpliedEndfile(handler);
187 FlushOutput(handler);
188 Close(status, handler);
191 void ExternalFileUnit::DestroyClosed() {
192 GetUnitMap().DestroyClosed(*this); // destroys *this
195 Iostat ExternalFileUnit::SetDirection(Direction direction) {
196 if (direction == Direction::Input) {
197 if (mayRead()) {
198 direction_ = Direction::Input;
199 return IostatOk;
200 } else {
201 return IostatReadFromWriteOnly;
203 } else {
204 if (mayWrite()) {
205 direction_ = Direction::Output;
206 return IostatOk;
207 } else {
208 return IostatWriteToReadOnly;
213 UnitMap &ExternalFileUnit::CreateUnitMap() {
214 Terminator terminator{__FILE__, __LINE__};
215 IoErrorHandler handler{terminator};
216 UnitMap &newUnitMap{*New<UnitMap>{terminator}().release()};
218 bool wasExtant{false};
219 ExternalFileUnit &out{*newUnitMap.LookUpOrCreate(6, terminator, wasExtant)};
220 RUNTIME_CHECK(terminator, !wasExtant);
221 out.Predefine(1);
222 handler.SignalError(out.SetDirection(Direction::Output));
223 out.isUnformatted = false;
224 defaultOutput = &out;
226 ExternalFileUnit &in{*newUnitMap.LookUpOrCreate(5, terminator, wasExtant)};
227 RUNTIME_CHECK(terminator, !wasExtant);
228 in.Predefine(0);
229 handler.SignalError(in.SetDirection(Direction::Input));
230 in.isUnformatted = false;
231 defaultInput = &in;
233 ExternalFileUnit &error{*newUnitMap.LookUpOrCreate(0, terminator, wasExtant)};
234 RUNTIME_CHECK(terminator, !wasExtant);
235 error.Predefine(2);
236 handler.SignalError(error.SetDirection(Direction::Output));
237 error.isUnformatted = false;
238 errorOutput = &error;
240 return newUnitMap;
243 // A back-up atexit() handler for programs that don't terminate with a main
244 // program END or a STOP statement or other Fortran-initiated program shutdown,
245 // such as programs with a C main() that terminate normally. It flushes all
246 // external I/O units. It is registered once the first time that any external
247 // I/O is attempted.
248 static void CloseAllExternalUnits() {
249 IoErrorHandler handler{"Fortran program termination"};
250 ExternalFileUnit::CloseAll(handler);
253 UnitMap &ExternalFileUnit::GetUnitMap() {
254 if (unitMap) {
255 return *unitMap;
258 CriticalSection critical{unitMapLock};
259 if (unitMap) {
260 return *unitMap;
262 unitMap = &CreateUnitMap();
264 std::atexit(CloseAllExternalUnits);
265 return *unitMap;
268 void ExternalFileUnit::CloseAll(IoErrorHandler &handler) {
269 CriticalSection critical{unitMapLock};
270 if (unitMap) {
271 unitMap->CloseAll(handler);
272 FreeMemoryAndNullify(unitMap);
274 defaultOutput = nullptr;
275 defaultInput = nullptr;
276 errorOutput = nullptr;
279 void ExternalFileUnit::FlushAll(IoErrorHandler &handler) {
280 CriticalSection critical{unitMapLock};
281 if (unitMap) {
282 unitMap->FlushAll(handler);
286 static inline void SwapEndianness(
287 char *data, std::size_t bytes, std::size_t elementBytes) {
288 if (elementBytes > 1) {
289 auto half{elementBytes >> 1};
290 for (std::size_t j{0}; j + elementBytes <= bytes; j += elementBytes) {
291 for (std::size_t k{0}; k < half; ++k) {
292 std::swap(data[j + k], data[j + elementBytes - 1 - k]);
298 bool ExternalFileUnit::Emit(const char *data, std::size_t bytes,
299 std::size_t elementBytes, IoErrorHandler &handler) {
300 auto furthestAfter{std::max(furthestPositionInRecord,
301 positionInRecord + static_cast<std::int64_t>(bytes))};
302 if (openRecl) {
303 // Check for fixed-length record overrun, but allow for
304 // sequential record termination.
305 int extra{0};
306 int header{0};
307 if (access == Access::Sequential) {
308 if (isUnformatted.value_or(false)) {
309 // record header + footer
310 header = static_cast<int>(sizeof(std::uint32_t));
311 extra = 2 * header;
312 } else {
313 #ifdef _WIN32
314 if (!isWindowsTextFile()) {
315 ++extra; // carriage return (CR)
317 #endif
318 ++extra; // newline (LF)
321 if (furthestAfter > extra + *openRecl) {
322 handler.SignalError(IostatRecordWriteOverrun,
323 "Attempt to write %zd bytes to position %jd in a fixed-size record "
324 "of %jd bytes",
325 bytes, static_cast<std::intmax_t>(positionInRecord - header),
326 static_cast<std::intmax_t>(*openRecl));
327 return false;
330 if (recordLength) {
331 // It is possible for recordLength to have a value now for a
332 // variable-length output record if the previous operation
333 // was a BACKSPACE or non advancing input statement.
334 recordLength.reset();
335 beganReadingRecord_ = false;
337 if (IsAfterEndfile()) {
338 handler.SignalError(IostatWriteAfterEndfile);
339 return false;
341 CheckDirectAccess(handler);
342 WriteFrame(frameOffsetInFile_, recordOffsetInFrame_ + furthestAfter, handler);
343 if (positionInRecord > furthestPositionInRecord) {
344 std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord, ' ',
345 positionInRecord - furthestPositionInRecord);
347 char *to{Frame() + recordOffsetInFrame_ + positionInRecord};
348 std::memcpy(to, data, bytes);
349 if (swapEndianness_) {
350 SwapEndianness(to, bytes, elementBytes);
352 positionInRecord += bytes;
353 furthestPositionInRecord = furthestAfter;
354 return true;
357 bool ExternalFileUnit::Receive(char *data, std::size_t bytes,
358 std::size_t elementBytes, IoErrorHandler &handler) {
359 RUNTIME_CHECK(handler, direction_ == Direction::Input);
360 auto furthestAfter{std::max(furthestPositionInRecord,
361 positionInRecord + static_cast<std::int64_t>(bytes))};
362 if (furthestAfter > recordLength.value_or(furthestAfter)) {
363 handler.SignalError(IostatRecordReadOverrun,
364 "Attempt to read %zd bytes at position %jd in a record of %jd bytes",
365 bytes, static_cast<std::intmax_t>(positionInRecord),
366 static_cast<std::intmax_t>(*recordLength));
367 return false;
369 auto need{recordOffsetInFrame_ + furthestAfter};
370 auto got{ReadFrame(frameOffsetInFile_, need, handler)};
371 if (got >= need) {
372 std::memcpy(data, Frame() + recordOffsetInFrame_ + positionInRecord, bytes);
373 if (swapEndianness_) {
374 SwapEndianness(data, bytes, elementBytes);
376 positionInRecord += bytes;
377 furthestPositionInRecord = furthestAfter;
378 return true;
379 } else {
380 HitEndOnRead(handler);
381 return false;
385 std::size_t ExternalFileUnit::GetNextInputBytes(
386 const char *&p, IoErrorHandler &handler) {
387 RUNTIME_CHECK(handler, direction_ == Direction::Input);
388 std::size_t length{1};
389 if (auto recl{EffectiveRecordLength()}) {
390 if (positionInRecord < *recl) {
391 length = *recl - positionInRecord;
392 } else {
393 p = nullptr;
394 return 0;
397 p = FrameNextInput(handler, length);
398 return p ? length : 0;
401 const char *ExternalFileUnit::FrameNextInput(
402 IoErrorHandler &handler, std::size_t bytes) {
403 RUNTIME_CHECK(handler, isUnformatted.has_value() && !*isUnformatted);
404 if (static_cast<std::int64_t>(positionInRecord + bytes) <=
405 recordLength.value_or(positionInRecord + bytes)) {
406 auto at{recordOffsetInFrame_ + positionInRecord};
407 auto need{static_cast<std::size_t>(at + bytes)};
408 auto got{ReadFrame(frameOffsetInFile_, need, handler)};
409 SetVariableFormattedRecordLength();
410 if (got >= need) {
411 return Frame() + at;
413 HitEndOnRead(handler);
415 return nullptr;
418 bool ExternalFileUnit::SetVariableFormattedRecordLength() {
419 if (recordLength || access == Access::Direct) {
420 return true;
421 } else if (FrameLength() > recordOffsetInFrame_) {
422 const char *record{Frame() + recordOffsetInFrame_};
423 std::size_t bytes{FrameLength() - recordOffsetInFrame_};
424 if (const char *nl{FindCharacter(record, '\n', bytes)}) {
425 recordLength = nl - record;
426 if (*recordLength > 0 && record[*recordLength - 1] == '\r') {
427 --*recordLength;
429 return true;
432 return false;
435 bool ExternalFileUnit::BeginReadingRecord(IoErrorHandler &handler) {
436 RUNTIME_CHECK(handler, direction_ == Direction::Input);
437 if (!beganReadingRecord_) {
438 beganReadingRecord_ = true;
439 if (access == Access::Direct) {
440 CheckDirectAccess(handler);
441 auto need{static_cast<std::size_t>(recordOffsetInFrame_ + *openRecl)};
442 auto got{ReadFrame(frameOffsetInFile_, need, handler)};
443 if (got >= need) {
444 recordLength = openRecl;
445 } else {
446 recordLength.reset();
447 HitEndOnRead(handler);
449 } else {
450 recordLength.reset();
451 if (IsAtEOF()) {
452 handler.SignalEnd();
453 } else {
454 RUNTIME_CHECK(handler, isUnformatted.has_value());
455 if (*isUnformatted) {
456 if (access == Access::Sequential) {
457 BeginSequentialVariableUnformattedInputRecord(handler);
459 } else { // formatted sequential or stream
460 BeginVariableFormattedInputRecord(handler);
465 RUNTIME_CHECK(handler,
466 recordLength.has_value() || !IsRecordFile() || handler.InError());
467 return !handler.InError();
470 void ExternalFileUnit::FinishReadingRecord(IoErrorHandler &handler) {
471 RUNTIME_CHECK(handler, direction_ == Direction::Input && beganReadingRecord_);
472 beganReadingRecord_ = false;
473 if (handler.GetIoStat() == IostatEnd ||
474 (IsRecordFile() && !recordLength.has_value())) {
475 // Avoid bogus crashes in END/ERR circumstances; but
476 // still increment the current record number so that
477 // an attempted read of an endfile record, followed by
478 // a BACKSPACE, will still be at EOF.
479 ++currentRecordNumber;
480 } else if (IsRecordFile()) {
481 recordOffsetInFrame_ += *recordLength;
482 if (access != Access::Direct) {
483 RUNTIME_CHECK(handler, isUnformatted.has_value());
484 recordLength.reset();
485 if (isUnformatted.value_or(false)) {
486 // Retain footer in frame for more efficient BACKSPACE
487 frameOffsetInFile_ += recordOffsetInFrame_;
488 recordOffsetInFrame_ = sizeof(std::uint32_t);
489 } else { // formatted
490 if (FrameLength() > recordOffsetInFrame_ &&
491 Frame()[recordOffsetInFrame_] == '\r') {
492 ++recordOffsetInFrame_;
494 if (FrameLength() > recordOffsetInFrame_ &&
495 Frame()[recordOffsetInFrame_] == '\n') {
496 ++recordOffsetInFrame_;
498 if (!pinnedFrame || mayPosition()) {
499 frameOffsetInFile_ += recordOffsetInFrame_;
500 recordOffsetInFrame_ = 0;
504 ++currentRecordNumber;
505 } else { // unformatted stream
506 furthestPositionInRecord =
507 std::max(furthestPositionInRecord, positionInRecord);
508 frameOffsetInFile_ += recordOffsetInFrame_ + furthestPositionInRecord;
510 BeginRecord();
513 bool ExternalFileUnit::AdvanceRecord(IoErrorHandler &handler) {
514 if (direction_ == Direction::Input) {
515 FinishReadingRecord(handler);
516 return BeginReadingRecord(handler);
517 } else { // Direction::Output
518 bool ok{true};
519 RUNTIME_CHECK(handler, isUnformatted.has_value());
520 positionInRecord = furthestPositionInRecord;
521 if (access == Access::Direct) {
522 if (furthestPositionInRecord <
523 openRecl.value_or(furthestPositionInRecord)) {
524 // Pad remainder of fixed length record
525 WriteFrame(
526 frameOffsetInFile_, recordOffsetInFrame_ + *openRecl, handler);
527 std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord,
528 isUnformatted.value_or(false) ? 0 : ' ',
529 *openRecl - furthestPositionInRecord);
530 furthestPositionInRecord = *openRecl;
532 } else if (*isUnformatted) {
533 if (access == Access::Sequential) {
534 // Append the length of a sequential unformatted variable-length record
535 // as its footer, then overwrite the reserved first four bytes of the
536 // record with its length as its header. These four bytes were skipped
537 // over in BeginUnformattedIO<Output>().
538 // TODO: Break very large records up into subrecords with negative
539 // headers &/or footers
540 std::uint32_t length;
541 length = furthestPositionInRecord - sizeof length;
542 ok = ok &&
543 Emit(reinterpret_cast<const char *>(&length), sizeof length,
544 sizeof length, handler);
545 positionInRecord = 0;
546 ok = ok &&
547 Emit(reinterpret_cast<const char *>(&length), sizeof length,
548 sizeof length, handler);
549 } else {
550 // Unformatted stream: nothing to do
552 } else if (handler.GetIoStat() != IostatOk &&
553 furthestPositionInRecord == 0) {
554 // Error in formatted variable length record, and no output yet; do
555 // nothing, like most other Fortran compilers do.
556 return true;
557 } else {
558 // Terminate formatted variable length record
559 const char *lineEnding{"\n"};
560 std::size_t lineEndingBytes{1};
561 #ifdef _WIN32
562 if (!isWindowsTextFile()) {
563 lineEnding = "\r\n";
564 lineEndingBytes = 2;
566 #endif
567 ok = ok && Emit(lineEnding, lineEndingBytes, 1, handler);
569 leftTabLimit.reset();
570 if (IsAfterEndfile()) {
571 return false;
573 CommitWrites();
574 ++currentRecordNumber;
575 if (access != Access::Direct) {
576 impliedEndfile_ = IsRecordFile();
577 if (IsAtEOF()) {
578 endfileRecordNumber.reset();
581 return ok;
585 void ExternalFileUnit::BackspaceRecord(IoErrorHandler &handler) {
586 if (access == Access::Direct || !IsRecordFile()) {
587 handler.SignalError(IostatBackspaceNonSequential,
588 "BACKSPACE(UNIT=%d) on direct-access file or unformatted stream",
589 unitNumber());
590 } else {
591 if (IsAfterEndfile()) {
592 // BACKSPACE after explicit ENDFILE
593 currentRecordNumber = *endfileRecordNumber;
594 } else if (leftTabLimit) {
595 // BACKSPACE after non-advancing I/O
596 leftTabLimit.reset();
597 } else {
598 DoImpliedEndfile(handler);
599 if (frameOffsetInFile_ + recordOffsetInFrame_ > 0) {
600 --currentRecordNumber;
601 if (openRecl && access == Access::Direct) {
602 BackspaceFixedRecord(handler);
603 } else {
604 RUNTIME_CHECK(handler, isUnformatted.has_value());
605 if (isUnformatted.value_or(false)) {
606 BackspaceVariableUnformattedRecord(handler);
607 } else {
608 BackspaceVariableFormattedRecord(handler);
613 BeginRecord();
617 void ExternalFileUnit::FlushOutput(IoErrorHandler &handler) {
618 if (!mayPosition()) {
619 auto frameAt{FrameAt()};
620 if (frameOffsetInFile_ >= frameAt &&
621 frameOffsetInFile_ <
622 static_cast<std::int64_t>(frameAt + FrameLength())) {
623 // A Flush() that's about to happen to a non-positionable file
624 // needs to advance frameOffsetInFile_ to prevent attempts at
625 // impossible seeks
626 CommitWrites();
627 leftTabLimit.reset();
630 Flush(handler);
633 void ExternalFileUnit::FlushIfTerminal(IoErrorHandler &handler) {
634 if (isTerminal()) {
635 FlushOutput(handler);
639 void ExternalFileUnit::Endfile(IoErrorHandler &handler) {
640 if (access == Access::Direct) {
641 handler.SignalError(IostatEndfileDirect,
642 "ENDFILE(UNIT=%d) on direct-access file", unitNumber());
643 } else if (!mayWrite()) {
644 handler.SignalError(IostatEndfileUnwritable,
645 "ENDFILE(UNIT=%d) on read-only file", unitNumber());
646 } else if (IsAfterEndfile()) {
647 // ENDFILE after ENDFILE
648 } else {
649 DoEndfile(handler);
650 if (IsRecordFile() && access != Access::Direct) {
651 // Explicit ENDFILE leaves position *after* the endfile record
652 RUNTIME_CHECK(handler, endfileRecordNumber.has_value());
653 currentRecordNumber = *endfileRecordNumber + 1;
658 void ExternalFileUnit::Rewind(IoErrorHandler &handler) {
659 if (access == Access::Direct) {
660 handler.SignalError(IostatRewindNonSequential,
661 "REWIND(UNIT=%d) on non-sequential file", unitNumber());
662 } else {
663 SetPosition(0, handler);
664 currentRecordNumber = 1;
665 leftTabLimit.reset();
669 void ExternalFileUnit::SetPosition(std::int64_t pos, IoErrorHandler &handler) {
670 DoImpliedEndfile(handler);
671 frameOffsetInFile_ = pos;
672 recordOffsetInFrame_ = 0;
673 if (access == Access::Direct) {
674 directAccessRecWasSet_ = true;
676 BeginRecord();
679 bool ExternalFileUnit::SetStreamPos(
680 std::int64_t oneBasedPos, IoErrorHandler &handler) {
681 if (access != Access::Stream) {
682 handler.SignalError("POS= may not appear unless ACCESS='STREAM'");
683 return false;
685 if (oneBasedPos < 1) { // POS=1 is beginning of file (12.6.2.11)
686 handler.SignalError(
687 "POS=%zd is invalid", static_cast<std::intmax_t>(oneBasedPos));
688 return false;
690 SetPosition(oneBasedPos - 1, handler);
691 // We no longer know which record we're in. Set currentRecordNumber to
692 // a large value from whence we can both advance and backspace.
693 currentRecordNumber = std::numeric_limits<std::int64_t>::max() / 2;
694 endfileRecordNumber.reset();
695 return true;
698 bool ExternalFileUnit::SetDirectRec(
699 std::int64_t oneBasedRec, IoErrorHandler &handler) {
700 if (access != Access::Direct) {
701 handler.SignalError("REC= may not appear unless ACCESS='DIRECT'");
702 return false;
704 if (!openRecl) {
705 handler.SignalError("RECL= was not specified");
706 return false;
708 if (oneBasedRec < 1) {
709 handler.SignalError(
710 "REC=%zd is invalid", static_cast<std::intmax_t>(oneBasedRec));
711 return false;
713 currentRecordNumber = oneBasedRec;
714 SetPosition((oneBasedRec - 1) * *openRecl, handler);
715 return true;
718 void ExternalFileUnit::EndIoStatement() {
719 io_.reset();
720 u_.emplace<std::monostate>();
721 lock_.Drop();
724 void ExternalFileUnit::BeginSequentialVariableUnformattedInputRecord(
725 IoErrorHandler &handler) {
726 std::int32_t header{0}, footer{0};
727 std::size_t need{recordOffsetInFrame_ + sizeof header};
728 std::size_t got{ReadFrame(frameOffsetInFile_, need, handler)};
729 // Try to emit informative errors to help debug corrupted files.
730 const char *error{nullptr};
731 if (got < need) {
732 if (got == recordOffsetInFrame_) {
733 HitEndOnRead(handler);
734 } else {
735 error = "Unformatted variable-length sequential file input failed at "
736 "record #%jd (file offset %jd): truncated record header";
738 } else {
739 header = ReadHeaderOrFooter(recordOffsetInFrame_);
740 recordLength = sizeof header + header; // does not include footer
741 need = recordOffsetInFrame_ + *recordLength + sizeof footer;
742 got = ReadFrame(frameOffsetInFile_, need, handler);
743 if (got < need) {
744 error = "Unformatted variable-length sequential file input failed at "
745 "record #%jd (file offset %jd): hit EOF reading record with "
746 "length %jd bytes";
747 } else {
748 footer = ReadHeaderOrFooter(recordOffsetInFrame_ + *recordLength);
749 if (footer != header) {
750 error = "Unformatted variable-length sequential file input failed at "
751 "record #%jd (file offset %jd): record header has length %jd "
752 "that does not match record footer (%jd)";
756 if (error) {
757 handler.SignalError(error, static_cast<std::intmax_t>(currentRecordNumber),
758 static_cast<std::intmax_t>(frameOffsetInFile_),
759 static_cast<std::intmax_t>(header), static_cast<std::intmax_t>(footer));
760 // TODO: error recovery
762 positionInRecord = sizeof header;
765 void ExternalFileUnit::BeginVariableFormattedInputRecord(
766 IoErrorHandler &handler) {
767 if (this == defaultInput) {
768 if (defaultOutput) {
769 defaultOutput->FlushOutput(handler);
771 if (errorOutput) {
772 errorOutput->FlushOutput(handler);
775 std::size_t length{0};
776 do {
777 std::size_t need{length + 1};
778 length =
779 ReadFrame(frameOffsetInFile_, recordOffsetInFrame_ + need, handler) -
780 recordOffsetInFrame_;
781 if (length < need) {
782 if (length > 0) {
783 // final record w/o \n
784 recordLength = length;
785 unterminatedRecord = true;
786 } else {
787 HitEndOnRead(handler);
789 break;
791 } while (!SetVariableFormattedRecordLength());
794 void ExternalFileUnit::BackspaceFixedRecord(IoErrorHandler &handler) {
795 RUNTIME_CHECK(handler, openRecl.has_value());
796 if (frameOffsetInFile_ < *openRecl) {
797 handler.SignalError(IostatBackspaceAtFirstRecord);
798 } else {
799 frameOffsetInFile_ -= *openRecl;
803 void ExternalFileUnit::BackspaceVariableUnformattedRecord(
804 IoErrorHandler &handler) {
805 std::int32_t header{0};
806 auto headerBytes{static_cast<std::int64_t>(sizeof header)};
807 frameOffsetInFile_ += recordOffsetInFrame_;
808 recordOffsetInFrame_ = 0;
809 if (frameOffsetInFile_ <= headerBytes) {
810 handler.SignalError(IostatBackspaceAtFirstRecord);
811 return;
813 // Error conditions here cause crashes, not file format errors, because the
814 // validity of the file structure before the current record will have been
815 // checked informatively in NextSequentialVariableUnformattedInputRecord().
816 std::size_t got{
817 ReadFrame(frameOffsetInFile_ - headerBytes, headerBytes, handler)};
818 if (static_cast<std::int64_t>(got) < headerBytes) {
819 handler.SignalError(IostatShortRead);
820 return;
822 recordLength = ReadHeaderOrFooter(0);
823 if (frameOffsetInFile_ < *recordLength + 2 * headerBytes) {
824 handler.SignalError(IostatBadUnformattedRecord);
825 return;
827 frameOffsetInFile_ -= *recordLength + 2 * headerBytes;
828 if (frameOffsetInFile_ >= headerBytes) {
829 frameOffsetInFile_ -= headerBytes;
830 recordOffsetInFrame_ = 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