[llvm] [flang-rt] Split large unformatted sequential records into subrecords (PR #213003)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Jul 30 04:56:23 PDT 2026
https://github.com/ejose02 created https://github.com/llvm/llvm-project/pull/213003
Implement the TODO in AdvanceRecord to break very large unformatted sequential records into a chain of signed 4-byte subrecord markers, compatible with gfortran on-disk layout:
```
// TODO: Break very large records up into subrecords with negative
// headers &/or footers
```
A single unsigned 32-bit header/footer cannot represent records at or above 4 GiB. The sign of each marker indicates continuation and the magnitude gives the payload size (at most ~2 GiB per subrecord). Records below that limit keep the original single header/footer layout.
Larger records are laid out in the frame buffer on output and read back by streaming across interior markers; backspace and positioning walk the full on-disk extent.
Added a unit test for reading and backspacing over a multi-subrecord file.
>From 81c8969f6aecbf9da35eedab7c4f1133895c0e70 Mon Sep 17 00:00:00 2001
From: ejose <ejose at amd.com>
Date: Thu, 30 Jul 2026 11:43:59 +0000
Subject: [PATCH] [flang-rt] Split large unformatted sequential records into
subrecords
Implement the TODO in AdvanceRecord to break very large unformatted sequential records into a chain of signed 4-byte subrecord markers, compatible with gfortran on-disk layout:
// TODO: Break very large records up into subrecords with negative
// headers &/or footers
A single unsigned 32-bit header/footer cannot represent records at or above 4 GiB. The sign of each marker indicates continuation and the magnitude gives the payload size (at most ~2 GiB per subrecord). Records below that limit keep the original single header/footer layout.
Larger records are laid out in the frame buffer on output and read back by streaming across interior markers; backspace and positioning walk the full on-disk extent.
Added a unit test for reading and backspacing over a multi-subrecord file.
---
flang-rt/lib/runtime/unit.cpp | 349 ++++++++++++------
flang-rt/lib/runtime/unit.h | 6 +-
flang-rt/unittests/Runtime/ExternalIOTest.cpp | 99 +++++
3 files changed, 350 insertions(+), 104 deletions(-)
diff --git a/flang-rt/lib/runtime/unit.cpp b/flang-rt/lib/runtime/unit.cpp
index c577ae7673127..ad8d290998219 100644
--- a/flang-rt/lib/runtime/unit.cpp
+++ b/flang-rt/lib/runtime/unit.cpp
@@ -44,6 +44,61 @@ static inline RT_API_ATTRS void SwapEndianness(
}
}
+static constexpr std::size_t kRecMarkSize{4};
+static constexpr std::int32_t kMaxSubrecPayload{0x7fffffff - 8};
+
+static inline RT_API_ATTRS std::size_t SubrecordPayloadBytes(
+ std::int32_t marker) {
+ return static_cast<std::size_t>(marker < 0 ? -marker : marker);
+}
+
+static inline RT_API_ATTRS std::int32_t SignedRecordMarker(
+ std::int32_t length, bool negative) {
+ return negative ? -length : length;
+}
+
+static inline RT_API_ATTRS void StoreRecordMarker(
+ char *p, std::int32_t value, bool swapEndianness) {
+ runtime::memcpy(p, &value, kRecMarkSize);
+ if (swapEndianness) {
+ SwapEndianness(p, kRecMarkSize, kRecMarkSize);
+ }
+}
+
+static inline RT_API_ATTRS std::int32_t LoadRecordMarker(
+ const char *p, bool swapEndianness) {
+ std::int32_t word;
+ runtime::memcpy(&word, p, kRecMarkSize);
+ if (swapEndianness) {
+ SwapEndianness(reinterpret_cast<char *>(&word), kRecMarkSize, kRecMarkSize);
+ }
+ return word;
+}
+
+static RT_API_ATTRS void LayoutBufferedSubrecords(
+ char *base, std::size_t payload, bool swapEndianness) {
+ std::size_t maxChunk{static_cast<std::size_t>(kMaxSubrecPayload)};
+ std::size_t subrecords{(payload + maxChunk - 1) / maxChunk};
+ for (std::size_t i{subrecords}; i-- > 0;) {
+ std::size_t chunk{i + 1 < subrecords ? maxChunk : payload - i * maxChunk};
+ std::size_t src{kRecMarkSize + i * maxChunk};
+ std::size_t dst{kRecMarkSize + i * (maxChunk + 2 * kRecMarkSize)};
+ if (dst != src && chunk > 0) {
+ runtime::memmove(base + dst, base + src, chunk);
+ }
+ std::int32_t length{static_cast<std::int32_t>(chunk)};
+ StoreRecordMarker(base + dst - kRecMarkSize,
+ SignedRecordMarker(length, i + 1 < subrecords), swapEndianness);
+ StoreRecordMarker(base + dst + chunk,
+ SignedRecordMarker(length, i > 0), swapEndianness);
+ }
+}
+
+void ExternalFileUnit::ResetSequentialUnformattedStream() {
+ streamRead_ = false;
+ streamReadRemain_ = 0;
+}
+
bool ExternalFileUnit::Emit(const char *data, std::size_t bytes,
std::size_t elementBytes, IoErrorHandler &handler) {
auto furthestAfter{std::max(furthestPositionInRecord,
@@ -116,6 +171,46 @@ bool ExternalFileUnit::Receive(char *data, std::size_t bytes,
static_cast<std::intmax_t>(*recordLength));
return false;
}
+ if (streamRead_) {
+ std::size_t copied{0};
+ while (copied < bytes) {
+ if (streamReadRemain_ == 0) {
+ streamReadPos_ += kRecMarkSize;
+ char mark[kRecMarkSize];
+ if (Read(streamReadPos_, mark, kRecMarkSize, kRecMarkSize, handler) <
+ kRecMarkSize) {
+ HitEndOnRead(handler);
+ return false;
+ }
+ streamReadPos_ += kRecMarkSize;
+ streamReadRemain_ =
+ SubrecordPayloadBytes(LoadRecordMarker(mark, swapEndianness_));
+ }
+ std::size_t chunk{bytes - copied};
+ if (chunk > streamReadRemain_) {
+ chunk = streamReadRemain_;
+ }
+ auto logicalRemain{static_cast<std::size_t>(
+ *recordLength - positionInRecord)};
+ if (chunk > logicalRemain) {
+ chunk = logicalRemain;
+ }
+ if (Read(streamReadPos_, data + copied, chunk, chunk, handler) < chunk) {
+ HitEndOnRead(handler);
+ return false;
+ }
+ if (swapEndianness_) {
+ SwapEndianness(data + copied, chunk, elementBytes);
+ }
+ streamReadPos_ += static_cast<FileOffset>(chunk);
+ streamReadRemain_ -= chunk;
+ copied += chunk;
+ positionInRecord += static_cast<std::int64_t>(chunk);
+ }
+ furthestPositionInRecord =
+ std::max(furthestPositionInRecord, positionInRecord);
+ return true;
+ }
auto need{recordOffsetInFrame_ + furthestAfter};
auto got{ReadFrame(frameOffsetInFile_, need, handler)};
if (got >= need) {
@@ -268,9 +363,9 @@ void ExternalFileUnit::FinishReadingRecord(IoErrorHandler &handler) {
RUNTIME_CHECK(handler, isUnformatted.has_value());
recordLength.reset();
if (isUnformatted.value_or(false)) {
- // Retain footer in frame for more efficient BACKSPACE
- frameOffsetInFile_ += recordOffsetInFrame_;
- recordOffsetInFrame_ = sizeof(std::uint32_t);
+ frameOffsetInFile_ += unformattedDiskExtent_;
+ unformattedDiskExtent_ = 0;
+ recordOffsetInFrame_ = 0;
} else { // formatted
if (FrameLength() > recordOffsetInFrame_ &&
Frame()[recordOffsetInFrame_] == '\r') {
@@ -294,6 +389,7 @@ void ExternalFileUnit::FinishReadingRecord(IoErrorHandler &handler) {
recordOffsetInFrame_ = 0;
}
BeginRecord();
+ ResetSequentialUnformattedStream();
leftTabLimit.reset();
}
@@ -319,21 +415,26 @@ bool ExternalFileUnit::AdvanceRecord(IoErrorHandler &handler) {
}
} else if (*isUnformatted) {
if (access == Access::Sequential) {
- // Append the length of a sequential unformatted variable-length record
- // as its footer, then overwrite the reserved first four bytes of the
- // record with its length as its header. These four bytes were skipped
- // over in BeginUnformattedIO<Output>().
- // TODO: Break very large records up into subrecords with negative
- // headers &/or footers
- std::uint32_t length;
- length = furthestPositionInRecord - sizeof length;
- ok = ok &&
- Emit(reinterpret_cast<const char *>(&length), sizeof length,
- sizeof length, handler);
- positionInRecord = 0;
- ok = ok &&
- Emit(reinterpret_cast<const char *>(&length), sizeof length,
- sizeof length, handler);
+ std::size_t payload{
+ static_cast<std::size_t>(furthestPositionInRecord - kRecMarkSize)};
+ if (payload <= static_cast<std::size_t>(kMaxSubrecPayload)) {
+ std::int32_t length{static_cast<std::int32_t>(payload)};
+ ok = ok && Emit(reinterpret_cast<const char *>(&length), kRecMarkSize,
+ kRecMarkSize, handler);
+ positionInRecord = 0;
+ ok = ok && Emit(reinterpret_cast<const char *>(&length), kRecMarkSize,
+ kRecMarkSize, handler);
+ } else {
+ std::size_t maxChunk{static_cast<std::size_t>(kMaxSubrecPayload)};
+ std::size_t subrecords{(payload + maxChunk - 1) / maxChunk};
+ std::size_t diskExtent{payload + 2 * subrecords * kRecMarkSize};
+ WriteFrame(
+ frameOffsetInFile_, recordOffsetInFrame_ + diskExtent, handler);
+ char *base{Frame() + recordOffsetInFrame_};
+ LayoutBufferedSubrecords(base, payload, swapEndianness_);
+ furthestPositionInRecord = static_cast<std::int64_t>(diskExtent);
+ positionInRecord = furthestPositionInRecord;
+ }
} else {
// Unformatted stream: nothing to do
}
@@ -459,6 +560,7 @@ void ExternalFileUnit::Rewind(IoErrorHandler &handler) {
void ExternalFileUnit::SetPosition(std::int64_t pos) {
frameOffsetInFile_ = pos;
recordOffsetInFrame_ = 0;
+ ResetSequentialUnformattedStream();
if (access == Access::Direct) {
directAccessRecWasSet_ = true;
}
@@ -550,67 +652,122 @@ void ExternalFileUnit::EndIoStatement() {
void ExternalFileUnit::BeginSequentialVariableUnformattedInputRecord(
IoErrorHandler &handler) {
RUNTIME_CHECK(handler, access == Access::Sequential);
- std::uint32_t header{0}, footer{0};
- std::size_t need{recordOffsetInFrame_ + sizeof header};
- std::size_t got{ReadFrame(frameOffsetInFile_, need, handler)};
- // Try to emit informative errors to help debug corrupted files.
+ unformattedDiskExtent_ = 0;
+ streamRead_ = false;
+ FileOffset recordStart{
+ frameOffsetInFile_ + static_cast<FileOffset>(recordOffsetInFrame_)};
const char *error{nullptr};
- if (got < need) {
- if (got == recordOffsetInFrame_) {
- HitEndOnRead(handler);
- } else {
- error = "Unformatted variable-length sequential file input failed at "
- "record #%jd (file offset %jd): truncated record header";
+ std::int32_t header{0}, footer{0}, firstHeader{0};
+ std::size_t totalPayload{0};
+ FileOffset filePos{recordStart};
+
+ enum class MarkerRead { Ok, Eof, Short };
+ enum class ScanOutcome { Ok, Eof, Fail };
+ const char *scanError{nullptr};
+
+ auto readMarkerAt = [&](FileOffset &pos, std::int32_t &marker, bool swap) {
+ char buf[kRecMarkSize];
+ auto got{Read(pos, buf, kRecMarkSize, kRecMarkSize, handler)};
+ if (got < kRecMarkSize) {
+ return got == 0 ? MarkerRead::Eof : MarkerRead::Short;
}
- } else {
- header = ReadHeaderOrFooter(recordOffsetInFrame_);
- recordLength = sizeof header + header; // does not include footer
- need = recordOffsetInFrame_ + *recordLength + sizeof footer;
- got = ReadFrame(frameOffsetInFile_, need, handler);
- if (got >= need) {
- footer = ReadHeaderOrFooter(recordOffsetInFrame_ + *recordLength);
+ pos += kRecMarkSize;
+ marker = LoadRecordMarker(buf, swap);
+ return MarkerRead::Ok;
+ };
+
+ auto scanRecord = [&](bool swap, std::int32_t &outHeader, std::int32_t &outFooter,
+ std::int32_t &outFirstHeader, std::size_t &outTotalPayload,
+ FileOffset &outFilePos) -> ScanOutcome {
+ scanError = nullptr;
+ outFilePos = recordStart;
+ outTotalPayload = 0;
+ switch (readMarkerAt(outFilePos, outHeader, swap)) {
+ case MarkerRead::Ok:
+ break;
+ case MarkerRead::Eof:
+ return ScanOutcome::Eof;
+ case MarkerRead::Short:
+ scanError = "Unformatted variable-length sequential file input failed at "
+ "record #%jd (file offset %jd): truncated record header";
+ return ScanOutcome::Fail;
}
- if (frameOffsetInFile_ == 0 && recordOffsetInFrame_ == 0 &&
- (got < need || footer != header)) {
- // Maybe an omitted or incorrect byte swap flag setting?
- // Try it the other way, since this is the first record.
- // (N.B. Won't work on files starting with empty records, but there's
- // no good way to know later if all preceding records were empty.)
- swapEndianness_ = !swapEndianness_;
- std::uint32_t header2{ReadHeaderOrFooter(0)};
- std::size_t recordLength2{sizeof header2 + header2};
- std::size_t need2{recordLength2 + sizeof footer};
- std::size_t got2{ReadFrame(0, need2, handler)};
- if (got2 >= need2) {
- std::uint32_t footer2{ReadHeaderOrFooter(recordLength2)};
- if (footer2 == header2) {
- error = "Unformatted variable-length sequential file input "
- "failed on the first record, probably due to a need "
- "for byte order data conversion; consider adding "
- "CONVERT='SWAP' to the OPEN statement or adding "
- "FORT_CONVERT=SWAP to the execution environment";
- }
+ outFirstHeader = outHeader;
+ do {
+ outTotalPayload += SubrecordPayloadBytes(outHeader);
+ outFilePos += static_cast<FileOffset>(SubrecordPayloadBytes(outHeader));
+ if (readMarkerAt(outFilePos, outFooter, swap) != MarkerRead::Ok) {
+ scanError = "Unformatted variable-length sequential file input failed at "
+ "record #%jd (file offset %jd): truncated record footer";
+ return ScanOutcome::Fail;
}
- swapEndianness_ = !swapEndianness_;
+ if (SubrecordPayloadBytes(outFooter) != SubrecordPayloadBytes(outHeader)) {
+ scanError = "Unformatted variable-length sequential file input failed at "
+ "record #%jd (file offset %jd): record header has length %jd "
+ "that does not match record footer (%jd)";
+ return ScanOutcome::Fail;
+ }
+ if (outHeader >= 0) {
+ return ScanOutcome::Ok;
+ }
+ if (readMarkerAt(outFilePos, outHeader, swap) != MarkerRead::Ok) {
+ scanError = "Unformatted variable-length sequential file input failed at "
+ "record #%jd (file offset %jd): truncated record header";
+ return ScanOutcome::Fail;
+ }
+ } while (true);
+ };
+
+ auto outcome{scanRecord(swapEndianness_, header, footer, firstHeader,
+ totalPayload, filePos)};
+ if (outcome == ScanOutcome::Eof) {
+ HitEndOnRead(handler);
+ return;
+ }
+ if (outcome == ScanOutcome::Fail) {
+ const char *primaryError{scanError};
+ if (frameOffsetInFile_ == 0 && recordOffsetInFrame_ == 0 &&
+ [&]() {
+ std::int32_t swappedHeader{0}, swappedFooter{0}, swappedFirstHeader{0};
+ std::size_t swappedTotalPayload{0};
+ FileOffset swappedFilePos{recordStart};
+ return scanRecord(!swapEndianness_, swappedHeader, swappedFooter,
+ swappedFirstHeader, swappedTotalPayload, swappedFilePos) ==
+ ScanOutcome::Ok;
+ }()) {
+ error = "Unformatted variable-length sequential file input failed on the "
+ "first record, probably due to a need for byte order data "
+ "conversion; consider adding CONVERT='SWAP' to the OPEN statement "
+ "or adding FORT_CONVERT=SWAP to the execution environment";
+ } else {
+ error = primaryError ? primaryError
+ : "Unformatted variable-length sequential file input "
+ "failed at record #%jd (file offset %jd): truncated "
+ "record header";
}
- if (error) {
- } else if (got < need) {
- error = "Unformatted variable-length sequential file input failed at "
- "record #%jd (file offset %jd): hit EOF reading record with "
- "length %jd bytes";
- } else if (footer != header) {
- error = "Unformatted variable-length sequential file input failed at "
- "record #%jd (file offset %jd): record header has length %jd "
- "that does not match record footer (%jd)";
+ } else {
+ auto diskBytes{filePos - recordStart};
+ recordLength = kRecMarkSize + totalPayload;
+ unformattedDiskExtent_ = diskBytes;
+ positionInRecord = kRecMarkSize;
+ if (diskBytes > kRecMarkSize + totalPayload + kRecMarkSize) {
+ streamRead_ = true;
+ streamReadPos_ = recordStart + kRecMarkSize;
+ streamReadRemain_ = SubrecordPayloadBytes(firstHeader);
+ } else {
+ auto need{recordOffsetInFrame_ + static_cast<std::size_t>(diskBytes)};
+ if (ReadFrame(recordStart, need, handler) < need) {
+ error = "Unformatted variable-length sequential file input failed at "
+ "record #%jd (file offset %jd): hit EOF reading record with "
+ "length %jd bytes";
+ }
}
}
if (error) {
handler.SignalError(error, static_cast<std::intmax_t>(currentRecordNumber),
- static_cast<std::intmax_t>(frameOffsetInFile_),
+ static_cast<std::intmax_t>(recordStart),
static_cast<std::intmax_t>(header), static_cast<std::intmax_t>(footer));
- // TODO: error recovery
}
- positionInRecord = sizeof header;
}
void ExternalFileUnit::BeginVariableFormattedInputRecord(
@@ -653,40 +810,36 @@ void ExternalFileUnit::BackspaceFixedRecord(IoErrorHandler &handler) {
void ExternalFileUnit::BackspaceVariableUnformattedRecord(
IoErrorHandler &handler) {
- std::uint32_t header{0};
- auto headerBytes{static_cast<std::int64_t>(sizeof header)};
frameOffsetInFile_ += recordOffsetInFrame_;
recordOffsetInFrame_ = 0;
- if (frameOffsetInFile_ <= headerBytes) {
+ if (frameOffsetInFile_ <= static_cast<FileOffset>(kRecMarkSize)) {
handler.SignalError(IostatBackspaceAtFirstRecord);
return;
}
- // Error conditions here cause crashes, not file format errors, because the
- // validity of the file structure before the current record will have been
- // checked informatively in NextSequentialVariableUnformattedInputRecord().
- std::size_t got{
- ReadFrame(frameOffsetInFile_ - headerBytes, headerBytes, handler)};
- if (static_cast<std::int64_t>(got) < headerBytes) {
- handler.SignalError(IostatShortRead);
- return;
- }
- recordLength = ReadHeaderOrFooter(0);
- if (frameOffsetInFile_ < *recordLength + 2 * headerBytes) {
- handler.SignalError(IostatBadUnformattedRecord);
- return;
- }
- frameOffsetInFile_ -= *recordLength + 2 * headerBytes;
- auto need{static_cast<std::size_t>(
- recordOffsetInFrame_ + sizeof header + *recordLength)};
- got = ReadFrame(frameOffsetInFile_, need, handler);
- if (got < need) {
+ std::int32_t marker{0};
+ do {
+ if (ReadFrame(frameOffsetInFile_ - kRecMarkSize, kRecMarkSize, handler) <
+ kRecMarkSize) {
+ handler.SignalError(IostatShortRead);
+ return;
+ }
+ marker = LoadRecordMarker(Frame(), swapEndianness_);
+ auto payload{SubrecordPayloadBytes(marker)};
+ if (frameOffsetInFile_ <
+ static_cast<FileOffset>(payload + 2 * kRecMarkSize)) {
+ handler.SignalError(IostatBadUnformattedRecord);
+ return;
+ }
+ frameOffsetInFile_ -= static_cast<FileOffset>(payload + 2 * kRecMarkSize);
+ } while (marker < 0);
+ auto need{static_cast<std::size_t>(kRecMarkSize + SubrecordPayloadBytes(marker))};
+ if (ReadFrame(frameOffsetInFile_, need, handler) < need) {
handler.SignalError(IostatShortRead);
return;
}
- header = ReadHeaderOrFooter(recordOffsetInFrame_);
- if (header != *recordLength) {
+ if (SubrecordPayloadBytes(LoadRecordMarker(Frame(), swapEndianness_)) !=
+ SubrecordPayloadBytes(marker)) {
handler.SignalError(IostatBadUnformattedRecord);
- return;
}
}
@@ -845,16 +998,6 @@ void ExternalFileUnit::PopChildIo(ChildIo &child) {
child_ = previous;
}
-std::uint32_t ExternalFileUnit::ReadHeaderOrFooter(std::int64_t frameOffset) {
- std::uint32_t word;
- char *wordPtr{reinterpret_cast<char *>(&word)};
- runtime::memcpy(wordPtr, Frame() + frameOffset, sizeof word);
- if (swapEndianness_) {
- SwapEndianness(wordPtr, sizeof word, sizeof word);
- }
- return word;
-}
-
void ChildIo::EndIoStatement() {
io_.reset();
u_.emplace<std::monostate>();
diff --git a/flang-rt/lib/runtime/unit.h b/flang-rt/lib/runtime/unit.h
index a9419daf3c052..761cc249f2c82 100644
--- a/flang-rt/lib/runtime/unit.h
+++ b/flang-rt/lib/runtime/unit.h
@@ -225,7 +225,7 @@ class ExternalFileUnit : public ConnectionState,
RT_API_ATTRS void CommitWrites();
RT_API_ATTRS bool CheckDirectAccess(IoErrorHandler &);
RT_API_ATTRS void HitEndOnRead(IoErrorHandler &);
- RT_API_ATTRS std::uint32_t ReadHeaderOrFooter(std::int64_t frameOffset);
+ RT_API_ATTRS void ResetSequentialUnformattedStream();
Lock lock_;
@@ -241,6 +241,10 @@ class ExternalFileUnit : public ConnectionState,
// manage the frame and the current record therein separately.
std::int64_t frameOffsetInFile_{0};
std::size_t recordOffsetInFrame_{0}; // of currentRecordNumber
+ std::int64_t unformattedDiskExtent_{0};
+ FileOffset streamReadPos_{0};
+ std::size_t streamReadRemain_{0};
+ bool streamRead_{false};
bool swapEndianness_{false};
bool createdForInternalChildIo_{false};
common::BitSet<64> asyncIdAvailable_[maxAsyncIds / 64];
diff --git a/flang-rt/unittests/Runtime/ExternalIOTest.cpp b/flang-rt/unittests/Runtime/ExternalIOTest.cpp
index 1414dede7a2d1..56283f3382ea8 100644
--- a/flang-rt/unittests/Runtime/ExternalIOTest.cpp
+++ b/flang-rt/unittests/Runtime/ExternalIOTest.cpp
@@ -332,6 +332,105 @@ TEST(ExternalIOTests, TestSequentialVariableUnformatted) {
<< "EndIoStatement() for Close";
}
+TEST(ExternalIOTests, TestSequentialUnformattedSubrecords) {
+ static constexpr const char *fileName{"subrecord_test.tmp"};
+
+ auto emitMarker{[](std::FILE *fp, std::int32_t marker) {
+ ASSERT_EQ(std::fwrite(&marker, sizeof marker, 1, fp), 1u);
+ }};
+ auto emitValue{[](std::FILE *fp, std::int64_t value) {
+ ASSERT_EQ(std::fwrite(&value, sizeof value, 1, fp), 1u);
+ }};
+
+ static constexpr std::int64_t kRecord1[]{100, 101, 102, 103, 104, 105};
+ static constexpr std::int64_t kRecord2{999};
+ static constexpr std::int32_t kSub{16};
+
+ {
+ std::FILE *fp{std::fopen(fileName, "wb")};
+ ASSERT_NE(fp, nullptr) << "fopen(" << fileName << ") for write";
+ emitMarker(fp, -kSub);
+ emitValue(fp, kRecord1[0]);
+ emitValue(fp, kRecord1[1]);
+ emitMarker(fp, kSub);
+ emitMarker(fp, -kSub);
+ emitValue(fp, kRecord1[2]);
+ emitValue(fp, kRecord1[3]);
+ emitMarker(fp, -kSub);
+ emitMarker(fp, kSub);
+ emitValue(fp, kRecord1[4]);
+ emitValue(fp, kRecord1[5]);
+ emitMarker(fp, -kSub);
+ emitMarker(fp, sizeof kRecord2);
+ emitValue(fp, kRecord2);
+ emitMarker(fp, sizeof kRecord2);
+ ASSERT_EQ(std::fclose(fp), 0) << "fclose after write";
+ }
+
+ // OPEN(UNIT=unit,FILE=fileName,ACCESS='SEQUENTIAL',ACTION='READ',&
+ // FORM='UNFORMATTED',STATUS='OLD')
+ static constexpr int unit{20};
+ Cookie io{IONAME(BeginOpenUnit)(unit, __FILE__, __LINE__)};
+ ASSERT_TRUE(IONAME(SetFile)(io, fileName, std::strlen(fileName)))
+ << "SetFile()";
+ ASSERT_TRUE(IONAME(SetAccess)(io, "SEQUENTIAL", 10)) << "SetAccess(SEQUENTIAL)";
+ ASSERT_TRUE(IONAME(SetAction)(io, "READ", 4)) << "SetAction(READ)";
+ ASSERT_TRUE(IONAME(SetForm)(io, "UNFORMATTED", 11)) << "SetForm(UNFORMATTED)";
+ ASSERT_TRUE(IONAME(SetStatus)(io, "OLD", 3)) << "SetStatus(OLD)";
+ ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) << "EndIoStatement() for OPEN";
+
+ StaticDescriptor<0> staticDescriptor;
+ Descriptor &desc{staticDescriptor.descriptor()};
+
+ auto readRecord1{[&](const char *what) {
+ std::int64_t buffer[6]{};
+ io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__);
+ desc.Establish(TypeCode{sizeof buffer[0]}, sizeof buffer, buffer, 0);
+ desc.Check();
+ ASSERT_TRUE(IONAME(InputDescriptor)(io, desc)) << "InputDescriptor() " << what;
+ ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
+ << "EndIoStatement() for READ record #1 " << what;
+ for (int k{0}; k < 6; ++k) {
+ ASSERT_EQ(buffer[k], kRecord1[k])
+ << "record #1 " << what << " element " << k << " read back "
+ << buffer[k] << ", expected " << kRecord1[k];
+ }
+ }};
+
+ auto readRecord2{[&](const char *what) {
+ std::int64_t value{0};
+ io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__);
+ desc.Establish(TypeCode{sizeof value}, sizeof value, &value, 0);
+ desc.Check();
+ ASSERT_TRUE(IONAME(InputDescriptor)(io, desc)) << "InputDescriptor() " << what;
+ ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
+ << "EndIoStatement() for READ record #2 " << what;
+ ASSERT_EQ(value, kRecord2) << "record #2 " << what << " read back " << value
+ << ", expected " << kRecord2;
+ }};
+
+ readRecord1("forward");
+ readRecord2("forward");
+
+ io = IONAME(BeginBackspace)(unit, __FILE__, __LINE__);
+ ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
+ << "EndIoStatement() for Backspace over record #2";
+ readRecord2("after backspace");
+
+ io = IONAME(BeginBackspace)(unit, __FILE__, __LINE__);
+ ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
+ << "EndIoStatement() for Backspace over record #2 (second time)";
+ io = IONAME(BeginBackspace)(unit, __FILE__, __LINE__);
+ ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
+ << "EndIoStatement() for Backspace over record #1";
+ readRecord1("after backspace");
+
+ // CLOSE(UNIT=unit,STATUS='DELETE')
+ io = IONAME(BeginClose)(unit, __FILE__, __LINE__);
+ ASSERT_TRUE(IONAME(SetStatus)(io, "DELETE", 6)) << "SetStatus(DELETE)";
+ ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) << "EndIoStatement() for Close";
+}
+
TEST(ExternalIOTests, TestDirectFormatted) {
// OPEN(NEWUNIT=unit,ACCESS='DIRECT',ACTION='READWRITE',&
// FORM='FORMATTED',RECL=8,STATUS='SCRATCH')
More information about the llvm-commits
mailing list