Skip to content

Commit 4e96f05

Browse files
klauslervdonaldson
authored andcommitted
[flang] OPEN(RECL=) handling for sequential formatted I/O
RECL= is required for direct access I/O, but is permitted as well for sequential I/O, where it is defined by the standard to specify a maximum record (line) length. The standard does not say what should happen when an sequential formatted input record appears whose length is unequal to RECL= when it is specified. Precedents from other compilers are unclear: one raises an error, some honor RECL= as an effective truncation, and a few ignore the situation. On output, all other compilers tested raised an error when an attempt is made to emit a record longer than RECL=. This patch treats RECL= as effective truncation on input and as a hard limit with error on output, and also ensures that RECL= can be set *longer* than the actual input record lengths. Differential Revision: https://reviews.llvm.org/D115102
1 parent d85db4e commit 4e96f05

File tree

7 files changed

+92
-67
lines changed

7 files changed

+92
-67
lines changed

flang/docs/Extensions.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,3 +351,12 @@ end
351351
the parent, allocatable or not;
352352
all finalization takes place before any deallocation;
353353
and no object or subobject will be finalized more than once.
354+
355+
* When `RECL=` is set via the `OPEN` statement for a sequential formatted input
356+
file, it functions as an effective maximum record length.
357+
Longer records, if any, will appear as if they had been truncated to
358+
the value of `RECL=`.
359+
(Other compilers ignore `RECL=`, signal an error, or apply effective truncation
360+
to some forms of input in this situation.)
361+
For sequential formatted output, RECL= serves as a limit on record lengths
362+
that raises an error when it is exceeded.

flang/runtime/connection.h

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,12 @@ enum class Access { Sequential, Direct, Stream };
2525
inline bool IsRecordFile(Access a) { return a != Access::Stream; }
2626

2727
// These characteristics of a connection are immutable after being
28-
// established in an OPEN statement, except for recordLength,
29-
// which is immutable only when isFixedRecordLength is true.
28+
// established in an OPEN statement.
3029
struct ConnectionAttributes {
3130
Access access{Access::Sequential}; // ACCESS='SEQUENTIAL', 'DIRECT', 'STREAM'
3231
std::optional<bool> isUnformatted; // FORM='UNFORMATTED' if true
3332
bool isUTF8{false}; // ENCODING='UTF-8'
34-
bool isFixedRecordLength{false}; // RECL= on OPEN
35-
std::optional<std::int64_t> recordLength; // RECL= or current record
33+
std::optional<std::int64_t> openRecl; // RECL= on OPEN
3634
};
3735

