QLocale: make qstrnto(u)ll not have output arguments

That is, return everything in the return argument. On the SysV ABI, that
means everything gets returned in registers, in both 32- and 64-bit
platforms (unlike QtPrivate::ParsedNumber). There's a minor but
perceptible performance improvement in parsing strings and byte arrays.

Before:
Parsed string   "42"    "1234"  "-1548860221"
Clock (ns)      16.673  18.878  25.517
CPU cycles      46.548  52.704  71.243
Instructions    201     233     331

After:
Parsed string   "42"    "1234"  "-1548860221"
Clock (ns)      15.577  17.998  24.198
CPU cycles      43.491  49.942  67.552
Instructions    179     211     308

On my Core i7-1165G7 @ 2.80 GHz, the 22-23 instruction gain per
iteration results in half the expected clock gain in runtime (22 /
2.8 GHz = 7.8 ns) because of a slightly lower instruction per cycle
rate. That's acceptable because we need less speculative execution.

Task-number: QTBUG-107788
Change-Id: I07ec23f3cb174fb197c3fffd17220fd64d473cc0
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
(cherry picked from commit d50d34e5de7f5cf5e34243210e3df519974d7794)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Thiago Macieira 2022-10-27 15:55:42 -07:00 committed by Qt Cherry-pick Bot
parent 1f92a4005c
commit 0ff4e7d4a3
10 changed files with 94 additions and 122 deletions

View File

