1 //===-- runtime/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 // Implementation of ExternalFileUnit common for both
10 // RT_USE_PSEUDO_FILE_UNIT=0 and RT_USE_PSEUDO_FILE_UNIT=1.
12 //===----------------------------------------------------------------------===//
20 namespace Fortran::runtime::io
{
22 #ifndef FLANG_RUNTIME_NO_GLOBAL_VAR_DEFS
23 RT_OFFLOAD_VAR_GROUP_BEGIN
24 RT_VAR_ATTRS ExternalFileUnit
*defaultInput
{nullptr}; // unit 5
25 RT_VAR_ATTRS ExternalFileUnit
*defaultOutput
{nullptr}; // unit 6
26 RT_VAR_ATTRS ExternalFileUnit
*errorOutput
{nullptr}; // unit 0 extension
27 RT_OFFLOAD_VAR_GROUP_END
28 #endif // FLANG_RUNTIME_NO_GLOBAL_VAR_DEFS
30 RT_OFFLOAD_API_GROUP_BEGIN
32 static inline RT_API_ATTRS
void SwapEndianness(
33 char *data
, std::size_t bytes
, std::size_t elementBytes
) {
34 if (elementBytes
> 1) {
35 auto half
{elementBytes
>> 1};
36 for (std::size_t j
{0}; j
+ elementBytes
<= bytes
; j
+= elementBytes
) {
37 for (std::size_t k
{0}; k
< half
; ++k
) {
39 RT_DIAG_DISABLE_CALL_HOST_FROM_DEVICE_WARN
40 std::swap(data
[j
+ k
], data
[j
+ elementBytes
- 1 - k
]);
47 bool ExternalFileUnit::Emit(const char *data
, std::size_t bytes
,
48 std::size_t elementBytes
, IoErrorHandler
&handler
) {
49 auto furthestAfter
{std::max(furthestPositionInRecord
,
50 positionInRecord
+ static_cast<std::int64_t>(bytes
))};
52 // Check for fixed-length record overrun, but allow for
53 // sequential record termination.
56 if (access
== Access::Sequential
) {
57 if (isUnformatted
.value_or(false)) {
58 // record header + footer
59 header
= static_cast<int>(sizeof(std::uint32_t));
63 if (!isWindowsTextFile()) {
64 ++extra
; // carriage return (CR)
67 ++extra
; // newline (LF)
70 if (furthestAfter
> extra
+ *openRecl
) {
71 handler
.SignalError(IostatRecordWriteOverrun
,
72 "Attempt to write %zd bytes to position %jd in a fixed-size record "
74 bytes
, static_cast<std::intmax_t>(positionInRecord
- header
),
75 static_cast<std::intmax_t>(*openRecl
));
80 // It is possible for recordLength to have a value now for a
81 // variable-length output record if the previous operation
82 // was a BACKSPACE or non advancing input statement.
84 beganReadingRecord_
= false;
86 if (IsAfterEndfile()) {
87 handler
.SignalError(IostatWriteAfterEndfile
);
90 CheckDirectAccess(handler
);
91 WriteFrame(frameOffsetInFile_
, recordOffsetInFrame_
+ furthestAfter
, handler
);
92 if (positionInRecord
> furthestPositionInRecord
) {
93 std::memset(Frame() + recordOffsetInFrame_
+ furthestPositionInRecord
, ' ',
94 positionInRecord
- furthestPositionInRecord
);
96 char *to
{Frame() + recordOffsetInFrame_
+ positionInRecord
};
97 std::memcpy(to
, data
, bytes
);
98 if (swapEndianness_
) {
99 SwapEndianness(to
, bytes
, elementBytes
);
101 positionInRecord
+= bytes
;
102 furthestPositionInRecord
= furthestAfter
;
103 anyWriteSinceLastPositioning_
= true;
107 bool ExternalFileUnit::Receive(char *data
, std::size_t bytes
,
108 std::size_t elementBytes
, IoErrorHandler
&handler
) {
109 RUNTIME_CHECK(handler
, direction_
== Direction::Input
);
110 auto furthestAfter
{std::max(furthestPositionInRecord
,
111 positionInRecord
+ static_cast<std::int64_t>(bytes
))};
112 if (furthestAfter
> recordLength
.value_or(furthestAfter
)) {
113 handler
.SignalError(IostatRecordReadOverrun
,
114 "Attempt to read %zd bytes at position %jd in a record of %jd bytes",
115 bytes
, static_cast<std::intmax_t>(positionInRecord
),
116 static_cast<std::intmax_t>(*recordLength
));
119 auto need
{recordOffsetInFrame_
+ furthestAfter
};
120 auto got
{ReadFrame(frameOffsetInFile_
, need
, handler
)};
122 std::memcpy(data
, Frame() + recordOffsetInFrame_
+ positionInRecord
, bytes
);
123 if (swapEndianness_
) {
124 SwapEndianness(data
, bytes
, elementBytes
);
126 positionInRecord
+= bytes
;
127 furthestPositionInRecord
= furthestAfter
;
130 HitEndOnRead(handler
);
135 std::size_t ExternalFileUnit::GetNextInputBytes(
136 const char *&p
, IoErrorHandler
&handler
) {
137 RUNTIME_CHECK(handler
, direction_
== Direction::Input
);
138 std::size_t length
{1};
139 if (auto recl
{EffectiveRecordLength()}) {
140 if (positionInRecord
< *recl
) {
141 length
= *recl
- positionInRecord
;
147 p
= FrameNextInput(handler
, length
);
148 return p
? length
: 0;
151 std::size_t ExternalFileUnit::ViewBytesInRecord(
152 const char *&p
, bool forward
) const {
154 auto recl
{recordLength
.value_or(positionInRecord
)};
156 if (positionInRecord
< recl
) {
157 p
= Frame() + recordOffsetInFrame_
+ positionInRecord
;
158 return recl
- positionInRecord
;
161 if (positionInRecord
<= recl
) {
162 p
= Frame() + recordOffsetInFrame_
+ positionInRecord
;
164 return positionInRecord
- leftTabLimit
.value_or(0);
169 const char *ExternalFileUnit::FrameNextInput(
170 IoErrorHandler
&handler
, std::size_t bytes
) {
171 RUNTIME_CHECK(handler
, isUnformatted
.has_value() && !*isUnformatted
);
172 if (static_cast<std::int64_t>(positionInRecord
+ bytes
) <=
173 recordLength
.value_or(positionInRecord
+ bytes
)) {
174 auto at
{recordOffsetInFrame_
+ positionInRecord
};
175 auto need
{static_cast<std::size_t>(at
+ bytes
)};
176 auto got
{ReadFrame(frameOffsetInFile_
, need
, handler
)};
177 SetVariableFormattedRecordLength();
181 HitEndOnRead(handler
);
186 bool ExternalFileUnit::SetVariableFormattedRecordLength() {
187 if (recordLength
|| access
== Access::Direct
) {
189 } else if (FrameLength() > recordOffsetInFrame_
) {
190 const char *record
{Frame() + recordOffsetInFrame_
};
191 std::size_t bytes
{FrameLength() - recordOffsetInFrame_
};
192 if (const char *nl
{FindCharacter(record
, '\n', bytes
)}) {
193 recordLength
= nl
- record
;
194 if (*recordLength
> 0 && record
[*recordLength
- 1] == '\r') {
203 bool ExternalFileUnit::BeginReadingRecord(IoErrorHandler
&handler
) {
204 RUNTIME_CHECK(handler
, direction_
== Direction::Input
);
205 if (!beganReadingRecord_
) {
206 beganReadingRecord_
= true;
207 // Don't use IsAtEOF() to check for an EOF condition here, just detect
208 // it from a failed or short read from the file. IsAtEOF() could be
209 // wrong for formatted input if actual newline characters had been
210 // written in-band by previous WRITEs before a REWIND. In fact,
211 // now that we know that the unit is being used for input (again),
212 // it's best to reset endfileRecordNumber and ensure IsAtEOF() will
213 // now be true on return only if it gets set by HitEndOnRead().
214 endfileRecordNumber
.reset();
215 if (access
== Access::Direct
) {
216 CheckDirectAccess(handler
);
217 auto need
{static_cast<std::size_t>(recordOffsetInFrame_
+ *openRecl
)};
218 auto got
{ReadFrame(frameOffsetInFile_
, need
, handler
)};
220 recordLength
= openRecl
;
222 recordLength
.reset();
223 HitEndOnRead(handler
);
226 if (anyWriteSinceLastPositioning_
&& access
== Access::Sequential
) {
227 // Most Fortran implementations allow a READ after a WRITE;
228 // the read then just hits an EOF.
229 DoEndfile
<false, Direction::Input
>(handler
);
231 recordLength
.reset();
232 RUNTIME_CHECK(handler
, isUnformatted
.has_value());
233 if (*isUnformatted
) {
234 if (access
== Access::Sequential
) {
235 BeginSequentialVariableUnformattedInputRecord(handler
);
237 } else { // formatted sequential or stream
238 BeginVariableFormattedInputRecord(handler
);
242 RUNTIME_CHECK(handler
,
243 recordLength
.has_value() || !IsRecordFile() || handler
.InError());
244 return !handler
.InError();
247 void ExternalFileUnit::FinishReadingRecord(IoErrorHandler
&handler
) {
248 RUNTIME_CHECK(handler
, direction_
== Direction::Input
&& beganReadingRecord_
);
249 beganReadingRecord_
= false;
250 if (handler
.GetIoStat() == IostatEnd
||
251 (IsRecordFile() && !recordLength
.has_value())) {
252 // Avoid bogus crashes in END/ERR circumstances; but
253 // still increment the current record number so that
254 // an attempted read of an endfile record, followed by
255 // a BACKSPACE, will still be at EOF.
256 ++currentRecordNumber
;
257 } else if (IsRecordFile()) {
258 recordOffsetInFrame_
+= *recordLength
;
259 if (access
!= Access::Direct
) {
260 RUNTIME_CHECK(handler
, isUnformatted
.has_value());
261 recordLength
.reset();
262 if (isUnformatted
.value_or(false)) {
263 // Retain footer in frame for more efficient BACKSPACE
264 frameOffsetInFile_
+= recordOffsetInFrame_
;
265 recordOffsetInFrame_
= sizeof(std::uint32_t);
266 } else { // formatted
267 if (FrameLength() > recordOffsetInFrame_
&&
268 Frame()[recordOffsetInFrame_
] == '\r') {
269 ++recordOffsetInFrame_
;
271 if (FrameLength() > recordOffsetInFrame_
&&
272 Frame()[recordOffsetInFrame_
] == '\n') {
273 ++recordOffsetInFrame_
;
275 if (!pinnedFrame
|| mayPosition()) {
276 frameOffsetInFile_
+= recordOffsetInFrame_
;
277 recordOffsetInFrame_
= 0;
281 ++currentRecordNumber
;
282 } else { // unformatted stream
283 furthestPositionInRecord
=
284 std::max(furthestPositionInRecord
, positionInRecord
);
285 frameOffsetInFile_
+= recordOffsetInFrame_
+ furthestPositionInRecord
;
286 recordOffsetInFrame_
= 0;
289 leftTabLimit
.reset();
292 bool ExternalFileUnit::AdvanceRecord(IoErrorHandler
&handler
) {
293 if (direction_
== Direction::Input
) {
294 FinishReadingRecord(handler
);
295 return BeginReadingRecord(handler
);
296 } else { // Direction::Output
298 RUNTIME_CHECK(handler
, isUnformatted
.has_value());
299 positionInRecord
= furthestPositionInRecord
;
300 if (access
== Access::Direct
) {
301 if (furthestPositionInRecord
<
302 openRecl
.value_or(furthestPositionInRecord
)) {
303 // Pad remainder of fixed length record
305 frameOffsetInFile_
, recordOffsetInFrame_
+ *openRecl
, handler
);
306 std::memset(Frame() + recordOffsetInFrame_
+ furthestPositionInRecord
,
307 isUnformatted
.value_or(false) ? 0 : ' ',
308 *openRecl
- furthestPositionInRecord
);
309 furthestPositionInRecord
= *openRecl
;
311 } else if (*isUnformatted
) {
312 if (access
== Access::Sequential
) {
313 // Append the length of a sequential unformatted variable-length record
314 // as its footer, then overwrite the reserved first four bytes of the
315 // record with its length as its header. These four bytes were skipped
316 // over in BeginUnformattedIO<Output>().
317 // TODO: Break very large records up into subrecords with negative
318 // headers &/or footers
319 std::uint32_t length
;
320 length
= furthestPositionInRecord
- sizeof length
;
322 Emit(reinterpret_cast<const char *>(&length
), sizeof length
,
323 sizeof length
, handler
);
324 positionInRecord
= 0;
326 Emit(reinterpret_cast<const char *>(&length
), sizeof length
,
327 sizeof length
, handler
);
329 // Unformatted stream: nothing to do
331 } else if (handler
.GetIoStat() != IostatOk
&&
332 furthestPositionInRecord
== 0) {
333 // Error in formatted variable length record, and no output yet; do
334 // nothing, like most other Fortran compilers do.
337 // Terminate formatted variable length record
338 const char *lineEnding
{"\n"};
339 std::size_t lineEndingBytes
{1};
341 if (!isWindowsTextFile()) {
346 ok
= ok
&& Emit(lineEnding
, lineEndingBytes
, 1, handler
);
348 leftTabLimit
.reset();
349 if (IsAfterEndfile()) {
353 ++currentRecordNumber
;
354 if (access
!= Access::Direct
) {
355 impliedEndfile_
= IsRecordFile();
357 endfileRecordNumber
.reset();
364 void ExternalFileUnit::BackspaceRecord(IoErrorHandler
&handler
) {
365 if (access
== Access::Direct
|| !IsRecordFile()) {
366 handler
.SignalError(IostatBackspaceNonSequential
,
367 "BACKSPACE(UNIT=%d) on direct-access file or unformatted stream",
370 if (IsAfterEndfile()) {
371 // BACKSPACE after explicit ENDFILE
372 currentRecordNumber
= *endfileRecordNumber
;
373 } else if (leftTabLimit
&& direction_
== Direction::Input
) {
374 // BACKSPACE after non-advancing input
375 leftTabLimit
.reset();
377 DoImpliedEndfile(handler
);
378 if (frameOffsetInFile_
+ recordOffsetInFrame_
> 0) {
379 --currentRecordNumber
;
380 if (openRecl
&& access
== Access::Direct
) {
381 BackspaceFixedRecord(handler
);
383 RUNTIME_CHECK(handler
, isUnformatted
.has_value());
384 if (isUnformatted
.value_or(false)) {
385 BackspaceVariableUnformattedRecord(handler
);
387 BackspaceVariableFormattedRecord(handler
);
393 anyWriteSinceLastPositioning_
= false;
397 void ExternalFileUnit::FlushOutput(IoErrorHandler
&handler
) {
398 if (!mayPosition()) {
399 auto frameAt
{FrameAt()};
400 if (frameOffsetInFile_
>= frameAt
&&
402 static_cast<std::int64_t>(frameAt
+ FrameLength())) {
403 // A Flush() that's about to happen to a non-positionable file
404 // needs to advance frameOffsetInFile_ to prevent attempts at
407 leftTabLimit
.reset();
413 void ExternalFileUnit::FlushIfTerminal(IoErrorHandler
&handler
) {
415 FlushOutput(handler
);
419 void ExternalFileUnit::Endfile(IoErrorHandler
&handler
) {
420 if (access
== Access::Direct
) {
421 handler
.SignalError(IostatEndfileDirect
,
422 "ENDFILE(UNIT=%d) on direct-access file", unitNumber());
423 } else if (!mayWrite()) {
424 handler
.SignalError(IostatEndfileUnwritable
,
425 "ENDFILE(UNIT=%d) on read-only file", unitNumber());
426 } else if (IsAfterEndfile()) {
427 // ENDFILE after ENDFILE
430 if (IsRecordFile() && access
!= Access::Direct
) {
431 // Explicit ENDFILE leaves position *after* the endfile record
432 RUNTIME_CHECK(handler
, endfileRecordNumber
.has_value());
433 currentRecordNumber
= *endfileRecordNumber
+ 1;
438 void ExternalFileUnit::Rewind(IoErrorHandler
&handler
) {
439 if (access
== Access::Direct
) {
440 handler
.SignalError(IostatRewindNonSequential
,
441 "REWIND(UNIT=%d) on non-sequential file", unitNumber());
443 DoImpliedEndfile(handler
);
444 SetPosition(0, handler
);
445 currentRecordNumber
= 1;
446 leftTabLimit
.reset();
447 anyWriteSinceLastPositioning_
= false;
451 void ExternalFileUnit::SetPosition(std::int64_t pos
, IoErrorHandler
&handler
) {
452 frameOffsetInFile_
= pos
;
453 recordOffsetInFrame_
= 0;
454 if (access
== Access::Direct
) {
455 directAccessRecWasSet_
= true;
460 bool ExternalFileUnit::SetStreamPos(
461 std::int64_t oneBasedPos
, IoErrorHandler
&handler
) {
462 if (access
!= Access::Stream
) {
463 handler
.SignalError("POS= may not appear unless ACCESS='STREAM'");
466 if (oneBasedPos
< 1) { // POS=1 is beginning of file (12.6.2.11)
468 "POS=%zd is invalid", static_cast<std::intmax_t>(oneBasedPos
));
471 // A backwards POS= implies truncation after writing, at least in
473 if (static_cast<std::size_t>(oneBasedPos
- 1) <
474 frameOffsetInFile_
+ recordOffsetInFrame_
) {
475 DoImpliedEndfile(handler
);
477 SetPosition(oneBasedPos
- 1, handler
);
478 // We no longer know which record we're in. Set currentRecordNumber to
479 // a large value from whence we can both advance and backspace.
480 currentRecordNumber
= std::numeric_limits
<std::int64_t>::max() / 2;
481 endfileRecordNumber
.reset();
485 bool ExternalFileUnit::SetDirectRec(
486 std::int64_t oneBasedRec
, IoErrorHandler
&handler
) {
487 if (access
!= Access::Direct
) {
488 handler
.SignalError("REC= may not appear unless ACCESS='DIRECT'");
492 handler
.SignalError("RECL= was not specified");
495 if (oneBasedRec
< 1) {
497 "REC=%zd is invalid", static_cast<std::intmax_t>(oneBasedRec
));
500 currentRecordNumber
= oneBasedRec
;
501 SetPosition((oneBasedRec
- 1) * *openRecl
, handler
);
505 void ExternalFileUnit::EndIoStatement() {
507 u_
.emplace
<std::monostate
>();
511 void ExternalFileUnit::BeginSequentialVariableUnformattedInputRecord(
512 IoErrorHandler
&handler
) {
513 RUNTIME_CHECK(handler
, access
== Access::Sequential
);
514 std::int32_t header
{0}, footer
{0};
515 std::size_t need
{recordOffsetInFrame_
+ sizeof header
};
516 std::size_t got
{ReadFrame(frameOffsetInFile_
, need
, handler
)};
517 // Try to emit informative errors to help debug corrupted files.
518 const char *error
{nullptr};
520 if (got
== recordOffsetInFrame_
) {
521 HitEndOnRead(handler
);
523 error
= "Unformatted variable-length sequential file input failed at "
524 "record #%jd (file offset %jd): truncated record header";
527 header
= ReadHeaderOrFooter(recordOffsetInFrame_
);
528 recordLength
= sizeof header
+ header
; // does not include footer
529 need
= recordOffsetInFrame_
+ *recordLength
+ sizeof footer
;
530 got
= ReadFrame(frameOffsetInFile_
, need
, handler
);
532 error
= "Unformatted variable-length sequential file input failed at "
533 "record #%jd (file offset %jd): hit EOF reading record with "
536 footer
= ReadHeaderOrFooter(recordOffsetInFrame_
+ *recordLength
);
537 if (footer
!= header
) {
538 error
= "Unformatted variable-length sequential file input failed at "
539 "record #%jd (file offset %jd): record header has length %jd "
540 "that does not match record footer (%jd)";
545 handler
.SignalError(error
, static_cast<std::intmax_t>(currentRecordNumber
),
546 static_cast<std::intmax_t>(frameOffsetInFile_
),
547 static_cast<std::intmax_t>(header
), static_cast<std::intmax_t>(footer
));
548 // TODO: error recovery
550 positionInRecord
= sizeof header
;
553 void ExternalFileUnit::BeginVariableFormattedInputRecord(
554 IoErrorHandler
&handler
) {
555 if (this == defaultInput
) {
557 defaultOutput
->FlushOutput(handler
);
560 errorOutput
->FlushOutput(handler
);
563 std::size_t length
{0};
565 std::size_t need
{length
+ 1};
567 ReadFrame(frameOffsetInFile_
, recordOffsetInFrame_
+ need
, handler
) -
568 recordOffsetInFrame_
;
571 // final record w/o \n
572 recordLength
= length
;
573 unterminatedRecord
= true;
575 HitEndOnRead(handler
);
579 } while (!SetVariableFormattedRecordLength());
582 void ExternalFileUnit::BackspaceFixedRecord(IoErrorHandler
&handler
) {
583 RUNTIME_CHECK(handler
, openRecl
.has_value());
584 if (frameOffsetInFile_
< *openRecl
) {
585 handler
.SignalError(IostatBackspaceAtFirstRecord
);
587 frameOffsetInFile_
-= *openRecl
;
591 void ExternalFileUnit::BackspaceVariableUnformattedRecord(
592 IoErrorHandler
&handler
) {
593 std::int32_t header
{0};
594 auto headerBytes
{static_cast<std::int64_t>(sizeof header
)};
595 frameOffsetInFile_
+= recordOffsetInFrame_
;
596 recordOffsetInFrame_
= 0;
597 if (frameOffsetInFile_
<= headerBytes
) {
598 handler
.SignalError(IostatBackspaceAtFirstRecord
);
601 // Error conditions here cause crashes, not file format errors, because the
602 // validity of the file structure before the current record will have been
603 // checked informatively in NextSequentialVariableUnformattedInputRecord().
605 ReadFrame(frameOffsetInFile_
- headerBytes
, headerBytes
, handler
)};
606 if (static_cast<std::int64_t>(got
) < headerBytes
) {
607 handler
.SignalError(IostatShortRead
);
610 recordLength
= ReadHeaderOrFooter(0);
611 if (frameOffsetInFile_
< *recordLength
+ 2 * headerBytes
) {
612 handler
.SignalError(IostatBadUnformattedRecord
);
615 frameOffsetInFile_
-= *recordLength
+ 2 * headerBytes
;
616 auto need
{static_cast<std::size_t>(
617 recordOffsetInFrame_
+ sizeof header
+ *recordLength
)};
618 got
= ReadFrame(frameOffsetInFile_
, need
, handler
);
620 handler
.SignalError(IostatShortRead
);
623 header
= ReadHeaderOrFooter(recordOffsetInFrame_
);
624 if (header
!= *recordLength
) {
625 handler
.SignalError(IostatBadUnformattedRecord
);
630 // There's no portable memrchr(), unfortunately, and strrchr() would
631 // fail on a record with a NUL, so we have to do it the hard way.
632 static RT_API_ATTRS
const char *FindLastNewline(
633 const char *str
, std::size_t length
) {
634 for (const char *p
{str
+ length
}; p
>= str
; p
--) {
642 void ExternalFileUnit::BackspaceVariableFormattedRecord(
643 IoErrorHandler
&handler
) {
644 // File offset of previous record's newline
646 frameOffsetInFile_
+ static_cast<std::int64_t>(recordOffsetInFrame_
) - 1};
648 handler
.SignalError(IostatBackspaceAtFirstRecord
);
652 if (frameOffsetInFile_
< prevNL
) {
654 FindLastNewline(Frame(), prevNL
- 1 - frameOffsetInFile_
)}) {
655 recordOffsetInFrame_
= p
- Frame() + 1;
656 recordLength
= prevNL
- (frameOffsetInFile_
+ recordOffsetInFrame_
);
660 if (frameOffsetInFile_
== 0) {
661 recordOffsetInFrame_
= 0;
662 recordLength
= prevNL
;
665 frameOffsetInFile_
-= std::min
<std::int64_t>(frameOffsetInFile_
, 1024);
666 auto need
{static_cast<std::size_t>(prevNL
+ 1 - frameOffsetInFile_
)};
667 auto got
{ReadFrame(frameOffsetInFile_
, need
, handler
)};
669 handler
.SignalError(IostatShortRead
);
673 if (Frame()[recordOffsetInFrame_
+ *recordLength
] != '\n') {
674 handler
.SignalError(IostatMissingTerminator
);
677 if (*recordLength
> 0 &&
678 Frame()[recordOffsetInFrame_
+ *recordLength
- 1] == '\r') {
683 void ExternalFileUnit::DoImpliedEndfile(IoErrorHandler
&handler
) {
684 if (access
!= Access::Direct
) {
685 if (!impliedEndfile_
&& leftTabLimit
&& direction_
== Direction::Output
) {
686 // Flush a partial record after non-advancing output
687 impliedEndfile_
= true;
689 if (impliedEndfile_
&& mayPosition()) {
693 impliedEndfile_
= false;
696 template <bool ANY_DIR
, Direction
DIR>
697 void ExternalFileUnit::DoEndfile(IoErrorHandler
&handler
) {
698 if (IsRecordFile() && access
!= Access::Direct
) {
699 furthestPositionInRecord
=
700 std::max(positionInRecord
, furthestPositionInRecord
);
701 if (leftTabLimit
) { // last I/O was non-advancing
702 if (access
== Access::Sequential
&& direction_
== Direction::Output
) {
703 if constexpr (ANY_DIR
|| DIR == Direction::Output
) {
704 // When DoEndfile() is called from BeginReadingRecord(),
705 // this call to AdvanceRecord() may appear as a recursion
706 // though it may never happen. Expose the call only
707 // under the constexpr direction check.
708 AdvanceRecord(handler
);
710 // This check always fails if we are here.
711 RUNTIME_CHECK(handler
, direction_
!= Direction::Output
);
713 } else { // Access::Stream or input
714 leftTabLimit
.reset();
715 ++currentRecordNumber
;
718 endfileRecordNumber
= currentRecordNumber
;
720 frameOffsetInFile_
+= recordOffsetInFrame_
+ furthestPositionInRecord
;
721 recordOffsetInFrame_
= 0;
722 FlushOutput(handler
);
723 Truncate(frameOffsetInFile_
, handler
);
724 TruncateFrame(frameOffsetInFile_
, handler
);
726 impliedEndfile_
= false;
727 anyWriteSinceLastPositioning_
= false;
730 template void ExternalFileUnit::DoEndfile(IoErrorHandler
&handler
);
731 template void ExternalFileUnit::DoEndfile
<false, Direction::Output
>(
732 IoErrorHandler
&handler
);
733 template void ExternalFileUnit::DoEndfile
<false, Direction::Input
>(
734 IoErrorHandler
&handler
);
736 void ExternalFileUnit::CommitWrites() {
737 frameOffsetInFile_
+=
738 recordOffsetInFrame_
+ recordLength
.value_or(furthestPositionInRecord
);
739 recordOffsetInFrame_
= 0;
743 bool ExternalFileUnit::CheckDirectAccess(IoErrorHandler
&handler
) {
744 if (access
== Access::Direct
) {
745 RUNTIME_CHECK(handler
, openRecl
);
746 if (!directAccessRecWasSet_
) {
748 "No REC= was specified for a data transfer with ACCESS='DIRECT'");
755 void ExternalFileUnit::HitEndOnRead(IoErrorHandler
&handler
) {
757 if (IsRecordFile() && access
!= Access::Direct
) {
758 endfileRecordNumber
= currentRecordNumber
;
762 ChildIo
&ExternalFileUnit::PushChildIo(IoStatementState
&parent
) {
763 OwningPtr
<ChildIo
> current
{std::move(child_
)};
764 Terminator
&terminator
{parent
.GetIoErrorHandler()};
765 OwningPtr
<ChildIo
> next
{New
<ChildIo
>{terminator
}(parent
, std::move(current
))};
766 child_
.reset(next
.release());
770 void ExternalFileUnit::PopChildIo(ChildIo
&child
) {
771 if (child_
.get() != &child
) {
772 child
.parent().GetIoErrorHandler().Crash(
773 "ChildIo being popped is not top of stack");
775 child_
.reset(child
.AcquirePrevious().release()); // deletes top child
778 std::int32_t ExternalFileUnit::ReadHeaderOrFooter(std::int64_t frameOffset
) {
780 char *wordPtr
{reinterpret_cast<char *>(&word
)};
781 std::memcpy(wordPtr
, Frame() + frameOffset
, sizeof word
);
782 if (swapEndianness_
) {
783 SwapEndianness(wordPtr
, sizeof word
, sizeof word
);
788 void ChildIo::EndIoStatement() {
790 u_
.emplace
<std::monostate
>();
793 Iostat
ChildIo::CheckFormattingAndDirection(
794 bool unformatted
, Direction direction
) {
795 bool parentIsInput
{!parent_
.get_if
<IoDirectionState
<Direction::Output
>>()};
796 bool parentIsFormatted
{parentIsInput
797 ? parent_
.get_if
<FormattedIoStatementState
<Direction::Input
>>() !=
799 : parent_
.get_if
<FormattedIoStatementState
<Direction::Output
>>() !=
801 bool parentIsUnformatted
{!parentIsFormatted
};
802 if (unformatted
!= parentIsUnformatted
) {
803 return unformatted
? IostatUnformattedChildOnFormattedParent
804 : IostatFormattedChildOnUnformattedParent
;
805 } else if (parentIsInput
!= (direction
== Direction::Input
)) {
806 return parentIsInput
? IostatChildOutputToInputParent
807 : IostatChildInputFromOutputParent
;
813 RT_OFFLOAD_API_GROUP_END
814 } // namespace Fortran::runtime::io