3836
struct ConnectionState : public ConnectionAttributes {
@@ -48,6 +46,15 @@ struct ConnectionState : public ConnectionAttributes {
4846
leftTabLimit.reset();
4947
}
5048

49+
std::optional<std::int64_t> EffectiveRecordLength() const {
50+
// When an input record is longer than an explicit RECL= from OPEN
51+
// it is effectively truncated on input.
52+
return openRecl && recordLength && *openRecl < *recordLength ? openRecl
53+
: recordLength;
54+
}
55+
56+
std::optional<std::int64_t> recordLength;
57+
5158
// Positions in a record file (sequential or direct, not stream)
5259
std::int64_t currentRecordNumber{1}; // 1 is first
5360
std::int64_t positionInRecord{0}; // offset in current record

flang/runtime/internal-unit.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ namespace Fortran::runtime::io {
1717
template <Direction DIR>
1818
InternalDescriptorUnit<DIR>::InternalDescriptorUnit(
1919
Scalar scalar, std::size_t length) {
20-
isFixedRecordLength = true;
2120
recordLength = length;
2221
endfileRecordNumber = 2;
2322
void *pointer{reinterpret_cast<void *>(const_cast<char *>(scalar))};
@@ -34,7 +33,6 @@ InternalDescriptorUnit<DIR>::InternalDescriptorUnit(
3433
terminator, that.SizeInBytes() <= d.SizeInBytes(maxRank, true, 0));
3534
new (&d) Descriptor{that};
3635
d.Check();
37-
isFixedRecordLength = true;
3836
recordLength = d.ElementBytes();
3937
endfileRecordNumber = d.Elements() + 1;
4038
}

flang/runtime/io-api.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,7 @@ bool IONAME(SetRec)(Cookie cookie, std::int64_t rec) {
543543
"REC= may not appear unless ACCESS='DIRECT'");
544544
return false;
545545
}
546-
if (!connection.isFixedRecordLength || !connection.recordLength) {
546+
if (!connection.openRecl) {
547547
io.GetIoErrorHandler().SignalError("RECL= was not specified");
548548
return false;
549549
}
@@ -554,7 +554,7 @@ bool IONAME(SetRec)(Cookie cookie, std::int64_t rec) {
554554
}
555555
connection.currentRecordNumber = rec;
556556
if (auto *unit{io.GetExternalFileUnit()}) {
557-
unit->SetPosition((rec - 1) * *connection.recordLength);
557+
unit->SetPosition((rec - 1) * *connection.openRecl);
558558
}
559559
return true;
560560
}
@@ -827,12 +827,11 @@ bool IONAME(SetRecl)(Cookie cookie, std::size_t n) {
827827
if (n <= 0) {
828828
io.GetIoErrorHandler().SignalError("RECL= must be greater than zero");
829829
}
830-
if (open->wasExtant() && open->unit().isFixedRecordLength &&
831-
open->unit().recordLength.value_or(n) != static_cast<std::int64_t>(n)) {
830+
if (open->wasExtant() &&
831+
open->unit().openRecl.value_or(n) != static_cast<std::int64_t>(n)) {
832832
open->SignalError("RECL= may not be changed for an open unit");
833833
}
834-
open->unit().isFixedRecordLength = true;
835-
open->unit().recordLength = n;
834+
open->unit().openRecl = n;
836835
return true;
837836
}
838837

flang/runtime/io-stmt.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -888,7 +888,7 @@ bool InquireUnitState::Inquire(
888888
case HashInquiryKeyword("DIRECT"):
889889
str = !unit().IsConnected() ? "UNKNOWN"
890890
: unit().access == Access::Direct ||
891-
(unit().mayPosition() && unit().isFixedRecordLength)
891+
(unit().mayPosition() && unit().openRecl)
892892
? "YES"
893893
: "NO";
894894
break;
@@ -1056,8 +1056,8 @@ bool InquireUnitState::Inquire(
10561056
result = -1;
10571057
} else if (unit().access == Access::Stream) {
10581058
result = -2;
1059-
} else if (unit().isFixedRecordLength && unit().recordLength) {
1060-
result = *unit().recordLength;
1059+
} else if (unit().openRecl) {
1060+
result = *unit().openRecl;
10611061
} else {
10621062
result = std::numeric_limits<std::uint32_t>::max();
10631063
}

flang/runtime/io-stmt.h

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -192,17 +192,20 @@ class IoStatementState {
192192
return next;
193193
}
194194
const ConnectionState &connection{GetConnectionState()};
195-
if (!connection.IsAtEOF() && connection.recordLength &&
196-
connection.positionInRecord >= *connection.recordLength) {
197-
IoErrorHandler &handler{GetIoErrorHandler()};
198-
if (mutableModes().nonAdvancing) {
199-
handler.SignalEor();
200-
} else if (connection.isFixedRecordLength && !connection.modes.pad) {
201-
handler.SignalError(IostatRecordReadOverrun);
202-
}
203-
if (connection.modes.pad) { // PAD='YES'
204-
--*remaining;
205-
return std::optional<char32_t>{' '};
195+
if (!connection.IsAtEOF()) {
196+
if (auto length{connection.EffectiveRecordLength()}) {
197+
if (connection.positionInRecord >= *length) {
198+
IoErrorHandler &handler{GetIoErrorHandler()};
199+
if (mutableModes().nonAdvancing) {
200+
handler.SignalEor();
201+
} else if (connection.openRecl && !connection.modes.pad) {
202+
handler.SignalError(IostatRecordReadOverrun);
203+
}
204+
if (connection.modes.pad) { // PAD='YES'
205+
--*remaining;
206+
return std::optional<char32_t>{' '};
207+
}
208+
}
206209
}
207210
}
208211
}

flang/runtime/unit.cpp

Lines changed: 50 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -129,26 +129,27 @@ void ExternalFileUnit::OpenUnit(std::optional<OpenStatus> status,
129129
Open(status.value_or(OpenStatus::Unknown), action, position, handler);
130130
auto totalBytes{knownSize()};
131131
if (access == Access::Direct) {
132-
if (!isFixedRecordLength || !recordLength) {
132+
if (!openRecl) {
133133
handler.SignalError(IostatOpenBadRecl,
134134
"OPEN(UNIT=%d,ACCESS='DIRECT'): record length is not known",
135135
unitNumber());
136-
} else if (*recordLength <= 0) {
136+
} else if (*openRecl <= 0) {
137137
handler.SignalError(IostatOpenBadRecl,
138138
"OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is invalid",
139-
unitNumber(), static_cast<std::intmax_t>(*recordLength));
140-
} else if (totalBytes && (*totalBytes % *recordLength != 0)) {
139+
unitNumber(), static_cast<std::intmax_t>(*openRecl));
140+
} else if (totalBytes && (*totalBytes % *openRecl != 0)) {
141141
handler.SignalError(IostatOpenBadAppend,
142142
"OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is not an "
143143
"even divisor of the file size %jd",
144-
unitNumber(), static_cast<std::intmax_t>(*recordLength),
144+
unitNumber(), static_cast<std::intmax_t>(*openRecl),
145145
static_cast<std::intmax_t>(*totalBytes));
146146
}
147+
recordLength = openRecl;
147148
}
148149
endfileRecordNumber.reset();
149150
currentRecordNumber = 1;
150-
if (totalBytes && recordLength && *recordLength) {
151-
endfileRecordNumber = 1 + (*totalBytes / *recordLength);
151+
if (totalBytes && access == Access::Direct && openRecl.value_or(0) > 0) {
152+
endfileRecordNumber = 1 + (*totalBytes / *openRecl);
152153
}
153154
if (position == Position::Append) {
154155
if (!endfileRecordNumber) {
@@ -276,21 +277,27 @@ bool ExternalFileUnit::Emit(const char *data, std::size_t bytes,
276277
std::size_t elementBytes, IoErrorHandler &handler) {
277278
auto furthestAfter{std::max(furthestPositionInRecord,
278279
positionInRecord + static_cast<std::int64_t>(bytes))};
279-
if (recordLength) {
280-
// It is possible for recordLength to have a value now for a
281-
// variable-length output record if the previous operation
282-
// was a BACKSPACE or non advancing input statement.
283-
if (!isFixedRecordLength) {
284-
recordLength.reset();
285-
beganReadingRecord_ = false;
286-
} else if (furthestAfter > *recordLength) {
280+
if (openRecl) {
281+
// Check for fixed-length record overrun, but allow for
282+
// the unformatted sequential record header & footer, if any.
283+
int extra{access == Access::Sequential && isUnformatted && *isUnformatted
284+
? static_cast<int>(sizeof(std::uint32_t))
285+
: 0};
286+
if (furthestAfter > 2 * extra + *openRecl) {
287287
handler.SignalError(IostatRecordWriteOverrun,
288288
"Attempt to write %zd bytes to position %jd in a fixed-size record "
289289
"of %jd bytes",
290-
bytes, static_cast<std::intmax_t>(positionInRecord),
291-
static_cast<std::intmax_t>(*recordLength));
290+
bytes,
291+
static_cast<std::intmax_t>(positionInRecord - extra /*header*/),
292+
static_cast<std::intmax_t>(*openRecl));
292293
return false;
293294
}
295+
} else if (recordLength) {
296+
// It is possible for recordLength to have a value now for a
297+
// variable-length output record if the previous operation
298+
// was a BACKSPACE or non advancing input statement.
299+
recordLength.reset();
300+
beganReadingRecord_ = false;
294301
}
295302
WriteFrame(frameOffsetInFile_, recordOffsetInFrame_ + furthestAfter, handler);
296303
if (positionInRecord > furthestPositionInRecord) {
@@ -340,7 +347,9 @@ std::size_t ExternalFileUnit::GetNextInputBytes(
340347
const char *&p, IoErrorHandler &handler) {
341348
RUNTIME_CHECK(handler, direction_ == Direction::Input);
342349
p = FrameNextInput(handler, 1);
343-
return p ? recordLength.value_or(positionInRecord + 1) - positionInRecord : 0;
350+
return p ? EffectiveRecordLength().value_or(positionInRecord + 1) -
351+
positionInRecord
352+
: 0;
344353
}
345354

346355
std::optional<char32_t> ExternalFileUnit::GetCurrentChar(
@@ -400,17 +409,20 @@ bool ExternalFileUnit::BeginReadingRecord(IoErrorHandler &handler) {
400409
RUNTIME_CHECK(handler, direction_ == Direction::Input);
401410
if (!beganReadingRecord_) {
402411
beganReadingRecord_ = true;
403-
if (access == Access::Sequential) {
412+
if (access == Access::Direct) {
413+
RUNTIME_CHECK(handler, openRecl);
414+
auto need{static_cast<std::size_t>(recordOffsetInFrame_ + *openRecl)};
415+
auto got{ReadFrame(frameOffsetInFile_, need, handler)};
416+
if (got >= need) {
417+
recordLength = openRecl;
418+
} else {
419+
recordLength.reset();
420+
handler.SignalEnd();
421+
}
422+
} else if (access == Access::Sequential) {
423+
recordLength.reset();
404424
if (endfileRecordNumber && currentRecordNumber >= *endfileRecordNumber) {
405425
handler.SignalEnd();
406-
} else if (isFixedRecordLength && access == Access::Direct) {
407-
RUNTIME_CHECK(handler, recordLength.has_value());
408-
auto need{
409-
static_cast<std::size_t>(recordOffsetInFrame_ + *recordLength)};
410-
auto got{ReadFrame(frameOffsetInFile_, need, handler)};
411-
if (got < need) {
412-
handler.SignalEnd();
413-
}
414426
} else {
415427
RUNTIME_CHECK(handler, isUnformatted.has_value());
416428
if (isUnformatted.value_or(false)) {
@@ -422,8 +434,7 @@ bool ExternalFileUnit::BeginReadingRecord(IoErrorHandler &handler) {
422434
}
423435
}
424436
RUNTIME_CHECK(handler,
425-
access != Access::Sequential || recordLength.has_value() ||
426-
handler.InError());
437+
recordLength.has_value() || !IsRecordFile(access) || handler.InError());
427438
return !handler.InError();
428439
}
429440

@@ -435,7 +446,7 @@ void ExternalFileUnit::FinishReadingRecord(IoErrorHandler &handler) {
435446
} else if (access == Access::Sequential) {
436447
RUNTIME_CHECK(handler, recordLength.has_value());
437448
recordOffsetInFrame_ += *recordLength;
438-
if (isFixedRecordLength && access == Access::Direct) {
449+
if (openRecl && access == Access::Direct) {
439450
frameOffsetInFile_ += recordOffsetInFrame_;
440451
recordOffsetInFrame_ = 0;
441452
} else {
@@ -472,17 +483,15 @@ bool ExternalFileUnit::AdvanceRecord(IoErrorHandler &handler) {
472483
} else { // Direction::Output
473484
bool ok{true};
474485
RUNTIME_CHECK(handler, isUnformatted.has_value());
475-
if (isFixedRecordLength && recordLength &&
476-
furthestPositionInRecord < *recordLength) {
486+
if (openRecl && furthestPositionInRecord < *openRecl) {
477487
// Pad remainder of fixed length record
478-
WriteFrame(
479-
frameOffsetInFile_, recordOffsetInFrame_ + *recordLength, handler);
488+
WriteFrame(frameOffsetInFile_, recordOffsetInFrame_ + *openRecl, handler);
480489
std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord,
481490
isUnformatted.value_or(false) ? 0 : ' ',
482-
*recordLength - furthestPositionInRecord);
483-
furthestPositionInRecord = *recordLength;
491+
*openRecl - furthestPositionInRecord);
492+
furthestPositionInRecord = *openRecl;
484493
}
485-
if (!(isFixedRecordLength && access == Access::Direct)) {
494+
if (!(openRecl && access == Access::Direct)) {
486495
positionInRecord = furthestPositionInRecord;
487496
if (isUnformatted.value_or(false)) {
488497
// Append the length of a sequential unformatted variable-length record
@@ -527,7 +536,7 @@ void ExternalFileUnit::BackspaceRecord(IoErrorHandler &handler) {
527536
DoImpliedEndfile(handler);
528537
if (frameOffsetInFile_ + recordOffsetInFrame_ > 0) {
529538
--currentRecordNumber;
530-
if (isFixedRecordLength && access == Access::Direct) {
539+
if (openRecl && access == Access::Direct) {
531540
BackspaceFixedRecord(handler);
532541
} else {
533542
RUNTIME_CHECK(handler, isUnformatted.has_value());
@@ -670,11 +679,11 @@ void ExternalFileUnit::BeginSequentialVariableFormattedInputRecord(
670679
}
671680

672681
void ExternalFileUnit::BackspaceFixedRecord(IoErrorHandler &handler) {
673-
RUNTIME_CHECK(handler, recordLength.has_value());
674-
if (frameOffsetInFile_ < *recordLength) {
682+
RUNTIME_CHECK(handler, openRecl.has_value());
683+
if (frameOffsetInFile_ < *openRecl) {
675684
handler.SignalError(IostatBackspaceAtFirstRecord);
676685
} else {
677-
frameOffsetInFile_ -= *recordLength;
686+
frameOffsetInFile_ -= *openRecl;
678687
}
679688
}
680689

0 commit comments

Comments
 (0)