Skip to content

Commit 991696c

Browse files
committed
[flang] Debugging of ACCESS='STREAM' I/O (take 2)
Corrects the runtime implementation of I/O on files with the access mode ACCESS='STREAM'. This is a collection of edge-case tweaks to ensure that the distinctions between stream and direct/sequential files, unformatted or formatted, are respected where appropriate. Moves NextInField() from io-stmt.h to io-stmt.cpp -- it was getting too big to keep in a header. This patch exposed a problem with the I/O runtime on Windows and it was reverted. This version also fixes that problem; files are now opened on Windows in binary mode to prevent inadvertent insertions of carriage returns before line feeds, and those line endings (CR+LF) are now explicitly generated. Differential Revision: https://reviews.llvm.org/D119015
1 parent eddf384 commit 991696c

File tree

12 files changed

+221
-153
lines changed

12 files changed

+221
-153
lines changed

flang/include/flang/Runtime/iostat.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ enum Iostat {
4545
IostatInternalWriteOverrun,
4646
IostatErrorInFormat,
4747
IostatErrorInKeyword,
48-
IostatEndfileNonSequential,
48+
IostatEndfileDirect,
4949
IostatEndfileUnwritable,
5050
IostatOpenBadRecl,
5151
IostatOpenUnknownSize,

flang/runtime/connection.h

+5-2
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,18 @@ class IoStatementState;
2222
enum class Direction { Output, Input };
2323
enum class Access { Sequential, Direct, Stream };
2424

25-
inline bool IsRecordFile(Access a) { return a != Access::Stream; }
26-
2725
// These characteristics of a connection are immutable after being
2826
// established in an OPEN statement.
2927
struct ConnectionAttributes {
3028
Access access{Access::Sequential}; // ACCESS='SEQUENTIAL', 'DIRECT', 'STREAM'
3129
std::optional<bool> isUnformatted; // FORM='UNFORMATTED' if true
3230
bool isUTF8{false}; // ENCODING='UTF-8'
3331
std::optional<std::int64_t> openRecl; // RECL= on OPEN
32+
33+
bool IsRecordFile() const {
34+
// Formatted stream files are viewed as having records, at least on input
35+
return access != Access::Stream || !isUnformatted.value_or(true);
36+
}
3437
};
3538

3639
struct ConnectionState : public ConnectionAttributes {

flang/runtime/edit-input.cpp

+17-19
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ static bool EditBOZInput(IoStatementState &io, const DataEdit &edit, void *n,
1919
std::optional<int> remaining;
2020
std::optional<char32_t> next{io.PrepareInput(edit, remaining)};
2121
common::UnsignedInt128 value{0};
22-
for (; next; next = io.NextInField(remaining)) {
22+
for (; next; next = io.NextInField(remaining, edit)) {
2323
char32_t ch{*next};
2424
if (ch == ' ' || ch == '\t') {
2525
continue;
@@ -63,7 +63,7 @@ static bool ScanNumericPrefix(IoStatementState &io, const DataEdit &edit,
6363
if (negative || *next == '+') {
6464
io.GotChar();
6565
io.SkipSpaces(remaining);
66-
next = io.NextInField(remaining, GetDecimalPoint(edit));
66+
next = io.NextInField(remaining, edit);
6767
}
6868
}
6969
return negative;
@@ -101,7 +101,7 @@ bool EditIntegerInput(
101101
bool negate{ScanNumericPrefix(io, edit, next, remaining)};
102102
common::UnsignedInt128 value{0};
103103
bool any{negate};
104-
for (; next; next = io.NextInField(remaining)) {
104+
for (; next; next = io.NextInField(remaining, edit)) {
105105
char32_t ch{*next};
106106
if (ch == ' ' || ch == '\t') {
107107
if (edit.modes.editingFlags & blankZero) {
@@ -167,7 +167,7 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
167167
// Subtle: a blank field of digits could be followed by 'E' or 'D',
168168
for (; next &&
169169
((*next >= 'a' && *next <= 'z') || (*next >= 'A' && *next <= 'Z'));
170-
next = io.NextInField(remaining)) {
170+
next = io.NextInField(remaining, edit)) {
171171
if (*next >= 'a' && *next <= 'z') {
172172
Put(*next - 'a' + 'A');
173173
} else {
@@ -176,7 +176,7 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
176176
}
177177
if (next && *next == '(') { // NaN(...)
178178
while (next && *next != ')') {
179-
next = io.NextInField(remaining);
179+
next = io.NextInField(remaining, edit);
180180
}
181181
}
182182
exponent = 0;
@@ -185,7 +185,7 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
185185
Put('.'); // input field is normalized to a fraction
186186
auto start{got};
187187
bool bzMode{(edit.modes.editingFlags & blankZero) != 0};
188-
for (; next; next = io.NextInField(remaining, decimal)) {
188+
for (; next; next = io.NextInField(remaining, edit)) {
189189
char32_t ch{*next};
190190
if (ch == ' ' || ch == '\t') {
191191
if (bzMode) {
@@ -214,7 +214,7 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
214214
// Optional exponent letter. Blanks are allowed between the
215215
// optional exponent letter and the exponent value.
216216
io.SkipSpaces(remaining);
217-
next = io.NextInField(remaining);
217+
next = io.NextInField(remaining, edit);
218218
}
219219
// The default exponent is -kP, but the scale factor doesn't affect
220220
// an explicit exponent.
@@ -224,9 +224,9 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
224224
(bzMode && (*next == ' ' || *next == '\t')))) {
225225
bool negExpo{*next == '-'};
226226
if (negExpo || *next == '+') {
227-
next = io.NextInField(remaining);
227+
next = io.NextInField(remaining, edit);
228228
}
229-
for (exponent = 0; next; next = io.NextInField(remaining)) {
229+
for (exponent = 0; next; next = io.NextInField(remaining, edit)) {
230230
if (*next >= '0' && *next <= '9') {
231231
exponent = 10 * exponent + *next - '0';
232232
} else if (bzMode && (*next == ' ' || *next == '\t')) {
@@ -257,7 +257,7 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
257257
// input value.
258258
if (edit.descriptor == DataEdit::ListDirectedImaginaryPart) {
259259
if (next && (*next == ' ' || *next == '\t')) {
260-
next = io.NextInField(remaining);
260+
next = io.NextInField(remaining, edit);
261261
}
262262
if (!next) { // NextInField fails on separators like ')'
263263
next = io.GetCurrentChar();
@@ -267,7 +267,7 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
267267
}
268268
} else if (remaining) {
269269
while (next && (*next == ' ' || *next == '\t')) {
270-
next = io.NextInField(remaining);
270+
next = io.NextInField(remaining, edit);
271271
}
272272
if (next) {
273273
return 0; // error: unused nonblank character in fixed-width field
@@ -457,7 +457,7 @@ bool EditLogicalInput(IoStatementState &io, const DataEdit &edit, bool &x) {
457457
std::optional<int> remaining;
458458
std::optional<char32_t> next{io.PrepareInput(edit, remaining)};
459459
if (next && *next == '.') { // skip optional period
460-
next = io.NextInField(remaining);
460+
next = io.NextInField(remaining, edit);
461461
}
462462
if (!next) {
463463
io.GetIoErrorHandler().SignalError("Empty LOGICAL input field");
@@ -480,7 +480,7 @@ bool EditLogicalInput(IoStatementState &io, const DataEdit &edit, bool &x) {
480480
if (remaining) { // ignore the rest of the field
481481
io.HandleRelativePosition(*remaining);
482482
} else if (edit.descriptor == DataEdit::ListDirected) {
483-
while (io.NextInField(remaining)) { // discard rest of field
483+
while (io.NextInField(remaining, edit)) { // discard rest of field
484484
}
485485
}
486486
return true;
@@ -520,7 +520,7 @@ static bool EditDelimitedCharacterInput(
520520
}
521521

522522
static bool EditListDirectedDefaultCharacterInput(
523-
IoStatementState &io, char *x, std::size_t length) {
523+
IoStatementState &io, char *x, std::size_t length, const DataEdit &edit) {
524524
auto ch{io.GetCurrentChar()};
525525
if (ch && (*ch == '\'' || *ch == '"')) {
526526
io.HandleRelativePosition(1);
@@ -532,8 +532,7 @@ static bool EditListDirectedDefaultCharacterInput(
532532
// Undelimited list-directed character input: stop at a value separator
533533
// or the end of the current record.
534534
std::optional<int> remaining{length};
535-
for (std::optional<char32_t> next{io.NextInField(remaining)}; next;
536-
next = io.NextInField(remaining)) {
535+
while (std::optional<char32_t> next{io.NextInField(remaining, edit)}) {
537536
switch (*next) {
538537
case ' ':
539538
case '\t':
@@ -555,7 +554,7 @@ bool EditDefaultCharacterInput(
555554
IoStatementState &io, const DataEdit &edit, char *x, std::size_t length) {
556555
switch (edit.descriptor) {
557556
case DataEdit::ListDirected:
558-
return EditListDirectedDefaultCharacterInput(io, x, length);
557+
return EditListDirectedDefaultCharacterInput(io, x, length, edit);
559558
case 'A':
560559
case 'G':
561560
break;
@@ -576,8 +575,7 @@ bool EditDefaultCharacterInput(
576575
// characters. When the variable is wider than the field, there's
577576
// trailing padding.
578577
std::int64_t skip{*remaining - static_cast<std::int64_t>(length)};
579-
for (std::optional<char32_t> next{io.NextInField(remaining)}; next;
580-
next = io.NextInField(remaining)) {
578+
while (std::optional<char32_t> next{io.NextInField(remaining, edit)}) {
581579
if (skip > 0) {
582580
--skip;
583581
io.GotChar(-1);

flang/runtime/file.cpp

+11-2
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ static int openfile_mkstemp(IoErrorHandler &handler) {
4545
if (::GetTempFileNameA(tempDirName, "Fortran", uUnique, tempFileName) == 0) {
4646
return -1;
4747
}
48-
int fd{::_open(
49-
tempFileName, _O_CREAT | _O_TEMPORARY | _O_RDWR, _S_IREAD | _S_IWRITE)};
48+
int fd{::_open(tempFileName, _O_CREAT | _O_BINARY | _O_TEMPORARY | _O_RDWR,
49+
_S_IREAD | _S_IWRITE)};
5050
#else
5151
char path[]{"/tmp/Fortran-Scratch-XXXXXX"};
5252
int fd{::mkstemp(path)};
@@ -82,6 +82,12 @@ void OpenFile::Open(OpenStatus status, std::optional<Action> action,
8282
return;
8383
}
8484
int flags{0};
85+
#ifdef _WIN32
86+
// We emit explicit CR+LF line endings and cope with them on input
87+
// for formatted files, since we can't yet always know now at OPEN
88+
// time whether the file is formatted or not.
89+
flags |= O_BINARY;
90+
#endif
8591
if (status != OpenStatus::Old) {
8692
flags |= O_CREAT;
8793
}
@@ -154,6 +160,9 @@ void OpenFile::Predefine(int fd) {
154160
mayRead_ = fd == 0;
155161
mayWrite_ = fd != 0;
156162
mayPosition_ = false;
163+
#ifdef _WIN32
164+
isWindowsTextFile_ = true;
165+
#endif
157166
}
158167

159168
void OpenFile::Close(CloseStatus status, IoErrorHandler &handler) {

flang/runtime/file.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ class OpenFile {
3535
bool mayPosition() const { return mayPosition_; }
3636
bool mayAsynchronous() const { return mayAsynchronous_; }
3737
void set_mayAsynchronous(bool yes) { mayAsynchronous_ = yes; }
38-
FileOffset position() const { return position_; }
3938
bool isTerminal() const { return isTerminal_; }
39+
bool isWindowsTextFile() const { return isWindowsTextFile_; }
4040
std::optional<FileOffset> knownSize() const { return knownSize_; }
4141

4242
bool IsConnected() const { return fd_ >= 0; }
@@ -98,6 +98,7 @@ class OpenFile {
9898
FileOffset position_{0};
9999
std::optional<FileOffset> knownSize_;
100100
bool isTerminal_{false};
101+
bool isWindowsTextFile_{false}; // expands LF to CR+LF on write
101102

102103
int nextId_;
103104
OwningPtr<Pending> pending_;

flang/runtime/io-api.cpp

+10-12
Original file line numberDiff line numberDiff line change
@@ -524,18 +524,17 @@ bool IONAME(SetPad)(Cookie cookie, const char *keyword, std::size_t length) {
524524
bool IONAME(SetPos)(Cookie cookie, std::int64_t pos) {
525525
IoStatementState &io{*cookie};
526526
ConnectionState &connection{io.GetConnectionState()};
527+
IoErrorHandler &handler{io.GetIoErrorHandler()};
527528
if (connection.access != Access::Stream) {
528-
io.GetIoErrorHandler().SignalError(
529-
"POS= may not appear unless ACCESS='STREAM'");
529+
handler.SignalError("POS= may not appear unless ACCESS='STREAM'");
530530
return false;
531531
}
532-
if (pos < 1) {
533-
io.GetIoErrorHandler().SignalError(
534-
"POS=%zd is invalid", static_cast<std::intmax_t>(pos));
532+
if (pos < 1) { // POS=1 is beginning of file (12.6.2.11)
533+
handler.SignalError("POS=%zd is invalid", static_cast<std::intmax_t>(pos));
535534
return false;
536535
}
537536
if (auto *unit{io.GetExternalFileUnit()}) {
538-
unit->SetPosition(pos);
537+
unit->SetPosition(pos - 1, handler);
539538
return true;
540539
}
541540
io.GetIoErrorHandler().Crash("SetPos() on internal unit");
@@ -545,23 +544,22 @@ bool IONAME(SetPos)(Cookie cookie, std::int64_t pos) {
545544
bool IONAME(SetRec)(Cookie cookie, std::int64_t rec) {
546545
IoStatementState &io{*cookie};
547546
ConnectionState &connection{io.GetConnectionState()};
547+
IoErrorHandler &handler{io.GetIoErrorHandler()};
548548
if (connection.access != Access::Direct) {
549-
io.GetIoErrorHandler().SignalError(
550-
"REC= may not appear unless ACCESS='DIRECT'");
549+
handler.SignalError("REC= may not appear unless ACCESS='DIRECT'");
551550
return false;
552551
}
553552
if (!connection.openRecl) {
554-
io.GetIoErrorHandler().SignalError("RECL= was not specified");
553+
handler.SignalError("RECL= was not specified");
555554
return false;
556555
}
557556
if (rec < 1) {
558-
io.GetIoErrorHandler().SignalError(
559-
"REC=%zd is invalid", static_cast<std::intmax_t>(rec));
557+
handler.SignalError("REC=%zd is invalid", static_cast<std::intmax_t>(rec));
560558
return false;
561559
}
562560
connection.currentRecordNumber = rec;
563561
if (auto *unit{io.GetExternalFileUnit()}) {
564-
unit->SetPosition((rec - 1) * *connection.openRecl);
562+
unit->SetPosition((rec - 1) * *connection.openRecl, handler);
565563
}
566564
return true;
567565
}

flang/runtime/io-stmt.cpp

+62-2
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ ExternalIoStatementState<DIR>::ExternalIoStatementState(
268268
: ExternalIoStatementBase{unit, sourceFile, sourceLine}, mutableModes_{
269269
unit.modes} {
270270
if constexpr (DIR == Direction::Output) {
271-
// If the last statement was a non advancing IO input statement, the unit
271+
// If the last statement was a non-advancing IO input statement, the unit
272272
// furthestPositionInRecord was not advanced, but the positionInRecord may
273273
// have been advanced. Advance furthestPositionInRecord here to avoid
274274
// overwriting the part of the record that has been read with blanks.
@@ -505,6 +505,66 @@ bool IoStatementState::EmitField(
505505
}
506506
}
507507

508+
std::optional<char32_t> IoStatementState::NextInField(
509+
std::optional<int> &remaining, const DataEdit &edit) {
510+
if (!remaining) { // Stream, list-directed, or NAMELIST
511+
if (auto next{GetCurrentChar()}) {
512+
if (edit.IsListDirected()) {
513+
// list-directed or NAMELIST: check for separators
514+
switch (*next) {
515+
case ' ':
516+
case '\t':
517+
case ';':
518+
case '/':
519+
case '(':
520+
case ')':
521+
case '\'':
522+
case '"':
523+
case '*':
524+
case '\n': // for stream access
525+
return std::nullopt;
526+
case ',':
527+
if (edit.modes.editingFlags & decimalComma) {
528+
break;
529+
} else {
530+
return std::nullopt;
531+
}
532+
default:
533+
break;
534+
}
535+
}
536+
HandleRelativePosition(1);
537+
GotChar();
538+
return next;
539+
}
540+
} else if (*remaining > 0) {
541+
if (auto next{GetCurrentChar()}) {
542+
--*remaining;
543+
HandleRelativePosition(1);
544+
GotChar();
545+
return next;
546+
}
547+
const ConnectionState &connection{GetConnectionState()};
548+
if (!connection.IsAtEOF()) {
549+
if (auto length{connection.EffectiveRecordLength()}) {
550+
if (connection.positionInRecord >= *length) {
551+
IoErrorHandler &handler{GetIoErrorHandler()};
552+
if (mutableModes().nonAdvancing) {
553+
handler.SignalEor();
554+
} else if (connection.openRecl && !connection.modes.pad) {
555+
handler.SignalError(IostatRecordReadOverrun);
556+
}
557+
if (connection.modes.pad) { // PAD='YES'
558+
--*remaining;
559+
return std::optional<char32_t>{' '};
560+
}
561+
}
562+
}
563+
}
564+
}
565+
return std::nullopt;
566+
}
567+
508568
bool IoStatementState::Inquire(
509569
InquiryKeywordHash inquiry, char *out, std::size_t chars) {
510570
return std::visit(
@@ -1060,7 +1120,7 @@ bool InquireUnitState::Inquire(
10601120
result = unit().IsConnected() ? unit().unitNumber() : -1;
10611121
return true;
10621122
case HashInquiryKeyword("POS"):
1063-
result = unit().position();
1123+
result = unit().InquirePos();
10641124
return true;
10651125
case HashInquiryKeyword("RECL"):
10661126
if (!unit().IsConnected()) {

0 commit comments

Comments
 (0)