@ -60,11 +60,9 @@ static bool parseIp4Internal(IPv4Address &address, const char *ptr, bool acceptL
ptr[1] != '.' && ptr[1] != '\0')
return false;
const char *endptr;
bool ok;
quint64 ll = qstrntoull(ptr, stop - ptr, &endptr, 0, &ok);
auto [ll, endptr] = qstrntoull(ptr, stop - ptr, 0);
quint32 x = ll;
if (!ok || endptr == ptr || ll != x)
if (!endptr || endptr == ptr || ll != x)
return false;
if (*endptr == '.' || dotCount == 3) {
@ -176,15 +174,13 @@ const QChar *parseIp6(IPv6Address &address, const QChar *begin, const QChar *end
continue;
}
const char *endptr;
bool ok;
quint64 ll = qstrntoull(ptr, stop - ptr, &endptr, 16, &ok);
auto [ll, endptr] = qstrntoull(ptr, stop - ptr, 16);
quint16 x = ll;
// Reject malformed fields:
// - failed to parse
// - too many hex digits
if (!ok || endptr > ptr + 4)
if (!endptr || endptr > ptr + 4)
return begin + (ptr - buffer.data());
if (*endptr == '.') {

View File

@ -451,32 +451,35 @@ inline bool QStorageIterator::next()
const char *const stop = ptr + len - 1;
// parse the line
bool ok;
mnt.mnt_freq = 0;
mnt.mnt_passno = 0;
mnt.mount_id = qstrntoll(ptr, stop - ptr, const_cast<const char **>(&ptr), 10, &ok);
if (!ok)
auto r = qstrntoll(ptr, stop - ptr, 10);
if (!r.ok())
return false;
mnt.mount_id = r.result;
int parent_id = qstrntoll(ptr, stop - ptr, const_cast<const char **>(&ptr), 10, &ok);
r = qstrntoll(r.endptr, stop - r.endptr, 10);
if (!r.ok())
return false;
int parent_id = r.result;
Q_UNUSED(parent_id);
if (!ok)
return false;
int rdevmajor = qstrntoll(ptr, stop - ptr, const_cast<const char **>(&ptr), 10, &ok);
if (!ok)
return false;
if (*ptr != ':')
return false;
int rdevminor = qstrntoll(ptr + 1, stop - ptr - 1, const_cast<const char **>(&ptr), 10, &ok);
if (!ok)
return false;
mnt.rdev = makedev(rdevmajor, rdevminor);
if (*ptr != ' ')
r = qstrntoll(r.endptr, stop - r.endptr, 10);
if (!r.ok())
return false;
if (*r.endptr != ':')
return false;
int rdevmajor = r.result;
r = qstrntoll(r.endptr + 1, stop - r.endptr - 1, 10);
if (!r.ok())
return false;
mnt.rdev = makedev(rdevmajor, r.result);
if (*r.endptr != ' ')
return false;
ptr = const_cast<char *>(r.endptr);
mnt.subvolume = ++ptr;
ptr = parseMangledPath(ptr);
if (!ptr)

View File

@ -4116,11 +4116,8 @@ qulonglong QLocaleData::stringToUnsLongLong(QStringView str, int base, bool *ok,
qlonglong QLocaleData::bytearrayToLongLong(QByteArrayView num, int base, bool *ok)
{
bool _ok;
const char *endptr;
const qlonglong l = qstrntoll(num.data(), num.size(), &endptr, base, &_ok);
if (!_ok || endptr == num.data()) {
auto [l, endptr] = qstrntoll(num.data(), num.size(), base);
if (!endptr) {
if (ok != nullptr)
*ok = false;
return 0;
@ -4146,11 +4143,8 @@ qlonglong QLocaleData::bytearrayToLongLong(QByteArrayView num, int base, bool *o
qulonglong QLocaleData::bytearrayToUnsLongLong(QByteArrayView num, int base, bool *ok)
{
bool _ok;
const char *endptr;
const qulonglong l = qstrntoull(num.data(), num.size(), &endptr, base, &_ok);
if (!_ok || endptr == num.data()) {
auto [l, endptr] = qstrntoull(num.data(), num.size(), base);
if (!endptr) {
if (ok != nullptr)
*ok = false;
return 0;

View File

@ -191,11 +191,10 @@ void qt_doubleToAscii(double d, QLocaleData::DoubleForm form, int precision,
// which case the missing digits are zeroes. In the 'e' case decptInTarget is always 1,
// as variants of snprintf always generate numbers with one digit before the '.' then.
// This is why the final decimal point is offset by 1, relative to the number after 'e'.
bool ok;
const char *endptr;
decpt = qstrntoll(target.data() + eSign + 1, length - eSign - 1, &endptr, 10, &ok) + 1;
Q_ASSERT(ok);
Q_ASSERT(endptr - target.data() <= length);
auto r = qstrntoll(target.data() + eSign + 1, length - eSign - 1, 10);
decpt = r.result + 1;
Q_ASSERT(r.ok());
Q_ASSERT(r.endptr - target.data() <= length);
} else {
// No 'e' found, so it's the 'f' form. Variants of snprintf generate numbers with
// potentially multiple digits before the '.', but without decimal exponent then. So we
@ -423,36 +422,25 @@ static bool isDigitForBase(char d, int base)
return false;
}
unsigned long long
qstrntoull(const char *begin, qsizetype size, const char **endptr, int base, bool *ok)
QSimpleParsedNumber<qulonglong> qstrntoull(const char *begin, qsizetype size, int base)
{
const char *p = begin, *const stop = begin + size;
while (p < stop && ascii_isspace(*p))
++p;
unsigned long long result = 0;
if (p >= stop || *p == '-') {
*ok = false;
if (endptr)
*endptr = begin;
return result;
}
if (p >= stop || *p == '-')
return { };
const auto prefix = scanPrefix(*p == '+' ? p + 1 : p, stop, base);
if (!prefix.base || prefix.next >= stop) {
if (endptr)
*endptr = begin;
*ok = false;
return 0;
}
if (!prefix.base || prefix.next >= stop)
return { };
const auto res = std::from_chars(prefix.next, stop, result, prefix.base);
*ok = res.ec == std::errc{};
if (endptr)
*endptr = res.ptr == prefix.next ? begin : res.ptr;
return result;
if (res.ec != std::errc{})
return { };
return { result, res.ptr == prefix.next ? begin : res.ptr };
}
long long
qstrntoll(const char *begin, qsizetype size, const char **endptr, int base, bool *ok)
QSimpleParsedNumber<qlonglong> qstrntoll(const char *begin, qsizetype size, int base)
{
const char *p = begin, *const stop = begin + size;
while (p < stop && ascii_isspace(*p))
@ -467,30 +455,22 @@ qstrntoll(const char *begin, qsizetype size, const char **endptr, int base, bool
const auto prefix = scanPrefix(p, stop, base);
// Must check for digit, as from_chars() will accept a sign, which would be
// a second sign, that we should reject.
if (!prefix.base || prefix.next >= stop || !isDigitForBase(*prefix.next, prefix.base)) {
if (endptr)
*endptr = begin;
*ok = false;
return 0;
}
if (!prefix.base || prefix.next >= stop || !isDigitForBase(*prefix.next, prefix.base))
return { };
long long result = 0;
auto res = std::from_chars(prefix.next, stop, result, prefix.base);
*ok = res.ec == std::errc{};
if (negate && res.ec == std::errc::result_out_of_range) {
// Maybe LLONG_MIN:
unsigned long long check = 0;
res = std::from_chars(prefix.next, stop, check, prefix.base);
if (res.ec == std::errc{} && check + std::numeric_limits<long long>::min() == 0) {
*ok = true;
if (endptr)
*endptr = res.ptr;
return std::numeric_limits<long long>::min();
}
if (res.ec == std::errc{} && check + std::numeric_limits<long long>::min() == 0)
return { std::numeric_limits<long long>::min(), res.ptr };
return { };
}
if (endptr)
*endptr = res.ptr == prefix.next ? begin : res.ptr;
return negate && *ok ? -result : result;
if (res.ec != std::errc{})
return { };
return { negate ? -result : result, res.ptr };
}
template <typename Char>

View File

@ -26,6 +26,13 @@ enum StrayCharacterMode {
WhitespacesAllowed
};
template <typename T> struct QSimpleParsedNumber
{
T result;
const char *endptr;
bool ok() { return endptr; }
};
// API note: this function can't process a number with more than 2.1 billion digits
[[nodiscard]] double qt_asciiToDouble(const char *num, qsizetype numLen, bool &ok, int &processed,
StrayCharacterMode strayCharMode = TrailingJunkProhibited);
@ -81,10 +88,8 @@ template <typename UcsInt>
return qstrntod(s00, len, se, ok);
}
[[nodiscard]] qlonglong qstrntoll(const char *nptr, qsizetype size, const char **endptr,
int base, bool *ok);
[[nodiscard]] qulonglong qstrntoull(const char *nptr, qsizetype size, const char **endptr,
int base, bool *ok);
[[nodiscard]] QSimpleParsedNumber<qlonglong> qstrntoll(const char *nptr, qsizetype size, int base);
[[nodiscard]] QSimpleParsedNumber<qulonglong> qstrntoull(const char *nptr, qsizetype size, int base);
QT_END_NAMESPACE

View File

@ -1071,11 +1071,9 @@ static QString winIso639LangName(LCID id)
lang_code = QString::fromWCharArray(out);
if (!lang_code.isEmpty()) {
const char *endptr;
bool ok;
const QByteArray latin1 = std::move(lang_code).toLatin1();
const auto i = qstrntoull(latin1.data(), latin1.size(), &endptr, 16, &ok);
if (ok && *endptr == '\0') {
const auto [i, endptr] = qstrntoull(latin1.data(), latin1.size(), 16);
if (endptr && *endptr == '\0') {
switch (i) {
case 0x814:
result = u"nn"_s; // Nynorsk
@ -1115,8 +1113,8 @@ static QByteArray getWinLocaleName(LCID id)
result = langEnvVar;
if (result == "C"
|| (!result.isEmpty() && qt_splitLocaleName(QString::fromLocal8Bit(result)))) {
bool ok = false; // See if we have a Windows locale code instead of a locale name:
long id = qstrntoll(result.data(), result.size(), 0, 0, &ok);
// See if we have a Windows locale code instead of a locale name:
auto [id, ok] = qstrntoll(result.data(), result.size(), 0);
if (!ok || id == 0 || id < INT_MIN || id > INT_MAX) // Assume real locale name
return result;
return winLangCodeToIsoName(int(id));

View File

@ -6767,14 +6767,14 @@ static int parse_field_width(const char *&c, qsizetype size)
// can't be negative - started with a digit
// contains at least one digit
const char *endp;
bool ok;
const qulonglong result = qstrntoull(c, size, &endp, 10, &ok);
auto [result, endp] = qstrntoull(c, size, 10);
c = endp;
if (!endp)
return false;
// preserve Qt 5.5 behavior of consuming all digits, no matter how many
while (c < stop && qIsDigit(*c))
++c;
return ok && result < qulonglong(std::numeric_limits<int>::max()) ? int(result) : 0;
return result < qulonglong(std::numeric_limits<int>::max()) ? int(result) : 0;
}
enum LengthMod { lm_none, lm_hh, lm_h, lm_l, lm_ll, lm_L, lm_j, lm_z, lm_t };

View File

@ -392,27 +392,28 @@ static int parsePosixTime(const char *begin, const char *end)
int hour, min = 0, sec = 0;
const int maxHour = 137; // POSIX's extended range.
bool ok = false;
const char *cut = begin;
hour = qstrntoll(begin, end - begin, &cut, 10, &ok);
if (!ok || hour < -maxHour || hour > maxHour || cut > begin + 2)
auto r = qstrntoll(begin, end - begin, 10);
hour = r.result;
if (!r.ok() || hour < -maxHour || hour > maxHour || r.endptr > begin + 2)
return INT_MIN;
begin = cut;
begin = r.endptr;
if (begin < end && *begin == ':') {
// minutes
++begin;
min = qstrntoll(begin, end - begin, &cut, 10, &ok);
if (!ok || min < 0 || min > 59 || cut > begin + 2)
r = qstrntoll(begin, end - begin, 10);
min = r.result;
if (!r.ok() || min < 0 || min > 59 || r.endptr > begin + 2)
return INT_MIN;
begin = cut;
begin = r.endptr;
if (begin < end && *begin == ':') {
// seconds
++begin;
sec = qstrntoll(begin, end - begin, &cut, 10, &ok);
if (!ok || sec < 0 || sec > 59 || cut > begin + 2)
r = qstrntoll(begin, end - begin, 10);
sec = r.result;
if (!r.ok() || sec < 0 || sec > 59 || r.endptr > begin + 2)
return INT_MIN;
begin = cut;
begin = r.endptr;
}
}

View File

@ -111,22 +111,18 @@ private:
#else
// can't use qEnvironmentVariableIntValue (reentrancy)
const char *seedstr = getenv("QT_HASH_SEED");
const char *endptr = nullptr;
bool ok = false;
int seed = 0;
if (seedstr)
seed = qstrntoll(seedstr, strlen(seedstr), &endptr, 10, &ok);
if (ok && endptr != seedstr + strlen(seedstr))
ok = false;
if (ok) {
if (seed) {
// can't use qWarning here (reentrancy)
fprintf(stderr, "QT_HASH_SEED: forced seed value is not 0; ignored.\n");
}
if (seedstr) {
auto r = qstrntoll(seedstr, strlen(seedstr), 10);
if (r.endptr == seedstr + strlen(seedstr)) {
if (r.result) {
// can't use qWarning here (reentrancy)
fprintf(stderr, "QT_HASH_SEED: forced seed value is not 0; ignored.\n");
}
// we don't have to store to the seed, since it's pre-initialized by
// the compiler to zero
return result;
// we don't have to store to the seed, since it's pre-initialized by
// the compiler to zero
return result;
}
}
// update the full seed

View File

@ -404,19 +404,18 @@ static QVersionNumber from_string(QLatin1StringView string, qsizetype *suffixInd
QVarLengthArray<int, 32> seg;
const char *start = string.begin();
const char *end = start;
const char *lastGoodEnd = start;
const char *endOfString = string.end();
do {
bool ok = false;
const qulonglong value = qstrntoull(start, endOfString - start, &end, 10, &ok);
if (!ok || value > qulonglong(std::numeric_limits<int>::max()))
// parsing as unsigned so a minus sign is rejected
auto [value, end] = qstrntoull(start, endOfString - start, 10);
if (!end || value > qulonglong(std::numeric_limits<int>::max()))
break;
seg.append(int(value));
start = end + 1;
lastGoodEnd = end;
} while (start < endOfString && end < endOfString && *end == '.');
} while (start < endOfString && *lastGoodEnd == '.');
if (suffixIndex)
*suffixIndex = lastGoodEnd - string.begin();