[libc] [llvm] [libc] Change ctype to be encoding independent (PR #110574)
Michael Jones via llvm-commits
llvm-commits at lists.llvm.org
Thu Nov 14 11:31:22 PST 2024
https://github.com/michaelrj-google updated https://github.com/llvm/llvm-project/pull/110574
>From 79a0b0c0ddd562ae4cc237940ad70e85307cbcfe Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Wed, 25 Sep 2024 14:52:14 -0700
Subject: [PATCH 1/2] [libc] Change ctype to be encoding independent
The previous implementation of the ctype functions assumed ASCII.
This patch changes to a switch/case implementation that looks odd, but
actually is easier for the compiler to understand and optimize.
---
libc/src/__support/ctype_utils.h | 298 ++++++++++++++++++++++++++++---
1 file changed, 275 insertions(+), 23 deletions(-)
diff --git a/libc/src/__support/ctype_utils.h b/libc/src/__support/ctype_utils.h
index 91f6ce8cabd8d04..2e9b23d3bc37f39 100644
--- a/libc/src/__support/ctype_utils.h
+++ b/libc/src/__support/ctype_utils.h
@@ -15,44 +15,296 @@
namespace LIBC_NAMESPACE_DECL {
namespace internal {
-// ------------------------------------------------------
-// Rationale: Since these classification functions are
-// called in other functions, we will avoid the overhead
-// of a function call by inlining them.
-// ------------------------------------------------------
+// -----------------------------------------------------------------------------
+// ****************** WARNING ******************
+// ****************** DO NOT TRY TO OPTIMIZE THESE FUNCTIONS! ******************
+// -----------------------------------------------------------------------------
+// This switch/case form is easier for the compiler to understand, and is
+// optimized into a form that is almost always the same as or better than
+// versions written by hand (see https://godbolt.org/z/qvrebqvvr). Also this
+// form makes these functions encoding independent. If you want to rewrite these
+// functions, make sure you have benchmarks to show your new solution is faster,
+// as well as a way to support non-ASCII character encodings.
-LIBC_INLINE static constexpr bool isalpha(unsigned ch) {
- return (ch | 32) - 'a' < 26;
+LIBC_INLINE static constexpr bool islower(int ch) {
+ switch (ch) {
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ case 'g':
+ case 'h':
+ case 'i':
+ case 'j':
+ case 'k':
+ case 'l':
+ case 'm':
+ case 'n':
+ case 'o':
+ case 'p':
+ case 'q':
+ case 'r':
+ case 's':
+ case 't':
+ case 'u':
+ case 'v':
+ case 'w':
+ case 'x':
+ case 'y':
+ case 'z':
+ return true;
+ default:
+ return false;
+ }
}
-LIBC_INLINE static constexpr bool isdigit(unsigned ch) {
- return (ch - '0') < 10;
+LIBC_INLINE static constexpr bool isupper(int ch) {
+ switch (ch) {
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'F':
+ case 'G':
+ case 'H':
+ case 'I':
+ case 'J':
+ case 'K':
+ case 'L':
+ case 'M':
+ case 'N':
+ case 'O':
+ case 'P':
+ case 'Q':
+ case 'R':
+ case 'S':
+ case 'T':
+ case 'U':
+ case 'V':
+ case 'W':
+ case 'X':
+ case 'Y':
+ case 'Z':
+ return true;
+ default:
+ return false;
+ }
}
-LIBC_INLINE static constexpr bool isalnum(unsigned ch) {
- return isalpha(ch) || isdigit(ch);
+LIBC_INLINE static constexpr bool isdigit(int ch) {
+ switch (ch) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ return true;
+ default:
+ return false;
+ }
}
-LIBC_INLINE static constexpr bool isgraph(unsigned ch) {
- return 0x20 < ch && ch < 0x7f;
+LIBC_INLINE static constexpr int tolower(int ch) {
+ switch (ch) {
+ case 'A':
+ return 'a';
+ case 'B':
+ return 'b';
+ case 'C':
+ return 'c';
+ case 'D':
+ return 'd';
+ case 'E':
+ return 'e';
+ case 'F':
+ return 'f';
+ case 'G':
+ return 'g';
+ case 'H':
+ return 'h';
+ case 'I':
+ return 'i';
+ case 'J':
+ return 'j';
+ case 'K':
+ return 'k';
+ case 'L':
+ return 'l';
+ case 'M':
+ return 'm';
+ case 'N':
+ return 'n';
+ case 'O':
+ return 'o';
+ case 'P':
+ return 'p';
+ case 'Q':
+ return 'q';
+ case 'R':
+ return 'r';
+ case 'S':
+ return 's';
+ case 'T':
+ return 't';
+ case 'U':
+ return 'u';
+ case 'V':
+ return 'v';
+ case 'W':
+ return 'w';
+ case 'X':
+ return 'x';
+ case 'Y':
+ return 'y';
+ case 'Z':
+ return 'z';
+ default:
+ return ch;
+ }
+}
+
+LIBC_INLINE static constexpr bool isalpha(int ch) {
+ switch (tolower(ch)) {
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ case 'g':
+ case 'h':
+ case 'i':
+ case 'j':
+ case 'k':
+ case 'l':
+ case 'm':
+ case 'n':
+ case 'o':
+ case 'p':
+ case 'q':
+ case 'r':
+ case 's':
+ case 't':
+ case 'u':
+ case 'v':
+ case 'w':
+ case 'x':
+ case 'y':
+ case 'z':
+ return true;
+ default:
+ return false;
+ }
}
-LIBC_INLINE static constexpr bool islower(unsigned ch) {
- return (ch - 'a') < 26;
+LIBC_INLINE static constexpr bool isalnum(int ch) {
+ return isalpha(ch) || isdigit(ch);
}
-LIBC_INLINE static constexpr bool isupper(unsigned ch) {
- return (ch - 'A') < 26;
+LIBC_INLINE static constexpr int b36_char_to_int(int ch) {
+ switch (tolower(ch)) {
+ case '0':
+ return 0;
+ case '1':
+ return 1;
+ case '2':
+ return 2;
+ case '3':
+ return 3;
+ case '4':
+ return 4;
+ case '5':
+ return 5;
+ case '6':
+ return 6;
+ case '7':
+ return 7;
+ case '8':
+ return 8;
+ case '9':
+ return 9;
+ case 'a':
+ return 10;
+ case 'b':
+ return 11;
+ case 'c':
+ return 12;
+ case 'd':
+ return 13;
+ case 'e':
+ return 14;
+ case 'f':
+ return 15;
+ case 'g':
+ return 16;
+ case 'h':
+ return 17;
+ case 'i':
+ return 18;
+ case 'j':
+ return 19;
+ case 'k':
+ return 20;
+ case 'l':
+ return 21;
+ case 'm':
+ return 22;
+ case 'n':
+ return 23;
+ case 'o':
+ return 24;
+ case 'p':
+ return 25;
+ case 'q':
+ return 26;
+ case 'r':
+ return 27;
+ case 's':
+ return 28;
+ case 't':
+ return 29;
+ case 'u':
+ return 30;
+ case 'v':
+ return 31;
+ case 'w':
+ return 32;
+ case 'x':
+ return 33;
+ case 'y':
+ return 34;
+ case 'z':
+ return 35;
+ default:
+ return 0;
+ }
}
-LIBC_INLINE static constexpr bool isspace(unsigned ch) {
- return ch == ' ' || (ch - '\t') < 5;
+LIBC_INLINE static constexpr bool isspace(int ch) {
+ switch (ch) {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\v':
+ case '\f':
+ case '\r':
+ return true;
+ default:
+ return false;
+ }
}
-LIBC_INLINE static constexpr int tolower(int ch) {
- if (isupper(ch))
- return ch + ('a' - 'A');
- return ch;
+// not yet encoding independent.
+LIBC_INLINE static constexpr bool isgraph(int ch) {
+ return 0x20 < ch && ch < 0x7f;
}
} // namespace internal
>From 795ba04531233172c6619fe3e469279ecb0d155d Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Thu, 14 Nov 2024 11:28:48 -0800
Subject: [PATCH 2/2] Move functions to use ctype utils, cleanup
---
libc/src/__support/ctype_utils.h | 266 +++++++++++++++++-
libc/src/__support/integer_literals.h | 28 +-
libc/src/ctype/isxdigit.cpp | 3 +-
libc/src/ctype/isxdigit_l.cpp | 3 +-
libc/src/stdio/printf_core/fixed_converter.h | 14 +-
.../stdio/printf_core/float_dec_converter.h | 6 +-
.../stdio/printf_core/float_hex_converter.h | 17 +-
.../printf_core/float_inf_nan_converter.h | 9 +-
libc/src/stdio/printf_core/int_converter.h | 21 +-
libc/src/stdio/scanf_core/converter_utils.h | 10 -
libc/src/stdio/scanf_core/float_converter.cpp | 18 +-
libc/src/stdio/scanf_core/int_converter.cpp | 12 +-
libc/src/stdio/scanf_core/ptr_converter.cpp | 4 +-
.../llvm-project-overlay/libc/BUILD.bazel | 2 +
14 files changed, 329 insertions(+), 84 deletions(-)
diff --git a/libc/src/__support/ctype_utils.h b/libc/src/__support/ctype_utils.h
index 2e9b23d3bc37f39..8521857ce765dec 100644
--- a/libc/src/__support/ctype_utils.h
+++ b/libc/src/__support/ctype_utils.h
@@ -171,8 +171,67 @@ LIBC_INLINE static constexpr int tolower(int ch) {
}
}
+LIBC_INLINE static constexpr int toupper(int ch) {
+ switch (ch) {
+ case 'a':
+ return 'A';
+ case 'b':
+ return 'B';
+ case 'c':
+ return 'C';
+ case 'd':
+ return 'D';
+ case 'e':
+ return 'E';
+ case 'f':
+ return 'F';
+ case 'g':
+ return 'G';
+ case 'h':
+ return 'H';
+ case 'i':
+ return 'I';
+ case 'j':
+ return 'J';
+ case 'k':
+ return 'K';
+ case 'l':
+ return 'L';
+ case 'm':
+ return 'M';
+ case 'n':
+ return 'N';
+ case 'o':
+ return 'O';
+ case 'p':
+ return 'P';
+ case 'q':
+ return 'Q';
+ case 'r':
+ return 'R';
+ case 's':
+ return 'S';
+ case 't':
+ return 'T';
+ case 'u':
+ return 'U';
+ case 'v':
+ return 'V';
+ case 'w':
+ return 'W';
+ case 'x':
+ return 'X';
+ case 'y':
+ return 'Y';
+ case 'z':
+ return 'Z';
+ default:
+ return ch;
+ }
+}
+
LIBC_INLINE static constexpr bool isalpha(int ch) {
- switch (tolower(ch)) {
+ switch (ch) {
case 'a':
case 'b':
case 'c':
@@ -199,6 +258,32 @@ LIBC_INLINE static constexpr bool isalpha(int ch) {
case 'x':
case 'y':
case 'z':
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'F':
+ case 'G':
+ case 'H':
+ case 'I':
+ case 'J':
+ case 'K':
+ case 'L':
+ case 'M':
+ case 'N':
+ case 'O':
+ case 'P':
+ case 'Q':
+ case 'R':
+ case 'S':
+ case 'T':
+ case 'U':
+ case 'V':
+ case 'W':
+ case 'X':
+ case 'Y':
+ case 'Z':
return true;
default:
return false;
@@ -206,11 +291,77 @@ LIBC_INLINE static constexpr bool isalpha(int ch) {
}
LIBC_INLINE static constexpr bool isalnum(int ch) {
- return isalpha(ch) || isdigit(ch);
+ switch (ch) {
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ case 'g':
+ case 'h':
+ case 'i':
+ case 'j':
+ case 'k':
+ case 'l':
+ case 'm':
+ case 'n':
+ case 'o':
+ case 'p':
+ case 'q':
+ case 'r':
+ case 's':
+ case 't':
+ case 'u':
+ case 'v':
+ case 'w':
+ case 'x':
+ case 'y':
+ case 'z':
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'F':
+ case 'G':
+ case 'H':
+ case 'I':
+ case 'J':
+ case 'K':
+ case 'L':
+ case 'M':
+ case 'N':
+ case 'O':
+ case 'P':
+ case 'Q':
+ case 'R':
+ case 'S':
+ case 'T':
+ case 'U':
+ case 'V':
+ case 'W':
+ case 'X':
+ case 'Y':
+ case 'Z':
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ return true;
+ default:
+ return false;
+ }
}
LIBC_INLINE static constexpr int b36_char_to_int(int ch) {
- switch (tolower(ch)) {
+ switch (ch) {
case '0':
return 0;
case '1':
@@ -232,62 +383,171 @@ LIBC_INLINE static constexpr int b36_char_to_int(int ch) {
case '9':
return 9;
case 'a':
+ case 'A':
return 10;
case 'b':
+ case 'B':
return 11;
case 'c':
+ case 'C':
return 12;
case 'd':
+ case 'D':
return 13;
case 'e':
+ case 'E':
return 14;
case 'f':
+ case 'F':
return 15;
case 'g':
+ case 'G':
return 16;
case 'h':
+ case 'H':
return 17;
case 'i':
+ case 'I':
return 18;
case 'j':
+ case 'J':
return 19;
case 'k':
+ case 'K':
return 20;
case 'l':
+ case 'L':
return 21;
case 'm':
+ case 'M':
return 22;
case 'n':
+ case 'N':
return 23;
case 'o':
+ case 'O':
return 24;
case 'p':
+ case 'P':
return 25;
case 'q':
+ case 'Q':
return 26;
case 'r':
+ case 'R':
return 27;
case 's':
+ case 'S':
return 28;
case 't':
+ case 'T':
return 29;
case 'u':
+ case 'U':
return 30;
case 'v':
+ case 'V':
return 31;
case 'w':
+ case 'W':
return 32;
case 'x':
+ case 'X':
return 33;
case 'y':
+ case 'Y':
return 34;
case 'z':
+ case 'Z':
return 35;
default:
return 0;
}
}
+LIBC_INLINE static constexpr int int_to_b36_char(int num) {
+ // Can't actually use LIBC_ASSERT here because it depends on integer_to_string
+ // which depends on this.
+
+ // LIBC_ASSERT(num < 36);
+ switch (num) {
+ case 0:
+ return '0';
+ case 1:
+ return '1';
+ case 2:
+ return '2';
+ case 3:
+ return '3';
+ case 4:
+ return '4';
+ case 5:
+ return '5';
+ case 6:
+ return '6';
+ case 7:
+ return '7';
+ case 8:
+ return '8';
+ case 9:
+ return '9';
+ case 10:
+ return 'a';
+ case 11:
+ return 'b';
+ case 12:
+ return 'c';
+ case 13:
+ return 'd';
+ case 14:
+ return 'e';
+ case 15:
+ return 'f';
+ case 16:
+ return 'g';
+ case 17:
+ return 'h';
+ case 18:
+ return 'i';
+ case 19:
+ return 'j';
+ case 20:
+ return 'k';
+ case 21:
+ return 'l';
+ case 22:
+ return 'm';
+ case 23:
+ return 'n';
+ case 24:
+ return 'o';
+ case 25:
+ return 'p';
+ case 26:
+ return 'q';
+ case 27:
+ return 'r';
+ case 28:
+ return 's';
+ case 29:
+ return 't';
+ case 30:
+ return 'u';
+ case 31:
+ return 'v';
+ case 32:
+ return 'w';
+ case 33:
+ return 'x';
+ case 34:
+ return 'y';
+ case 35:
+ return 'z';
+ default:
+ return '!';
+ }
+}
+
LIBC_INLINE static constexpr bool isspace(int ch) {
switch (ch) {
case ' ':
diff --git a/libc/src/__support/integer_literals.h b/libc/src/__support/integer_literals.h
index 4c5c4c416668119..0298ec7d088d697 100644
--- a/libc/src/__support/integer_literals.h
+++ b/libc/src/__support/integer_literals.h
@@ -13,12 +13,13 @@
#ifndef LLVM_LIBC_SRC___SUPPORT_INTEGER_LITERALS_H
#define LLVM_LIBC_SRC___SUPPORT_INTEGER_LITERALS_H
-#include "src/__support/CPP/limits.h" // CHAR_BIT
+#include "src/__support/CPP/limits.h" // CHAR_BIT
+#include "src/__support/ctype_utils.h"
#include "src/__support/macros/attributes.h" // LIBC_INLINE
#include "src/__support/macros/config.h"
-#include "src/__support/uint128.h" // UInt128
-#include <stddef.h> // size_t
-#include <stdint.h> // uintxx_t
+#include "src/__support/uint128.h" // UInt128
+#include <stddef.h> // size_t
+#include <stdint.h> // uintxx_t
namespace LIBC_NAMESPACE_DECL {
@@ -75,26 +76,13 @@ template <typename T, int base> struct DigitBuffer {
push(*str);
}
- // Returns the digit for a particular character.
- // Returns INVALID_DIGIT if the character is invalid.
- LIBC_INLINE static constexpr uint8_t get_digit_value(const char c) {
- const auto to_lower = [](char c) { return c | 32; };
- const auto is_digit = [](char c) { return c >= '0' && c <= '9'; };
- const auto is_alpha = [](char c) {
- return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z');
- };
- if (is_digit(c))
- return static_cast<uint8_t>(c - '0');
- if (base > 10 && is_alpha(c))
- return static_cast<uint8_t>(to_lower(c) - 'a' + 10);
- return INVALID_DIGIT;
- }
-
// Adds a single character to this buffer.
LIBC_INLINE constexpr void push(char c) {
if (c == '\'')
return; // ' is valid but not taken into account.
- const uint8_t value = get_digit_value(c);
+ const int b36_val = internal::b36_char_to_int(c);
+ const uint8_t value = static_cast<uint8_t>(
+ b36_val < base && (b36_val != 0 || c == '0') ? b36_val : INVALID_DIGIT);
if (value == INVALID_DIGIT || size >= MAX_DIGITS) {
// During constant evaluation `__builtin_unreachable` will halt the
// compiler as it is not executable. This is preferable over `assert` that
diff --git a/libc/src/ctype/isxdigit.cpp b/libc/src/ctype/isxdigit.cpp
index 6b730c354db0837..81f645c6f49fc92 100644
--- a/libc/src/ctype/isxdigit.cpp
+++ b/libc/src/ctype/isxdigit.cpp
@@ -16,7 +16,8 @@ namespace LIBC_NAMESPACE_DECL {
LLVM_LIBC_FUNCTION(int, isxdigit, (int c)) {
const unsigned ch = static_cast<unsigned>(c);
- return static_cast<int>(internal::isdigit(ch) || (ch | 32) - 'a' < 6);
+ return static_cast<int>(internal::isalnum(ch) &&
+ internal::b36_char_to_int(ch) < 16);
}
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/ctype/isxdigit_l.cpp b/libc/src/ctype/isxdigit_l.cpp
index 8a5c7d4d28ab1c8..eddfd20a2da3b49 100644
--- a/libc/src/ctype/isxdigit_l.cpp
+++ b/libc/src/ctype/isxdigit_l.cpp
@@ -16,7 +16,8 @@ namespace LIBC_NAMESPACE_DECL {
LLVM_LIBC_FUNCTION(int, isxdigit_l, (int c, locale_t)) {
const unsigned ch = static_cast<unsigned>(c);
- return static_cast<int>(internal::isdigit(ch) || (ch | 32) - 'a' < 6);
+ return static_cast<int>(internal::isalnum(ch) &&
+ internal::b36_char_to_int(ch) < 16);
}
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/printf_core/fixed_converter.h b/libc/src/stdio/printf_core/fixed_converter.h
index c8812d77b62e34a..ba0a62d9fcb87fd 100644
--- a/libc/src/stdio/printf_core/fixed_converter.h
+++ b/libc/src/stdio/printf_core/fixed_converter.h
@@ -11,6 +11,7 @@
#include "include/llvm-libc-macros/stdfix-macros.h"
#include "src/__support/CPP/string_view.h"
+#include "src/__support/ctype_utils.h"
#include "src/__support/fixed_point/fx_bits.h"
#include "src/__support/fixed_point/fx_rep.h"
#include "src/__support/integer_to_string.h"
@@ -68,10 +69,6 @@ LIBC_INLINE int convert_fixed(Writer *writer, const FormatSection &to_conv) {
using LARep = fixed_point::FXRep<unsigned long accum>;
using StorageType = LARep::StorageType;
- // All of the letters will be defined relative to variable a, which will be
- // the appropriate case based on the name of the conversion. This converts any
- // conversion name into the letter 'a' with the appropriate case.
- const char a = (to_conv.conv_name & 32) | 'A';
FormatFlags flags = to_conv.flags;
bool is_negative;
@@ -179,9 +176,9 @@ LIBC_INLINE int convert_fixed(Writer *writer, const FormatSection &to_conv) {
// unspecified.
RoundDirection round;
char first_digit_after = fraction_digits[precision];
- if (first_digit_after > '5') {
+ if (internal::b36_char_to_int(first_digit_after) > 5) {
round = RoundDirection::Up;
- } else if (first_digit_after < '5') {
+ } else if (internal::b36_char_to_int(first_digit_after) < 5) {
round = RoundDirection::Down;
} else {
// first_digit_after == '5'
@@ -204,7 +201,8 @@ LIBC_INLINE int convert_fixed(Writer *writer, const FormatSection &to_conv) {
keep_rounding = false;
char cur_digit = fraction_digits[digit_to_round];
// if the digit should not be rounded up
- if (round == RoundDirection::Even && ((cur_digit - '0') % 2) == 0) {
+ if (round == RoundDirection::Even &&
+ (internal::b36_char_to_int(cur_digit) % 2) == 0) {
// break out of the loop
break;
}
@@ -246,7 +244,7 @@ LIBC_INLINE int convert_fixed(Writer *writer, const FormatSection &to_conv) {
char sign_char = 0;
// Check if the conv name is uppercase
- if (a == 'A') {
+ if (internal::isupper(to_conv.conv_name)) {
// These flags are only for signed conversions, so this removes them if the
// conversion is unsigned.
flags = FormatFlags(flags &
diff --git a/libc/src/stdio/printf_core/float_dec_converter.h b/libc/src/stdio/printf_core/float_dec_converter.h
index e39ba6ecea8d48d..d93457fcafd7f98 100644
--- a/libc/src/stdio/printf_core/float_dec_converter.h
+++ b/libc/src/stdio/printf_core/float_dec_converter.h
@@ -13,6 +13,7 @@
#include "src/__support/FPUtil/FPBits.h"
#include "src/__support/FPUtil/rounding_mode.h"
#include "src/__support/big_int.h" // is_big_int_v
+#include "src/__support/ctype_utils.h"
#include "src/__support/float_to_string.h"
#include "src/__support/integer_to_string.h"
#include "src/__support/libc_assert.h"
@@ -587,8 +588,6 @@ LIBC_INLINE int convert_float_dec_exp_typed(Writer *writer,
int exponent = float_bits.get_explicit_exponent();
StorageType mantissa = float_bits.get_explicit_mantissa();
- const char a = (to_conv.conv_name & 32) | 'A';
-
char sign_char = 0;
if (float_bits.is_neg())
@@ -734,7 +733,8 @@ LIBC_INLINE int convert_float_dec_exp_typed(Writer *writer,
round = get_round_direction(last_digit, truncated, float_bits.sign());
RET_IF_RESULT_NEGATIVE(float_writer.write_last_block(
- digits, maximum, round, final_exponent, a + 'E' - 'A'));
+ digits, maximum, round, final_exponent,
+ internal::islower(to_conv.conv_name) ? 'e' : 'E'));
RET_IF_RESULT_NEGATIVE(float_writer.right_pad());
return WRITE_OK;
diff --git a/libc/src/stdio/printf_core/float_hex_converter.h b/libc/src/stdio/printf_core/float_hex_converter.h
index 0b3ff3dd1cbfdce..5d9c42882a589bd 100644
--- a/libc/src/stdio/printf_core/float_hex_converter.h
+++ b/libc/src/stdio/printf_core/float_hex_converter.h
@@ -12,6 +12,7 @@
#include "src/__support/CPP/string_view.h"
#include "src/__support/FPUtil/FPBits.h"
#include "src/__support/FPUtil/rounding_mode.h"
+#include "src/__support/ctype_utils.h"
#include "src/__support/macros/config.h"
#include "src/stdio/printf_core/converter_utils.h"
#include "src/stdio/printf_core/core_structs.h"
@@ -31,7 +32,6 @@ LIBC_INLINE int convert_float_hex_exp(Writer *writer,
// All of the letters will be defined relative to variable a, which will be
// the appropriate case based on the name of the conversion. This converts any
// conversion name into the letter 'a' with the appropriate case.
- const char a = (to_conv.conv_name & 32) | 'A';
bool is_negative;
int exponent;
@@ -138,9 +138,11 @@ LIBC_INLINE int convert_float_hex_exp(Writer *writer,
size_t mant_cur = mant_len;
size_t first_non_zero = 1;
for (; mant_cur > 0; --mant_cur, mantissa >>= 4) {
- char mant_mod_16 = static_cast<char>(mantissa) & 15;
- char new_digit = static_cast<char>(
- (mant_mod_16 > 9) ? (mant_mod_16 - 10 + a) : (mant_mod_16 + '0'));
+ char mant_mod_16 = static_cast<char>(mantissa % 16);
+ char new_digit = static_cast<char>(internal::int_to_b36_char(mant_mod_16));
+ if (internal::isupper(to_conv.conv_name)) {
+ new_digit = static_cast<char>(internal::toupper(new_digit));
+ }
mant_buffer[mant_cur - 1] = new_digit;
if (new_digit != '0' && first_non_zero < mant_cur)
first_non_zero = mant_cur;
@@ -168,7 +170,8 @@ LIBC_INLINE int convert_float_hex_exp(Writer *writer,
size_t exp_cur = EXP_LEN;
for (; exponent > 0; --exp_cur, exponent /= 10) {
- exp_buffer[exp_cur - 1] = static_cast<char>((exponent % 10) + '0');
+ exp_buffer[exp_cur - 1] =
+ static_cast<char>(internal::int_to_b36_char(exponent % 10));
}
if (exp_cur == EXP_LEN) { // if nothing else was written, write a 0.
exp_buffer[EXP_LEN - 1] = '0';
@@ -187,7 +190,7 @@ LIBC_INLINE int convert_float_hex_exp(Writer *writer,
constexpr size_t PREFIX_LEN = 2;
char prefix[PREFIX_LEN];
prefix[0] = '0';
- prefix[1] = a + ('x' - 'a');
+ prefix[1] = internal::islower(to_conv.conv_name) ? 'x' : 'X';
const cpp::string_view prefix_str(prefix, PREFIX_LEN);
// If the precision is greater than the actual result, pad with 0s
@@ -200,7 +203,7 @@ LIBC_INLINE int convert_float_hex_exp(Writer *writer,
constexpr cpp::string_view HEXADECIMAL_POINT(".");
// This is for the letter 'p' before the exponent.
- const char exp_separator = a + ('p' - 'a');
+ const char exp_separator = internal::islower(to_conv.conv_name) ? 'p' : 'P';
constexpr int EXP_SEPARATOR_LEN = 1;
padding = static_cast<int>(to_conv.min_width - (sign_char > 0 ? 1 : 0) -
diff --git a/libc/src/stdio/printf_core/float_inf_nan_converter.h b/libc/src/stdio/printf_core/float_inf_nan_converter.h
index a7da682b835beed..3e41612e21c9fcc 100644
--- a/libc/src/stdio/printf_core/float_inf_nan_converter.h
+++ b/libc/src/stdio/printf_core/float_inf_nan_converter.h
@@ -10,6 +10,7 @@
#define LLVM_LIBC_SRC_STDIO_PRINTF_CORE_FLOAT_INF_NAN_CONVERTER_H
#include "src/__support/FPUtil/FPBits.h"
+#include "src/__support/ctype_utils.h"
#include "src/__support/macros/config.h"
#include "src/stdio/printf_core/converter_utils.h"
#include "src/stdio/printf_core/core_structs.h"
@@ -26,8 +27,6 @@ using StorageType = fputil::FPBits<long double>::StorageType;
LIBC_INLINE int convert_inf_nan(Writer *writer, const FormatSection &to_conv) {
// All of the letters will be defined relative to variable a, which will be
// the appropriate case based on the case of the conversion.
- const char a = (to_conv.conv_name & 32) | 'A';
-
bool is_negative;
StorageType mantissa;
if (to_conv.length_modifier == LengthModifier::L) {
@@ -66,9 +65,11 @@ LIBC_INLINE int convert_inf_nan(Writer *writer, const FormatSection &to_conv) {
if (sign_char)
RET_IF_RESULT_NEGATIVE(writer->write(sign_char));
if (mantissa == 0) { // inf
- RET_IF_RESULT_NEGATIVE(writer->write(a == 'a' ? "inf" : "INF"));
+ RET_IF_RESULT_NEGATIVE(
+ writer->write(internal::islower(to_conv.conv_name) ? "inf" : "INF"));
} else { // nan
- RET_IF_RESULT_NEGATIVE(writer->write(a == 'a' ? "nan" : "NAN"));
+ RET_IF_RESULT_NEGATIVE(
+ writer->write(internal::islower(to_conv.conv_name) ? "nan" : "NAN"));
}
if (padding > 0 && ((to_conv.flags & FormatFlags::LEFT_JUSTIFIED) ==
diff --git a/libc/src/stdio/printf_core/int_converter.h b/libc/src/stdio/printf_core/int_converter.h
index f345e86b97a6911..d0af229f89be518 100644
--- a/libc/src/stdio/printf_core/int_converter.h
+++ b/libc/src/stdio/printf_core/int_converter.h
@@ -11,6 +11,7 @@
#include "src/__support/CPP/span.h"
#include "src/__support/CPP/string_view.h"
+#include "src/__support/ctype_utils.h"
#include "src/__support/integer_to_string.h"
#include "src/__support/macros/config.h"
#include "src/stdio/printf_core/converter_utils.h"
@@ -23,11 +24,6 @@
namespace LIBC_NAMESPACE_DECL {
namespace printf_core {
-// These functions only work on characters that are already known to be in the
-// alphabet. Their behavior is undefined otherwise.
-LIBC_INLINE constexpr char to_lower(char a) { return a | 32; }
-LIBC_INLINE constexpr bool is_lower(char a) { return (a & 32) > 0; }
-
namespace details {
using HexFmt = IntegerToString<uintmax_t, radix::Hex>;
@@ -49,14 +45,14 @@ LIBC_INLINE constexpr size_t num_buf_size() {
LIBC_INLINE cpp::optional<cpp::string_view>
num_to_strview(uintmax_t num, cpp::span<char> bufref, char conv_name) {
- if (to_lower(conv_name) == 'x') {
- if (is_lower(conv_name))
+ if (internal::tolower(conv_name) == 'x') {
+ if (internal::islower(conv_name))
return HexFmt::format_to(bufref, num);
else
return HexFmtUppercase::format_to(bufref, num);
} else if (conv_name == 'o') {
return OctFmt::format_to(bufref, num);
- } else if (to_lower(conv_name) == 'b') {
+ } else if (internal::tolower(conv_name) == 'b') {
return BinFmt::format_to(bufref, num);
} else {
return DecFmt::format_to(bufref, num);
@@ -72,7 +68,6 @@ LIBC_INLINE int convert_int(Writer *writer, const FormatSection &to_conv) {
uintmax_t num = static_cast<uintmax_t>(to_conv.conv_val_raw);
bool is_negative = false;
FormatFlags flags = to_conv.flags;
- const char a = is_lower(to_conv.conv_name) ? 'a' : 'A';
// If the conversion is signed, then handle negative values.
if (to_conv.conv_name == 'd' || to_conv.conv_name == 'i') {
@@ -116,16 +111,16 @@ LIBC_INLINE int convert_int(Writer *writer, const FormatSection &to_conv) {
// conversions. Since hexadecimal is unsigned these will never conflict.
size_t prefix_len;
char prefix[2];
- if ((to_lower(to_conv.conv_name) == 'x') &&
+ if ((internal::tolower(to_conv.conv_name) == 'x') &&
((flags & FormatFlags::ALTERNATE_FORM) != 0) && num != 0) {
prefix_len = 2;
prefix[0] = '0';
- prefix[1] = a + ('x' - 'a');
- } else if ((to_lower(to_conv.conv_name) == 'b') &&
+ prefix[1] = internal::islower(to_conv.conv_name) ? 'x' : 'X';
+ } else if ((internal::tolower(to_conv.conv_name) == 'b') &&
((flags & FormatFlags::ALTERNATE_FORM) != 0) && num != 0) {
prefix_len = 2;
prefix[0] = '0';
- prefix[1] = a + ('b' - 'a');
+ prefix[1] = internal::islower(to_conv.conv_name) ? 'b' : 'B';
} else {
prefix_len = (sign_char == 0 ? 0 : 1);
prefix[0] = sign_char;
diff --git a/libc/src/stdio/scanf_core/converter_utils.h b/libc/src/stdio/scanf_core/converter_utils.h
index 61954556b838a02..6f4d16cffb19cb5 100644
--- a/libc/src/stdio/scanf_core/converter_utils.h
+++ b/libc/src/stdio/scanf_core/converter_utils.h
@@ -19,16 +19,6 @@
namespace LIBC_NAMESPACE_DECL {
namespace scanf_core {
-LIBC_INLINE constexpr char to_lower(char a) { return a | 32; }
-
-LIBC_INLINE constexpr int b36_char_to_int(char input) {
- if (internal::isdigit(input))
- return input - '0';
- if (internal::isalpha(input))
- return to_lower(input) + 10 - 'a';
- return 0;
-}
-
LIBC_INLINE void write_int_with_length(uintmax_t output_val,
const FormatSection &to_conv) {
if ((to_conv.flags & NO_WRITE) != 0) {
diff --git a/libc/src/stdio/scanf_core/float_converter.cpp b/libc/src/stdio/scanf_core/float_converter.cpp
index b2d60a249a5a776..9c714d0727214e9 100644
--- a/libc/src/stdio/scanf_core/float_converter.cpp
+++ b/libc/src/stdio/scanf_core/float_converter.cpp
@@ -55,11 +55,12 @@ int convert_float(Reader *reader, const FormatSection &to_conv) {
// Handle inf
- if (to_lower(cur_char) == inf_string[0]) {
+ if (internal::tolower(cur_char) == inf_string[0]) {
size_t inf_index = 0;
- for (; inf_index < sizeof(inf_string) && out_str.length() < max_width &&
- to_lower(cur_char) == inf_string[inf_index];
+ for (;
+ inf_index < (sizeof(inf_string) - 1) && out_str.length() < max_width &&
+ internal::tolower(cur_char) == inf_string[inf_index];
++inf_index) {
if (!out_str.append(cur_char)) {
return ALLOCATION_FAILURE;
@@ -78,11 +79,12 @@ int convert_float(Reader *reader, const FormatSection &to_conv) {
static const char nan_string[] = "nan";
// Handle nan
- if (to_lower(cur_char) == nan_string[0]) {
+ if (internal::tolower(cur_char) == nan_string[0]) {
size_t nan_index = 0;
- for (; nan_index < sizeof(nan_string) && out_str.length() < max_width &&
- to_lower(cur_char) == nan_string[nan_index];
+ for (;
+ nan_index < (sizeof(nan_string) - 1) && out_str.length() < max_width &&
+ internal::tolower(cur_char) == nan_string[nan_index];
++nan_index) {
if (!out_str.append(cur_char)) {
return ALLOCATION_FAILURE;
@@ -117,7 +119,7 @@ int convert_float(Reader *reader, const FormatSection &to_conv) {
}
// If that next character is an 'x' then this is a hexadecimal number.
- if (to_lower(cur_char) == 'x') {
+ if (internal::tolower(cur_char) == 'x') {
base = 16;
if (!out_str.append(cur_char)) {
@@ -163,7 +165,7 @@ int convert_float(Reader *reader, const FormatSection &to_conv) {
// Handle the exponent, which has an exponent mark, an optional sign, and
// decimal digits.
- if (to_lower(cur_char) == exponent_mark) {
+ if (internal::tolower(cur_char) == exponent_mark) {
if (!out_str.append(cur_char)) {
return ALLOCATION_FAILURE;
}
diff --git a/libc/src/stdio/scanf_core/int_converter.cpp b/libc/src/stdio/scanf_core/int_converter.cpp
index ecdac52e84bbde9..fce817245c01087 100644
--- a/libc/src/stdio/scanf_core/int_converter.cpp
+++ b/libc/src/stdio/scanf_core/int_converter.cpp
@@ -80,7 +80,8 @@ int convert_int(Reader *reader, const FormatSection &to_conv) {
is_signed = true;
} else if (to_conv.conv_name == 'o') {
base = 8;
- } else if (to_lower(to_conv.conv_name) == 'x' || to_conv.conv_name == 'p') {
+ } else if (internal::tolower(to_conv.conv_name) == 'x' ||
+ to_conv.conv_name == 'p') {
base = 16;
} else if (to_conv.conv_name == 'd') {
base = 10;
@@ -122,7 +123,7 @@ int convert_int(Reader *reader, const FormatSection &to_conv) {
return READ_OK;
}
- if (to_lower(cur_char) == 'x') {
+ if (internal::tolower(cur_char) == 'x') {
// This is a valid hex prefix.
is_number = false;
@@ -175,17 +176,18 @@ int convert_int(Reader *reader, const FormatSection &to_conv) {
const uintmax_t max_div_by_base = MAX / base;
- if (internal::isalnum(cur_char) && b36_char_to_int(cur_char) < base) {
+ if (internal::isalnum(cur_char) &&
+ internal::b36_char_to_int(cur_char) < base) {
is_number = true;
}
bool has_overflow = false;
size_t i = 0;
for (; i < max_width && internal::isalnum(cur_char) &&
- b36_char_to_int(cur_char) < base;
+ internal::b36_char_to_int(cur_char) < base;
++i, cur_char = reader->getc()) {
- uintmax_t cur_digit = b36_char_to_int(cur_char);
+ uintmax_t cur_digit = internal::b36_char_to_int(cur_char);
if (result == MAX) {
has_overflow = true;
diff --git a/libc/src/stdio/scanf_core/ptr_converter.cpp b/libc/src/stdio/scanf_core/ptr_converter.cpp
index 1a42a389d74b412..37f002d3da69855 100644
--- a/libc/src/stdio/scanf_core/ptr_converter.cpp
+++ b/libc/src/stdio/scanf_core/ptr_converter.cpp
@@ -8,6 +8,7 @@
#include "src/stdio/scanf_core/ptr_converter.h"
+#include "src/__support/ctype_utils.h"
#include "src/__support/macros/config.h"
#include "src/stdio/scanf_core/converter_utils.h"
#include "src/stdio/scanf_core/core_structs.h"
@@ -24,7 +25,8 @@ int convert_pointer(Reader *reader, const FormatSection &to_conv) {
// Check if it's exactly the nullptr string, if so then it's a nullptr.
char cur_char = reader->getc();
size_t i = 0;
- for (; i < sizeof(nullptr_string) && to_lower(cur_char) == nullptr_string[i];
+ for (; i < (sizeof(nullptr_string) - 1) &&
+ internal::tolower(cur_char) == nullptr_string[i];
++i) {
cur_char = reader->getc();
}
diff --git a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
index 69a0dada23b4cd2..a4c47483805724d 100644
--- a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
@@ -740,6 +740,7 @@ libc_support_library(
hdrs = ["src/__support/integer_literals.h"],
deps = [
":__support_cpp_limits",
+ ":__support_ctype_utils",
":__support_uint128",
],
)
@@ -4450,6 +4451,7 @@ libc_support_library(
":__support_cpp_limits",
":__support_cpp_span",
":__support_cpp_string_view",
+ ":__support_ctype_utils",
":__support_float_to_string",
":__support_fputil_fenv_impl",
":__support_fputil_fp_bits",
More information about the llvm-commits
mailing list