[flang][runtime] Formatted input optimizations (#134715)
Make some minor tweaks (inlining, caching) to the formatting input path to improve integer input in a SPEC code. (None of the I/O library has been tuned yet for performance, and there are some easy optimizations for common cases.) Input integer values are now calculated with native C/C++ 128-bit integers. A benchmark that only reads about 5M lines of three integer values each speeds up from over 8 seconds to under 3 in my environment with these changeds. If this works out, the code here can be used to optimize the formatted input paths for real and character data, too. Fixes https://github.com/llvm/llvm-project/issues/134026.
This commit is contained in:
@@ -45,19 +45,36 @@ struct ConnectionAttributes {
|
||||
};
|
||||
|
||||
struct ConnectionState : public ConnectionAttributes {
|
||||
RT_API_ATTRS bool
|
||||
IsAtEOF() const; // true when read has hit EOF or endfile record
|
||||
RT_API_ATTRS bool
|
||||
IsAfterEndfile() const; // true after ENDFILE until repositioned
|
||||
RT_API_ATTRS bool IsAtEOF() const {
|
||||
// true when read has hit EOF or endfile record
|
||||
return endfileRecordNumber && currentRecordNumber >= *endfileRecordNumber;
|
||||
}
|
||||
RT_API_ATTRS bool IsAfterEndfile() const {
|
||||
// true after ENDFILE until repositioned
|
||||
return endfileRecordNumber && currentRecordNumber > *endfileRecordNumber;
|
||||
}
|
||||
|
||||
// All positions and measurements are always in units of bytes,
|
||||
// not characters. Multi-byte character encodings are possible in
|
||||
// both internal I/O (when the character kind of the variable is 2 or 4)
|
||||
// and external formatted I/O (when the encoding is UTF-8).
|
||||
RT_API_ATTRS std::size_t RemainingSpaceInRecord() const;
|
||||
RT_API_ATTRS bool NeedAdvance(std::size_t) const;
|
||||
RT_API_ATTRS void HandleAbsolutePosition(std::int64_t);
|
||||
RT_API_ATTRS void HandleRelativePosition(std::int64_t);
|
||||
RT_API_ATTRS std::size_t RemainingSpaceInRecord() const {
|
||||
auto recl{recordLength.value_or(openRecl.value_or(
|
||||
executionEnvironment.listDirectedOutputLineLengthLimit))};
|
||||
return positionInRecord >= recl ? 0 : recl - positionInRecord;
|
||||
}
|
||||
RT_API_ATTRS bool NeedAdvance(std::size_t width) const {
|
||||
return positionInRecord > 0 && width > RemainingSpaceInRecord();
|
||||
}
|
||||
RT_API_ATTRS void HandleAbsolutePosition(std::int64_t n) {
|
||||
positionInRecord = (n < 0 ? 0 : n) + leftTabLimit.value_or(0);
|
||||
}
|
||||
RT_API_ATTRS void HandleRelativePosition(std::int64_t n) {
|
||||
auto least{leftTabLimit.value_or(0)};
|
||||
auto newPos{positionInRecord + n};
|
||||
positionInRecord = newPos < least ? least : newPos;
|
||||
;
|
||||
}
|
||||
|
||||
RT_API_ATTRS void BeginRecord() {
|
||||
positionInRecord = 0;
|
||||
|
||||
@@ -130,20 +130,94 @@ public:
|
||||
}
|
||||
|
||||
// Vacant after the end of the current record
|
||||
RT_API_ATTRS Fortran::common::optional<char32_t> GetCurrentChar(
|
||||
RT_API_ATTRS Fortran::common::optional<char32_t> GetCurrentCharSlow(
|
||||
std::size_t &byteCount);
|
||||
|
||||
// For faster formatted input editing, this structure can be built by
|
||||
// GetUpcomingFastAsciiField() and used to save significant time in
|
||||
// GetCurrentChar, NextInField() and other input utilities when the input
|
||||
// is buffered, does not require UTF-8 conversion, and comprises only
|
||||
// single byte characters.
|
||||
class FastAsciiField {
|
||||
public:
|
||||
RT_API_ATTRS FastAsciiField(ConnectionState &connection)
|
||||
: connection_{connection} {}
|
||||
RT_API_ATTRS FastAsciiField(
|
||||
ConnectionState &connection, const char *start, std::size_t bytes)
|
||||
: connection_{connection}, at_{start}, limit_{start + bytes} {
|
||||
CheckForAsterisk();
|
||||
}
|
||||
RT_API_ATTRS ConnectionState &connection() { return connection_; }
|
||||
RT_API_ATTRS std::size_t got() const { return got_; }
|
||||
|
||||
RT_API_ATTRS bool MustUseSlowPath() const { return at_ == nullptr; }
|
||||
|
||||
RT_API_ATTRS Fortran::common::optional<char32_t> Next() const {
|
||||
if (at_ && at_ < limit_) {
|
||||
return *at_;
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
RT_API_ATTRS void NextRecord(IoStatementState &io) {
|
||||
if (at_) {
|
||||
if (std::size_t bytes{io.GetNextInputBytes(at_)}) {
|
||||
limit_ = at_ + bytes;
|
||||
CheckForAsterisk();
|
||||
} else {
|
||||
at_ = limit_ = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
RT_API_ATTRS void Advance(int gotten, std::size_t bytes) {
|
||||
if (at_ && at_ < limit_) {
|
||||
++at_;
|
||||
got_ += gotten;
|
||||
}
|
||||
connection_.HandleRelativePosition(bytes);
|
||||
}
|
||||
RT_API_ATTRS bool MightHaveAsterisk() const { return !at_ || hasAsterisk_; }
|
||||
|
||||
private:
|
||||
RT_API_ATTRS void CheckForAsterisk() {
|
||||
hasAsterisk_ =
|
||||
at_ && at_ < limit_ && std::memchr(at_, '*', limit_ - at_) != nullptr;
|
||||
}
|
||||
|
||||
ConnectionState &connection_;
|
||||
const char *at_{nullptr};
|
||||
const char *limit_{nullptr};
|
||||
std::size_t got_{0}; // for READ(..., SIZE=)
|
||||
bool hasAsterisk_{false};
|
||||
};
|
||||
|
||||
RT_API_ATTRS FastAsciiField GetUpcomingFastAsciiField();
|
||||
|
||||
RT_API_ATTRS Fortran::common::optional<char32_t> GetCurrentChar(
|
||||
std::size_t &byteCount, FastAsciiField *field = nullptr) {
|
||||
if (field) {
|
||||
if (auto ch{field->Next()}) {
|
||||
byteCount = ch ? 1 : 0;
|
||||
return ch;
|
||||
} else if (!field->MustUseSlowPath()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
return GetCurrentCharSlow(byteCount);
|
||||
}
|
||||
|
||||
// The result of CueUpInput() and the "remaining" arguments to SkipSpaces()
|
||||
// and NextInField() are always in units of bytes, not characters; the
|
||||
// distinction matters for internal input from CHARACTER(KIND=2 and 4).
|
||||
|
||||
// For fixed-width fields, return the number of remaining bytes.
|
||||
// Skip over leading blanks.
|
||||
RT_API_ATTRS Fortran::common::optional<int> CueUpInput(const DataEdit &edit) {
|
||||
RT_API_ATTRS Fortran::common::optional<int> CueUpInput(
|
||||
const DataEdit &edit, FastAsciiField *fastField = nullptr) {
|
||||
Fortran::common::optional<int> remaining;
|
||||
if (edit.IsListDirected()) {
|
||||
std::size_t byteCount{0};
|
||||
GetNextNonBlank(byteCount);
|
||||
GetNextNonBlank(byteCount, fastField);
|
||||
} else {
|
||||
if (edit.width.value_or(0) > 0) {
|
||||
remaining = *edit.width;
|
||||
@@ -152,16 +226,17 @@ public:
|
||||
*remaining *= bytesPerChar;
|
||||
}
|
||||
}
|
||||
SkipSpaces(remaining);
|
||||
SkipSpaces(remaining, fastField);
|
||||
}
|
||||
return remaining;
|
||||
}
|
||||
|
||||
RT_API_ATTRS Fortran::common::optional<char32_t> SkipSpaces(
|
||||
Fortran::common::optional<int> &remaining) {
|
||||
Fortran::common::optional<int> &remaining,
|
||||
FastAsciiField *fastField = nullptr) {
|
||||
while (!remaining || *remaining > 0) {
|
||||
std::size_t byteCount{0};
|
||||
if (auto ch{GetCurrentChar(byteCount)}) {
|
||||
if (auto ch{GetCurrentChar(byteCount, fastField)}) {
|
||||
if (*ch != ' ' && *ch != '\t') {
|
||||
return ch;
|
||||
}
|
||||
@@ -172,7 +247,11 @@ public:
|
||||
GotChar(byteCount);
|
||||
*remaining -= byteCount;
|
||||
}
|
||||
HandleRelativePosition(byteCount);
|
||||
if (fastField) {
|
||||
fastField->Advance(0, byteCount);
|
||||
} else {
|
||||
HandleRelativePosition(byteCount);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -183,25 +262,35 @@ public:
|
||||
// Acquires the next input character, respecting any applicable field width
|
||||
// or separator character.
|
||||
RT_API_ATTRS Fortran::common::optional<char32_t> NextInField(
|
||||
Fortran::common::optional<int> &remaining, const DataEdit &);
|
||||
Fortran::common::optional<int> &remaining, const DataEdit &,
|
||||
FastAsciiField *field = nullptr);
|
||||
|
||||
// Detect and signal any end-of-record condition after input.
|
||||
// Returns true if at EOR and remaining input should be padded with blanks.
|
||||
RT_API_ATTRS bool CheckForEndOfRecord(std::size_t afterReading);
|
||||
RT_API_ATTRS bool CheckForEndOfRecord(
|
||||
std::size_t afterReading, const ConnectionState &);
|
||||
|
||||
// Skips spaces, advances records, and ignores NAMELIST comments
|
||||
RT_API_ATTRS Fortran::common::optional<char32_t> GetNextNonBlank(
|
||||
std::size_t &byteCount) {
|
||||
auto ch{GetCurrentChar(byteCount)};
|
||||
std::size_t &byteCount, FastAsciiField *fastField = nullptr) {
|
||||
auto ch{GetCurrentChar(byteCount, fastField)};
|
||||
bool inNamelist{mutableModes().inNamelist};
|
||||
while (!ch || *ch == ' ' || *ch == '\t' || *ch == '\n' ||
|
||||
(inNamelist && *ch == '!')) {
|
||||
if (ch && (*ch == ' ' || *ch == '\t' || *ch == '\n')) {
|
||||
HandleRelativePosition(byteCount);
|
||||
} else if (!AdvanceRecord()) {
|
||||
if (fastField) {
|
||||
fastField->Advance(0, byteCount);
|
||||
} else {
|
||||
HandleRelativePosition(byteCount);
|
||||
}
|
||||
} else if (AdvanceRecord()) {
|
||||
if (fastField) {
|
||||
fastField->NextRecord(*this);
|
||||
}
|
||||
} else {
|
||||
return Fortran::common::nullopt;
|
||||
}
|
||||
ch = GetCurrentChar(byteCount);
|
||||
ch = GetCurrentChar(byteCount, fastField);
|
||||
}
|
||||
return ch;
|
||||
}
|
||||
|
||||
@@ -9,37 +9,10 @@
|
||||
#include "flang-rt/runtime/connection.h"
|
||||
#include "flang-rt/runtime/environment.h"
|
||||
#include "flang-rt/runtime/io-stmt.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace Fortran::runtime::io {
|
||||
RT_OFFLOAD_API_GROUP_BEGIN
|
||||
|
||||
RT_API_ATTRS std::size_t ConnectionState::RemainingSpaceInRecord() const {
|
||||
auto recl{recordLength.value_or(openRecl.value_or(
|
||||
executionEnvironment.listDirectedOutputLineLengthLimit))};
|
||||
return positionInRecord >= recl ? 0 : recl - positionInRecord;
|
||||
}
|
||||
|
||||
RT_API_ATTRS bool ConnectionState::NeedAdvance(std::size_t width) const {
|
||||
return positionInRecord > 0 && width > RemainingSpaceInRecord();
|
||||
}
|
||||
|
||||
RT_API_ATTRS bool ConnectionState::IsAtEOF() const {
|
||||
return endfileRecordNumber && currentRecordNumber >= *endfileRecordNumber;
|
||||
}
|
||||
|
||||
RT_API_ATTRS bool ConnectionState::IsAfterEndfile() const {
|
||||
return endfileRecordNumber && currentRecordNumber > *endfileRecordNumber;
|
||||
}
|
||||
|
||||
RT_API_ATTRS void ConnectionState::HandleAbsolutePosition(std::int64_t n) {
|
||||
positionInRecord = std::max(n, std::int64_t{0}) + leftTabLimit.value_or(0);
|
||||
}
|
||||
|
||||
RT_API_ATTRS void ConnectionState::HandleRelativePosition(std::int64_t n) {
|
||||
positionInRecord = std::max(leftTabLimit.value_or(0), positionInRecord + n);
|
||||
}
|
||||
|
||||
SavedPosition::SavedPosition(IoStatementState &io) : io_{io} {
|
||||
ConnectionState &conn{io_.GetConnectionState()};
|
||||
saved_ = conn;
|
||||
|
||||
@@ -169,17 +169,18 @@ static inline RT_API_ATTRS char32_t GetRadixPointChar(const DataEdit &edit) {
|
||||
// Prepares input from a field, and returns the sign, if any, else '\0'.
|
||||
static RT_API_ATTRS char ScanNumericPrefix(IoStatementState &io,
|
||||
const DataEdit &edit, Fortran::common::optional<char32_t> &next,
|
||||
Fortran::common::optional<int> &remaining) {
|
||||
remaining = io.CueUpInput(edit);
|
||||
next = io.NextInField(remaining, edit);
|
||||
Fortran::common::optional<int> &remaining,
|
||||
IoStatementState::FastAsciiField *fastField = nullptr) {
|
||||
remaining = io.CueUpInput(edit, fastField);
|
||||
next = io.NextInField(remaining, edit, fastField);
|
||||
char sign{'\0'};
|
||||
if (next) {
|
||||
if (*next == '-' || *next == '+') {
|
||||
sign = *next;
|
||||
if (!edit.IsListDirected()) {
|
||||
io.SkipSpaces(remaining);
|
||||
io.SkipSpaces(remaining, fastField);
|
||||
}
|
||||
next = io.NextInField(remaining, edit);
|
||||
next = io.NextInField(remaining, edit, fastField);
|
||||
}
|
||||
}
|
||||
return sign;
|
||||
@@ -213,17 +214,18 @@ RT_API_ATTRS bool EditIntegerInput(IoStatementState &io, const DataEdit &edit,
|
||||
}
|
||||
Fortran::common::optional<int> remaining;
|
||||
Fortran::common::optional<char32_t> next;
|
||||
char sign{ScanNumericPrefix(io, edit, next, remaining)};
|
||||
auto fastField{io.GetUpcomingFastAsciiField()};
|
||||
char sign{ScanNumericPrefix(io, edit, next, remaining, &fastField)};
|
||||
if (sign == '-' && !isSigned) {
|
||||
io.GetIoErrorHandler().SignalError("Negative sign in UNSIGNED input field");
|
||||
return false;
|
||||
}
|
||||
common::UnsignedInt128 value{0};
|
||||
common::uint128_t value{0};
|
||||
bool any{!!sign};
|
||||
bool overflow{false};
|
||||
const char32_t comma{GetSeparatorChar(edit)};
|
||||
static constexpr auto maxu128{~common::UnsignedInt128{0}};
|
||||
for (; next; next = io.NextInField(remaining, edit)) {
|
||||
static constexpr auto maxu128{~common::uint128_t{0}};
|
||||
for (; next; next = io.NextInField(remaining, edit, &fastField)) {
|
||||
char32_t ch{*next};
|
||||
if (ch == ' ' || ch == '\t') {
|
||||
if (edit.modes.editingFlags & blankZero) {
|
||||
@@ -243,7 +245,7 @@ RT_API_ATTRS bool EditIntegerInput(IoStatementState &io, const DataEdit &edit,
|
||||
// input, like a few other Fortran compilers do.
|
||||
// TODO: also process exponents? Some compilers do, but they obviously
|
||||
// can't just be ignored.
|
||||
while ((next = io.NextInField(remaining, edit))) {
|
||||
while ((next = io.NextInField(remaining, edit, &fastField))) {
|
||||
if (*next < '0' || *next > '9') {
|
||||
break;
|
||||
}
|
||||
@@ -271,7 +273,7 @@ RT_API_ATTRS bool EditIntegerInput(IoStatementState &io, const DataEdit &edit,
|
||||
return false;
|
||||
}
|
||||
if (isSigned) {
|
||||
auto maxForKind{common::UnsignedInt128{1} << ((8 * kind) - 1)};
|
||||
auto maxForKind{common::uint128_t{1} << ((8 * kind) - 1)};
|
||||
overflow |= value >= maxForKind && (value > maxForKind || sign != '-');
|
||||
} else {
|
||||
auto maxForKind{maxu128 >> (((16 - kind) * 8) + (isSigned ? 1 : 0))};
|
||||
@@ -287,7 +289,16 @@ RT_API_ATTRS bool EditIntegerInput(IoStatementState &io, const DataEdit &edit,
|
||||
}
|
||||
if (any || !io.GetIoErrorHandler().InError()) {
|
||||
// The value is stored in the lower order bits on big endian platform.
|
||||
// When memcpy, shift the value to the higher order bit.
|
||||
// For memcpy, shift the value to the highest order bits.
|
||||
#if USING_NATIVE_INT128_T
|
||||
auto shft{static_cast<int>(sizeof value - kind)};
|
||||
if (!isHostLittleEndian && shft >= 0) {
|
||||
auto l{value << shft};
|
||||
std::memcpy(n, &l, kind);
|
||||
} else {
|
||||
std::memcpy(n, &value, kind); // a blank field means zero
|
||||
}
|
||||
#else
|
||||
auto shft{static_cast<int>(sizeof(value.low())) - kind};
|
||||
// For kind==8 (i.e. shft==0), the value is stored in low_ in big endian.
|
||||
if (!isHostLittleEndian && shft >= 0) {
|
||||
@@ -296,6 +307,8 @@ RT_API_ATTRS bool EditIntegerInput(IoStatementState &io, const DataEdit &edit,
|
||||
} else {
|
||||
std::memcpy(n, &value, kind); // a blank field means zero
|
||||
}
|
||||
#endif
|
||||
io.GotChar(fastField.got());
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -1070,7 +1083,7 @@ RT_API_ATTRS bool EditCharacterInput(IoStatementState &io, const DataEdit &edit,
|
||||
readyBytes = io.GetNextInputBytes(input);
|
||||
if (readyBytes == 0 ||
|
||||
(readyBytes < remainingChars && edit.modes.nonAdvancing)) {
|
||||
if (io.CheckForEndOfRecord(readyBytes)) {
|
||||
if (io.CheckForEndOfRecord(readyBytes, connection)) {
|
||||
if (readyBytes == 0) {
|
||||
// PAD='YES' and no more data
|
||||
Fortran::runtime::fill_n(x, lengthChars, ' ');
|
||||
|
||||
@@ -1057,14 +1057,15 @@ bool IODEF(InputDescriptor)(Cookie cookie, const Descriptor &descriptor) {
|
||||
}
|
||||
|
||||
bool IODEF(InputInteger)(Cookie cookie, std::int64_t &n, int kind) {
|
||||
if (!cookie->CheckFormattedStmtType<Direction::Input>("InputInteger")) {
|
||||
return false;
|
||||
IoStatementState &io{*cookie};
|
||||
if (io.BeginReadingRecord()) {
|
||||
if (auto edit{io.GetNextDataEdit()}) {
|
||||
return edit->descriptor == DataEdit::ListDirectedNullValue ||
|
||||
EditIntegerInput(io, *edit, reinterpret_cast<void *>(&n), kind,
|
||||
/*isSigned=*/true);
|
||||
}
|
||||
}
|
||||
StaticDescriptor<0> staticDescriptor;
|
||||
Descriptor &descriptor{staticDescriptor.descriptor()};
|
||||
descriptor.Establish(
|
||||
TypeCategory::Integer, kind, reinterpret_cast<void *>(&n), 0);
|
||||
return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IODEF(InputReal32)(Cookie cookie, float &x) {
|
||||
|
||||
@@ -596,7 +596,7 @@ ExternalFileUnit *IoStatementState::GetExternalFileUnit() const {
|
||||
[](auto &x) { return x.get().GetExternalFileUnit(); }, u_);
|
||||
}
|
||||
|
||||
Fortran::common::optional<char32_t> IoStatementState::GetCurrentChar(
|
||||
Fortran::common::optional<char32_t> IoStatementState::GetCurrentCharSlow(
|
||||
std::size_t &byteCount) {
|
||||
const char *p{nullptr};
|
||||
std::size_t bytes{GetNextInputBytes(p)};
|
||||
@@ -628,12 +628,24 @@ Fortran::common::optional<char32_t> IoStatementState::GetCurrentChar(
|
||||
}
|
||||
}
|
||||
|
||||
IoStatementState::FastAsciiField IoStatementState::GetUpcomingFastAsciiField() {
|
||||
ConnectionState &connection{GetConnectionState()};
|
||||
if (!connection.isUTF8 && connection.internalIoCharKind <= 1) {
|
||||
const char *p{nullptr};
|
||||
if (std::size_t bytes{GetNextInputBytes(p)}) {
|
||||
return FastAsciiField(connection, p, bytes);
|
||||
}
|
||||
}
|
||||
return FastAsciiField(connection);
|
||||
}
|
||||
|
||||
Fortran::common::optional<char32_t> IoStatementState::NextInField(
|
||||
Fortran::common::optional<int> &remaining, const DataEdit &edit) {
|
||||
Fortran::common::optional<int> &remaining, const DataEdit &edit,
|
||||
FastAsciiField *field) {
|
||||
std::size_t byteCount{0};
|
||||
if (!remaining) { // Stream, list-directed, NAMELIST, &c.
|
||||
if (auto next{GetCurrentChar(byteCount)}) {
|
||||
if (edit.width.value_or(0) == 0) {
|
||||
if (auto next{GetCurrentChar(byteCount, field)}) {
|
||||
if ((*next < '0' || *next == ';') && edit.width.value_or(0) == 0) {
|
||||
// list-directed, NAMELIST, I0 &c., or width-free I/G:
|
||||
// check for separator character
|
||||
switch (*next) {
|
||||
@@ -667,21 +679,30 @@ Fortran::common::optional<char32_t> IoStatementState::NextInField(
|
||||
break;
|
||||
}
|
||||
}
|
||||
HandleRelativePosition(byteCount);
|
||||
GotChar(byteCount);
|
||||
if (field) {
|
||||
field->Advance(1, byteCount);
|
||||
} else {
|
||||
HandleRelativePosition(byteCount);
|
||||
GotChar(byteCount);
|
||||
}
|
||||
return next;
|
||||
}
|
||||
} else if (*remaining > 0) {
|
||||
if (auto next{GetCurrentChar(byteCount)}) {
|
||||
if (auto next{GetCurrentChar(byteCount, field)}) {
|
||||
if (byteCount > static_cast<std::size_t>(*remaining)) {
|
||||
return Fortran::common::nullopt;
|
||||
}
|
||||
*remaining -= byteCount;
|
||||
HandleRelativePosition(byteCount);
|
||||
GotChar(byteCount);
|
||||
if (field) {
|
||||
field->Advance(1, byteCount);
|
||||
} else {
|
||||
HandleRelativePosition(byteCount);
|
||||
GotChar(byteCount);
|
||||
}
|
||||
return next;
|
||||
}
|
||||
if (CheckForEndOfRecord(0)) { // do padding
|
||||
if (CheckForEndOfRecord(0,
|
||||
field ? field->connection() : GetConnectionState())) { // do padding
|
||||
--*remaining;
|
||||
return Fortran::common::optional<char32_t>{' '};
|
||||
}
|
||||
@@ -689,8 +710,8 @@ Fortran::common::optional<char32_t> IoStatementState::NextInField(
|
||||
return Fortran::common::nullopt;
|
||||
}
|
||||
|
||||
bool IoStatementState::CheckForEndOfRecord(std::size_t afterReading) {
|
||||
const ConnectionState &connection{GetConnectionState()};
|
||||
bool IoStatementState::CheckForEndOfRecord(
|
||||
std::size_t afterReading, const ConnectionState &connection) {
|
||||
if (!connection.IsAtEOF()) {
|
||||
if (auto length{connection.EffectiveRecordLength()}) {
|
||||
if (connection.positionInRecord +
|
||||
@@ -799,7 +820,6 @@ Fortran::common::optional<DataEdit>
|
||||
ListDirectedStatementState<Direction::Input>::GetNextDataEdit(
|
||||
IoStatementState &io, int maxRepeat) {
|
||||
// N.B. list-directed transfers cannot be nonadvancing (C1221)
|
||||
ConnectionState &connection{io.GetConnectionState()};
|
||||
DataEdit edit;
|
||||
edit.descriptor = DataEdit::ListDirected;
|
||||
edit.repeat = 1; // may be overridden below
|
||||
@@ -840,14 +860,15 @@ ListDirectedStatementState<Direction::Input>::GetNextDataEdit(
|
||||
imaginaryPart_ = true;
|
||||
edit.descriptor = DataEdit::ListDirectedImaginaryPart;
|
||||
}
|
||||
auto ch{io.GetNextNonBlank(byteCount)};
|
||||
auto fastField{io.GetUpcomingFastAsciiField()};
|
||||
auto ch{io.GetNextNonBlank(byteCount, &fastField)};
|
||||
if (ch && *ch == comma && eatComma_) {
|
||||
// Consume comma & whitespace after previous item.
|
||||
// This includes the comma between real and imaginary components
|
||||
// in list-directed/NAMELIST complex input.
|
||||
// (When DECIMAL='COMMA', the comma is actually a semicolon.)
|
||||
io.HandleRelativePosition(byteCount);
|
||||
ch = io.GetNextNonBlank(byteCount);
|
||||
fastField.Advance(0, byteCount);
|
||||
ch = io.GetNextNonBlank(byteCount, &fastField);
|
||||
}
|
||||
eatComma_ = true;
|
||||
if (!ch) {
|
||||
@@ -865,8 +886,9 @@ ListDirectedStatementState<Direction::Input>::GetNextDataEdit(
|
||||
if (imaginaryPart_) { // can't repeat components
|
||||
return edit;
|
||||
}
|
||||
if (*ch >= '0' && *ch <= '9') { // look for "r*" repetition count
|
||||
auto start{connection.positionInRecord};
|
||||
if (*ch >= '0' && *ch <= '9' && fastField.MightHaveAsterisk()) {
|
||||
// look for "r*" repetition count
|
||||
auto start{fastField.connection().positionInRecord};
|
||||
int r{0};
|
||||
do {
|
||||
static auto constexpr clamp{(std::numeric_limits<int>::max() - '9') / 10};
|
||||
@@ -875,12 +897,12 @@ ListDirectedStatementState<Direction::Input>::GetNextDataEdit(
|
||||
break;
|
||||
}
|
||||
r = 10 * r + (*ch - '0');
|
||||
io.HandleRelativePosition(byteCount);
|
||||
ch = io.GetCurrentChar(byteCount);
|
||||
fastField.Advance(0, byteCount);
|
||||
ch = io.GetCurrentChar(byteCount, &fastField);
|
||||
} while (ch && *ch >= '0' && *ch <= '9');
|
||||
if (r > 0 && ch && *ch == '*') { // subtle: r must be nonzero
|
||||
io.HandleRelativePosition(byteCount);
|
||||
ch = io.GetCurrentChar(byteCount);
|
||||
fastField.Advance(0, byteCount);
|
||||
ch = io.GetCurrentChar(byteCount, &fastField);
|
||||
if (ch && *ch == '/') { // r*/
|
||||
hitSlash_ = true;
|
||||
edit.descriptor = DataEdit::ListDirectedNullValue;
|
||||
@@ -895,12 +917,12 @@ ListDirectedStatementState<Direction::Input>::GetNextDataEdit(
|
||||
repeatPosition_.emplace(io);
|
||||
}
|
||||
} else { // not a repetition count, just an integer value; rewind
|
||||
connection.positionInRecord = start;
|
||||
fastField.connection().positionInRecord = start;
|
||||
}
|
||||
}
|
||||
if (!imaginaryPart_ && ch && *ch == '(') {
|
||||
realPart_ = true;
|
||||
io.HandleRelativePosition(byteCount);
|
||||
fastField.connection().HandleRelativePosition(byteCount);
|
||||
edit.descriptor = DataEdit::ListDirectedRealPart;
|
||||
}
|
||||
return edit;
|
||||
|
||||
@@ -135,6 +135,13 @@ bool ExternalFileUnit::Receive(char *data, std::size_t bytes,
|
||||
std::size_t ExternalFileUnit::GetNextInputBytes(
|
||||
const char *&p, IoErrorHandler &handler) {
|
||||
RUNTIME_CHECK(handler, direction_ == Direction::Input);
|
||||
if (access == Access::Sequential &&
|
||||
positionInRecord < recordLength.value_or(positionInRecord)) {
|
||||
// Fast path for variable-length formatted input: the whole record
|
||||
// must be in frame as a result of newline detection for record length.
|
||||
p = Frame() + recordOffsetInFrame_ + positionInRecord;
|
||||
return *recordLength - positionInRecord;
|
||||
}
|
||||
std::size_t length{1};
|
||||
if (auto recl{EffectiveRecordLength()}) {
|
||||
if (positionInRecord < *recl) {
|
||||
@@ -443,7 +450,6 @@ void ExternalFileUnit::Rewind(IoErrorHandler &handler) {
|
||||
DoImpliedEndfile(handler);
|
||||
SetPosition(0);
|
||||
currentRecordNumber = 1;
|
||||
leftTabLimit.reset();
|
||||
anyWriteSinceLastPositioning_ = false;
|
||||
}
|
||||
}
|
||||
@@ -455,6 +461,8 @@ void ExternalFileUnit::SetPosition(std::int64_t pos) {
|
||||
directAccessRecWasSet_ = true;
|
||||
}
|
||||
BeginRecord();
|
||||
beganReadingRecord_ = false; // for positioning after nonadvancing input
|
||||
leftTabLimit.reset();
|
||||
}
|
||||
|
||||
void ExternalFileUnit::Sought(std::int64_t zeroBasedPos) {
|
||||
|
||||
@@ -278,9 +278,11 @@ using SignedInt128 = Int128<true>;
|
||||
|
||||
#if !AVOID_NATIVE_UINT128_T && (defined __GNUC__ || defined __clang__) && \
|
||||
defined __SIZEOF_INT128__
|
||||
#define USING_NATIVE_INT128_T 1
|
||||
using uint128_t = __uint128_t;
|
||||
using int128_t = __int128_t;
|
||||
#else
|
||||
#undef USING_NATIVE_INT128_T
|
||||
using uint128_t = UnsignedInt128;
|
||||
using int128_t = SignedInt128;
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user