[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
This commit is contained in:
Peter Klausler 2021-12-02 16:36:09 -08:00
parent 41a0e850fa
commit 06ca9f24e7
7 changed files with 92 additions and 67 deletions

View File

@ -351,3 +351,12 @@ end
the parent, allocatable or not;
all finalization takes place before any deallocation;
and no object or subobject will be finalized more than once.
* When `RECL=` is set via the `OPEN` statement for a sequential formatted input
file, it functions as an effective maximum record length.
Longer records, if any, will appear as if they had been truncated to
the value of `RECL=`.
(Other compilers ignore `RECL=`, signal an error, or apply effective truncation
to some forms of input in this situation.)
For sequential formatted output, RECL= serves as a limit on record lengths
that raises an error when it is exceeded.

View File

@ -25,14 +25,12 @@ enum class Access { Sequential, Direct, Stream };
inline bool IsRecordFile(Access a) { return a != Access::Stream; }
// These characteristics of a connection are immutable after being
// established in an OPEN statement, except for recordLength,
// which is immutable only when isFixedRecordLength is true.
// established in an OPEN statement.
struct ConnectionAttributes {
Access access{Access::Sequential}; // ACCESS='SEQUENTIAL', 'DIRECT', 'STREAM'
std::optional<bool> isUnformatted; // FORM='UNFORMATTED' if true
bool isUTF8{false}; // ENCODING='UTF-8'
bool isFixedRecordLength{false}; // RECL= on OPEN
std::optional<std::int64_t> recordLength; // RECL= or current record
std::optional<std::int64_t> openRecl; // RECL= on OPEN
};
struct ConnectionState : public ConnectionAttributes {
@ -48,6 +46,15 @@ struct ConnectionState : public ConnectionAttributes {
leftTabLimit.reset();
}
std::optional<std::int64_t> EffectiveRecordLength() const {
// When an input record is longer than an explicit RECL= from OPEN
// it is effectively truncated on input.
return openRecl && recordLength && *openRecl < *recordLength ? openRecl
: recordLength;
}
std::optional<std::int64_t> recordLength;
// Positions in a record file (sequential or direct, not stream)
std::int64_t currentRecordNumber{1}; // 1 is first
std::int64_t positionInRecord{0}; // offset in current record

View File

@ -17,7 +17,6 @@ namespace Fortran::runtime::io {
template <Direction DIR>
InternalDescriptorUnit<DIR>::InternalDescriptorUnit(
Scalar scalar, std::size_t length) {
isFixedRecordLength = true;
recordLength = length;
endfileRecordNumber = 2;
void *pointer{reinterpret_cast<void *>(const_cast<char *>(scalar))};
@ -34,7 +33,6 @@ InternalDescriptorUnit<DIR>::InternalDescriptorUnit(
terminator, that.SizeInBytes() <= d.SizeInBytes(maxRank, true, 0));
new (&d) Descriptor{that};
d.Check();
isFixedRecordLength = true;
recordLength = d.ElementBytes();
endfileRecordNumber = d.Elements() + 1;
}

View File

@ -543,7 +543,7 @@ bool IONAME(SetRec)(Cookie cookie, std::int64_t rec) {
"REC= may not appear unless ACCESS='DIRECT'");
return false;
}
if (!connection.isFixedRecordLength || !connection.recordLength) {
if (!connection.openRecl) {
io.GetIoErrorHandler().SignalError("RECL= was not specified");
return false;
}
@ -554,7 +554,7 @@ bool IONAME(SetRec)(Cookie cookie, std::int64_t rec) {
}
connection.currentRecordNumber = rec;
if (auto *unit{io.GetExternalFileUnit()}) {
unit->SetPosition((rec - 1) * *connection.recordLength);
unit->SetPosition((rec - 1) * *connection.openRecl);
}
return true;
}
@ -827,12 +827,11 @@ bool IONAME(SetRecl)(Cookie cookie, std::size_t n) {
if (n <= 0) {
io.GetIoErrorHandler().SignalError("RECL= must be greater than zero");
}
if (open->wasExtant() && open->unit().isFixedRecordLength &&
open->unit().recordLength.value_or(n) != static_cast<std::int64_t>(n)) {
if (open->wasExtant() &&
open->unit().openRecl.value_or(n) != static_cast<std::int64_t>(n)) {
open->SignalError("RECL= may not be changed for an open unit");
}
open->unit().isFixedRecordLength = true;
open->unit().recordLength = n;
open->unit().openRecl = n;
return true;
}

View File

@ -888,7 +888,7 @@ bool InquireUnitState::Inquire(
case HashInquiryKeyword("DIRECT"):
str = !unit().IsConnected() ? "UNKNOWN"
: unit().access == Access::Direct ||
(unit().mayPosition() && unit().isFixedRecordLength)
(unit().mayPosition() && unit().openRecl)
? "YES"
: "NO";
break;
@ -1056,8 +1056,8 @@ bool InquireUnitState::Inquire(
result = -1;
} else if (unit().access == Access::Stream) {
result = -2;
} else if (unit().isFixedRecordLength && unit().recordLength) {
result = *unit().recordLength;
} else if (unit().openRecl) {
result = *unit().openRecl;
} else {
result = std::numeric_limits<std::uint32_t>::max();
}

View File

@ -192,17 +192,20 @@ public:
return next;
}
const ConnectionState &connection{GetConnectionState()};
if (!connection.IsAtEOF() && connection.recordLength &&
connection.positionInRecord >= *connection.recordLength) {
IoErrorHandler &handler{GetIoErrorHandler()};
if (mutableModes().nonAdvancing) {
handler.SignalEor();
} else if (connection.isFixedRecordLength && !connection.modes.pad) {
handler.SignalError(IostatRecordReadOverrun);
}
if (connection.modes.pad) { // PAD='YES'
--*remaining;
return std::optional<char32_t>{' '};
if (!connection.IsAtEOF()) {
if (auto length{connection.EffectiveRecordLength()}) {
if (connection.positionInRecord >= *length) {
IoErrorHandler &handler{GetIoErrorHandler()};
if (mutableModes().nonAdvancing) {
handler.SignalEor();
} else if (connection.openRecl && !connection.modes.pad) {
handler.SignalError(IostatRecordReadOverrun);
}
if (connection.modes.pad) { // PAD='YES'
--*remaining;
return std::optional<char32_t>{' '};
}
}
}
}
}

View File

@ -129,26 +129,27 @@ void ExternalFileUnit::OpenUnit(std::optional<OpenStatus> status,
Open(status.value_or(OpenStatus::Unknown), action, position, handler);
auto totalBytes{knownSize()};
if (access == Access::Direct) {
if (!isFixedRecordLength || !recordLength) {
if (!openRecl) {
handler.SignalError(IostatOpenBadRecl,
"OPEN(UNIT=%d,ACCESS='DIRECT'): record length is not known",
unitNumber());
} else if (*recordLength <= 0) {
} else if (*openRecl <= 0) {
handler.SignalError(IostatOpenBadRecl,
"OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is invalid",
unitNumber(), static_cast<std::intmax_t>(*recordLength));
} else if (totalBytes && (*totalBytes % *recordLength != 0)) {
unitNumber(), static_cast<std::intmax_t>(*openRecl));
} else if (totalBytes && (*totalBytes % *openRecl != 0)) {
handler.SignalError(IostatOpenBadAppend,
"OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is not an "
"even divisor of the file size %jd",
unitNumber(), static_cast<std::intmax_t>(*recordLength),
unitNumber(), static_cast<std::intmax_t>(*openRecl),
static_cast<std::intmax_t>(*totalBytes));
}
recordLength = openRecl;
}
endfileRecordNumber.reset();
currentRecordNumber = 1;
if (totalBytes && recordLength && *recordLength) {
endfileRecordNumber = 1 + (*totalBytes / *recordLength);
if (totalBytes && access == Access::Direct && openRecl.value_or(0) > 0) {
endfileRecordNumber = 1 + (*totalBytes / *openRecl);
}
if (position == Position::Append) {
if (!endfileRecordNumber) {
@ -276,21 +277,27 @@ bool ExternalFileUnit::Emit(const char *data, std::size_t bytes,
std::size_t elementBytes, IoErrorHandler &handler) {
auto furthestAfter{std::max(furthestPositionInRecord,
positionInRecord + static_cast<std::int64_t>(bytes))};
if (recordLength) {
// It is possible for recordLength to have a value now for a
// variable-length output record if the previous operation
// was a BACKSPACE or non advancing input statement.
if (!isFixedRecordLength) {
recordLength.reset();
beganReadingRecord_ = false;
} else if (furthestAfter > *recordLength) {
if (openRecl) {
// Check for fixed-length record overrun, but allow for
// the unformatted sequential record header & footer, if any.
int extra{access == Access::Sequential && isUnformatted && *isUnformatted
? static_cast<int>(sizeof(std::uint32_t))
: 0};
if (furthestAfter > 2 * extra + *openRecl) {
handler.SignalError(IostatRecordWriteOverrun,
"Attempt to write %zd bytes to position %jd in a fixed-size record "
"of %jd bytes",
bytes, static_cast<std::intmax_t>(positionInRecord),
static_cast<std::intmax_t>(*recordLength));
bytes,
static_cast<std::intmax_t>(positionInRecord - extra /*header*/),
static_cast<std::intmax_t>(*openRecl));
return false;
}
} else if (recordLength) {
// It is possible for recordLength to have a value now for a
// variable-length output record if the previous operation
// was a BACKSPACE or non advancing input statement.
recordLength.reset();
beganReadingRecord_ = false;
}
WriteFrame(frameOffsetInFile_, recordOffsetInFrame_ + furthestAfter, handler);
if (positionInRecord > furthestPositionInRecord) {
@ -340,7 +347,9 @@ std::size_t ExternalFileUnit::GetNextInputBytes(
const char *&p, IoErrorHandler &handler) {
RUNTIME_CHECK(handler, direction_ == Direction::Input);
p = FrameNextInput(handler, 1);
return p ? recordLength.value_or(positionInRecord + 1) - positionInRecord : 0;
return p ? EffectiveRecordLength().value_or(positionInRecord + 1) -
positionInRecord
: 0;
}
std::optional<char32_t> ExternalFileUnit::GetCurrentChar(
@ -400,17 +409,20 @@ bool ExternalFileUnit::BeginReadingRecord(IoErrorHandler &handler) {
RUNTIME_CHECK(handler, direction_ == Direction::Input);
if (!beganReadingRecord_) {
beganReadingRecord_ = true;
if (access == Access::Sequential) {
if (access == Access::Direct) {
RUNTIME_CHECK(handler, openRecl);
auto need{static_cast<std::size_t>(recordOffsetInFrame_ + *openRecl)};
auto got{ReadFrame(frameOffsetInFile_, need, handler)};
if (got >= need) {
recordLength = openRecl;
} else {
recordLength.reset();
handler.SignalEnd();
}
} else if (access == Access::Sequential) {
recordLength.reset();
if (endfileRecordNumber && currentRecordNumber >= *endfileRecordNumber) {
handler.SignalEnd();
} else if (isFixedRecordLength && access == Access::Direct) {
RUNTIME_CHECK(handler, recordLength.has_value());
auto need{
static_cast<std::size_t>(recordOffsetInFrame_ + *recordLength)};
auto got{ReadFrame(frameOffsetInFile_, need, handler)};
if (got < need) {
handler.SignalEnd();
}
} else {
RUNTIME_CHECK(handler, isUnformatted.has_value());
if (isUnformatted.value_or(false)) {
@ -422,8 +434,7 @@ bool ExternalFileUnit::BeginReadingRecord(IoErrorHandler &handler) {
}
}
RUNTIME_CHECK(handler,
access != Access::Sequential || recordLength.has_value() ||
handler.InError());
recordLength.has_value() || !IsRecordFile(access) || handler.InError());
return !handler.InError();
}
@ -435,7 +446,7 @@ void ExternalFileUnit::FinishReadingRecord(IoErrorHandler &handler) {
} else if (access == Access::Sequential) {
RUNTIME_CHECK(handler, recordLength.has_value());
recordOffsetInFrame_ += *recordLength;
if (isFixedRecordLength && access == Access::Direct) {
if (openRecl && access == Access::Direct) {
frameOffsetInFile_ += recordOffsetInFrame_;
recordOffsetInFrame_ = 0;
} else {
@ -472,17 +483,15 @@ bool ExternalFileUnit::AdvanceRecord(IoErrorHandler &handler) {
} else { // Direction::Output
bool ok{true};
RUNTIME_CHECK(handler, isUnformatted.has_value());
if (isFixedRecordLength && recordLength &&
furthestPositionInRecord < *recordLength) {
if (openRecl && furthestPositionInRecord < *openRecl) {
// Pad remainder of fixed length record
WriteFrame(
frameOffsetInFile_, recordOffsetInFrame_ + *recordLength, handler);
WriteFrame(frameOffsetInFile_, recordOffsetInFrame_ + *openRecl, handler);
std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord,
isUnformatted.value_or(false) ? 0 : ' ',
*recordLength - furthestPositionInRecord);
furthestPositionInRecord = *recordLength;
*openRecl - furthestPositionInRecord);
furthestPositionInRecord = *openRecl;
}
if (!(isFixedRecordLength && access == Access::Direct)) {
if (!(openRecl && access == Access::Direct)) {
positionInRecord = furthestPositionInRecord;
if (isUnformatted.value_or(false)) {
// Append the length of a sequential unformatted variable-length record
@ -527,7 +536,7 @@ void ExternalFileUnit::BackspaceRecord(IoErrorHandler &handler) {
DoImpliedEndfile(handler);
if (frameOffsetInFile_ + recordOffsetInFrame_ > 0) {
--currentRecordNumber;
if (isFixedRecordLength && access == Access::Direct) {
if (openRecl && access == Access::Direct) {
BackspaceFixedRecord(handler);
} else {
RUNTIME_CHECK(handler, isUnformatted.has_value());
@ -670,11 +679,11 @@ void ExternalFileUnit::BeginSequentialVariableFormattedInputRecord(
}
void ExternalFileUnit::BackspaceFixedRecord(IoErrorHandler &handler) {
RUNTIME_CHECK(handler, recordLength.has_value());
if (frameOffsetInFile_ < *recordLength) {
RUNTIME_CHECK(handler, openRecl.has_value());
if (frameOffsetInFile_ < *openRecl) {
handler.SignalError(IostatBackspaceAtFirstRecord);
} else {
frameOffsetInFile_ -= *recordLength;
frameOffsetInFile_ -= *openRecl;
}
}