AK: Make short ASCII string literals compile-time constants
Make the _string, _fly_string, _utf16, and _utf16_fly_string UDL operators constexpr, with a fast path for short (<= 7 byte) ASCII literals that folds directly into an inline ShortString. Previously, every "foo"_fly_string (and friends) involved an out-of-line call into the string factory, even though the result is entirely known at compile time.
This commit is contained in:
parent
7c1d359790
commit
0317007ee1
8 changed files with 139 additions and 7 deletions
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/AllOf.h>
|
||||
#include <AK/Error.h>
|
||||
#include <AK/Format.h>
|
||||
#include <AK/Optional.h>
|
||||
|
|
@ -25,6 +26,16 @@ public:
|
|||
|
||||
static ErrorOr<FlyString> from_utf8(StringView);
|
||||
static FlyString from_utf8_without_validation(ReadonlyBytes);
|
||||
|
||||
[[nodiscard]] static constexpr FlyString from_ascii_short_string_without_validation(char const* data, size_t length)
|
||||
{
|
||||
VERIFY(length <= Detail::MAX_SHORT_STRING_BYTE_COUNT);
|
||||
auto short_string = Detail::ShortString::create_with_byte_count(length);
|
||||
for (size_t i = 0; i < length; ++i)
|
||||
short_string.storage[i] = static_cast<u8>(data[i]);
|
||||
return FlyString { Detail::StringBase { short_string } };
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
requires(IsOneOf<RemoveCVReference<T>, ByteString, FlyString, String>)
|
||||
static ErrorOr<String> from_utf8(T&&) = delete;
|
||||
|
|
@ -131,8 +142,14 @@ struct ASCIICaseInsensitiveFlyStringTraits : public Traits<String> {
|
|||
|
||||
}
|
||||
|
||||
[[nodiscard]] ALWAYS_INLINE AK::FlyString operator""_fly_string(char const* cstring, size_t length)
|
||||
[[nodiscard]] ALWAYS_INLINE constexpr AK::FlyString operator""_fly_string(char const* cstring, size_t length)
|
||||
{
|
||||
// OPTIMIZATION: Short ASCII strings become compile-time constants with no runtime validation or table lookup.
|
||||
if (length <= AK::Detail::MAX_SHORT_STRING_BYTE_COUNT
|
||||
&& AK::all_of(cstring, cstring + length, AK::is_ascii)) {
|
||||
return AK::FlyString::from_ascii_short_string_without_validation(cstring, length);
|
||||
}
|
||||
|
||||
ASSERT(Utf8View(AK::StringView(cstring, length)).validate());
|
||||
return AK::FlyString::from_utf8_without_validation({ cstring, length });
|
||||
}
|
||||
|
|
|
|||
18
AK/String.h
18
AK/String.h
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/AllOf.h>
|
||||
#include <AK/CharacterTypes.h>
|
||||
#include <AK/Concepts.h>
|
||||
#include <AK/Format.h>
|
||||
|
|
@ -56,6 +57,15 @@ public:
|
|||
[[nodiscard]] static String from_utf8_without_validation(ReadonlyBytes);
|
||||
[[nodiscard]] static String from_ascii_without_validation(ReadonlyBytes);
|
||||
|
||||
[[nodiscard]] static constexpr String from_ascii_short_string_without_validation(char const* data, size_t length)
|
||||
{
|
||||
VERIFY(length <= Detail::MAX_SHORT_STRING_BYTE_COUNT);
|
||||
auto short_string = Detail::ShortString::create_with_byte_count(length);
|
||||
for (size_t i = 0; i < length; ++i)
|
||||
short_string.storage[i] = static_cast<u8>(data[i]);
|
||||
return String { StringBase { short_string } };
|
||||
}
|
||||
|
||||
static ErrorOr<String> from_string_builder(Badge<StringBuilder>, StringBuilder&);
|
||||
[[nodiscard]] static String from_string_builder_without_validation(Badge<StringBuilder>, StringBuilder&);
|
||||
|
||||
|
|
@ -266,8 +276,14 @@ struct ASCIICaseInsensitiveStringTraits : public Traits<String> {
|
|||
|
||||
}
|
||||
|
||||
[[nodiscard]] ALWAYS_INLINE AK::String operator""_string(char const* cstring, size_t length)
|
||||
[[nodiscard]] ALWAYS_INLINE constexpr AK::String operator""_string(char const* cstring, size_t length)
|
||||
{
|
||||
// OPTIMIZATION: Short ASCII strings become compile-time constants with no runtime validation or heap allocation.
|
||||
if (length <= AK::Detail::MAX_SHORT_STRING_BYTE_COUNT
|
||||
&& AK::all_of(cstring, cstring + length, AK::is_ascii)) {
|
||||
return AK::String::from_ascii_short_string_without_validation(cstring, length);
|
||||
}
|
||||
|
||||
ASSERT(Utf8View(AK::StringView(cstring, length)).validate());
|
||||
return AK::String::from_utf8_without_validation({ cstring, length });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,8 +146,26 @@ public:
|
|||
// This is primarily interesting to unit tests.
|
||||
[[nodiscard]] static size_t number_of_utf16_fly_strings();
|
||||
|
||||
[[nodiscard]] static constexpr Utf16FlyString from_ascii_short_string_without_validation(char const* data, size_t length)
|
||||
{
|
||||
VERIFY(length <= Detail::MAX_SHORT_STRING_BYTE_COUNT);
|
||||
auto short_string = Detail::ShortString::create_with_byte_count(length);
|
||||
for (size_t i = 0; i < length; ++i)
|
||||
short_string.storage[i] = static_cast<u8>(data[i]);
|
||||
return Utf16FlyString { Detail::Utf16StringBase { short_string } };
|
||||
}
|
||||
|
||||
[[nodiscard]] static constexpr Utf16FlyString from_ascii_short_string_without_validation(char16_t const* data, size_t length)
|
||||
{
|
||||
VERIFY(length <= Detail::MAX_SHORT_STRING_BYTE_COUNT);
|
||||
auto short_string = Detail::ShortString::create_with_byte_count(length);
|
||||
for (size_t i = 0; i < length; ++i)
|
||||
short_string.storage[i] = static_cast<u8>(data[i]);
|
||||
return Utf16FlyString { Detail::Utf16StringBase { short_string } };
|
||||
}
|
||||
|
||||
private:
|
||||
ALWAYS_INLINE explicit Utf16FlyString(Detail::Utf16StringBase data)
|
||||
ALWAYS_INLINE constexpr explicit Utf16FlyString(Detail::Utf16StringBase data)
|
||||
: m_data(move(data))
|
||||
{
|
||||
}
|
||||
|
|
@ -202,15 +220,26 @@ inline constexpr bool IsHashCompatible<Utf16FlyString, Utf16String> = true;
|
|||
|
||||
}
|
||||
|
||||
[[nodiscard]] ALWAYS_INLINE AK::Utf16FlyString operator""_utf16_fly_string(char const* string, size_t length)
|
||||
[[nodiscard]] ALWAYS_INLINE constexpr AK::Utf16FlyString operator""_utf16_fly_string(char const* string, size_t length)
|
||||
{
|
||||
// OPTIMIZATION: Short ASCII strings become compile-time constants with no runtime validation or table lookup.
|
||||
if (length <= AK::Detail::MAX_SHORT_STRING_BYTE_COUNT
|
||||
&& AK::all_of(string, string + length, AK::is_ascii)) {
|
||||
return AK::Utf16FlyString::from_ascii_short_string_without_validation(string, length);
|
||||
}
|
||||
|
||||
AK::StringView view { string, length };
|
||||
|
||||
ASSERT(AK::Utf8View { view }.validate());
|
||||
return AK::Utf16FlyString::from_utf8_without_validation(view);
|
||||
}
|
||||
|
||||
[[nodiscard]] ALWAYS_INLINE AK::Utf16FlyString operator""_utf16_fly_string(char16_t const* string, size_t length)
|
||||
[[nodiscard]] ALWAYS_INLINE constexpr AK::Utf16FlyString operator""_utf16_fly_string(char16_t const* string, size_t length)
|
||||
{
|
||||
// OPTIMIZATION: Short ASCII strings become compile-time constants with no runtime work.
|
||||
if (length <= AK::Detail::MAX_SHORT_STRING_BYTE_COUNT
|
||||
&& AK::all_of(string, string + length, AK::is_ascii))
|
||||
return AK::Utf16FlyString::from_ascii_short_string_without_validation(string, length);
|
||||
|
||||
return AK::Utf16FlyString::from_utf16({ string, length });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/AllOf.h>
|
||||
#include <AK/Badge.h>
|
||||
#include <AK/Error.h>
|
||||
#include <AK/Format.h>
|
||||
|
|
@ -81,6 +82,24 @@ public:
|
|||
return Utf16String { short_string };
|
||||
}
|
||||
|
||||
[[nodiscard]] static constexpr Utf16String from_ascii_short_string_without_validation(char const* data, size_t length)
|
||||
{
|
||||
VERIFY(length <= Detail::MAX_SHORT_STRING_BYTE_COUNT);
|
||||
auto short_string = Detail::ShortString::create_with_byte_count(length);
|
||||
for (size_t i = 0; i < length; ++i)
|
||||
short_string.storage[i] = static_cast<u8>(data[i]);
|
||||
return Utf16String { short_string };
|
||||
}
|
||||
|
||||
[[nodiscard]] static constexpr Utf16String from_ascii_short_string_without_validation(char16_t const* data, size_t length)
|
||||
{
|
||||
VERIFY(length <= Detail::MAX_SHORT_STRING_BYTE_COUNT);
|
||||
auto short_string = Detail::ShortString::create_with_byte_count(length);
|
||||
for (size_t i = 0; i < length; ++i)
|
||||
short_string.storage[i] = static_cast<u8>(data[i]);
|
||||
return Utf16String { short_string };
|
||||
}
|
||||
|
||||
ALWAYS_INLINE static Utf16String from_code_point(u32 code_point)
|
||||
{
|
||||
Array<char16_t, 2> code_units;
|
||||
|
|
@ -331,15 +350,26 @@ struct Traits<Utf16String> : public DefaultTraits<Utf16String> {
|
|||
|
||||
}
|
||||
|
||||
[[nodiscard]] ALWAYS_INLINE AK::Utf16String operator""_utf16(char const* string, size_t length)
|
||||
[[nodiscard]] ALWAYS_INLINE constexpr AK::Utf16String operator""_utf16(char const* string, size_t length)
|
||||
{
|
||||
// OPTIMIZATION: Short ASCII strings become compile-time constants with no runtime validation or heap allocation.
|
||||
if (length <= AK::Detail::MAX_SHORT_STRING_BYTE_COUNT
|
||||
&& AK::all_of(string, string + length, AK::is_ascii)) {
|
||||
return AK::Utf16String::from_ascii_short_string_without_validation(string, length);
|
||||
}
|
||||
|
||||
AK::StringView view { string, length };
|
||||
|
||||
ASSERT(AK::Utf8View { view }.validate());
|
||||
return AK::Utf16String::from_utf8_without_validation(view);
|
||||
}
|
||||
|
||||
[[nodiscard]] ALWAYS_INLINE AK::Utf16String operator""_utf16(char16_t const* string, size_t length)
|
||||
[[nodiscard]] ALWAYS_INLINE constexpr AK::Utf16String operator""_utf16(char16_t const* string, size_t length)
|
||||
{
|
||||
// OPTIMIZATION: Short ASCII strings become compile-time constants with no runtime work.
|
||||
if (length <= AK::Detail::MAX_SHORT_STRING_BYTE_COUNT
|
||||
&& AK::all_of(string, string + length, AK::is_ascii))
|
||||
return AK::Utf16String::from_ascii_short_string_without_validation(string, length);
|
||||
|
||||
return AK::Utf16String::from_utf16({ string, length });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,14 @@ TEST_CASE(empty_string)
|
|||
EXPECT_EQ(FlyString::number_of_fly_strings(), 0u);
|
||||
}
|
||||
|
||||
TEST_CASE(short_ascii_literal_is_constexpr)
|
||||
{
|
||||
// The _fly_string UDL folds short ASCII literals to a compile-time constant.
|
||||
static constexpr FlyString fly = "foo"_fly_string;
|
||||
EXPECT_EQ(fly, "foo"sv);
|
||||
EXPECT_EQ(fly.bytes_as_string_view().length(), 3u);
|
||||
}
|
||||
|
||||
TEST_CASE(short_string)
|
||||
{
|
||||
FlyString fly1 { "foo"_string };
|
||||
|
|
|
|||
|
|
@ -18,6 +18,14 @@
|
|||
#include <AK/Vector.h>
|
||||
#include <ctype.h>
|
||||
|
||||
TEST_CASE(short_ascii_literal_is_constexpr)
|
||||
{
|
||||
// The _string UDL folds short ASCII literals to a compile-time constant.
|
||||
static constexpr String s = "foo"_string;
|
||||
EXPECT_EQ(s, "foo"sv);
|
||||
EXPECT_EQ(s.bytes().size(), 3u);
|
||||
}
|
||||
|
||||
TEST_CASE(construct_empty)
|
||||
{
|
||||
String empty;
|
||||
|
|
|
|||
|
|
@ -14,6 +14,18 @@ static_assert(AK::Concepts::HashCompatible<Utf16FlyString, Utf16String>);
|
|||
static_assert(AK::Concepts::HashCompatible<Utf16View, Utf16FlyString>);
|
||||
static_assert(AK::Concepts::HashCompatible<Utf16FlyString, Utf16View>);
|
||||
|
||||
TEST_CASE(short_ascii_literal_is_constexpr)
|
||||
{
|
||||
// The _utf16_fly_string UDL folds short ASCII literals to compile-time
|
||||
// constants, both for char and char16_t inputs.
|
||||
static constexpr Utf16FlyString from_char = "foo"_utf16_fly_string;
|
||||
static constexpr Utf16FlyString from_char16 = u"bar"_utf16_fly_string;
|
||||
EXPECT_EQ(from_char, "foo"sv);
|
||||
EXPECT_EQ(from_char16, "bar"sv);
|
||||
EXPECT_EQ(from_char.length_in_code_units(), 3u);
|
||||
EXPECT_EQ(from_char16.length_in_code_units(), 3u);
|
||||
}
|
||||
|
||||
TEST_CASE(empty_string)
|
||||
{
|
||||
Utf16FlyString fly {};
|
||||
|
|
|
|||
|
|
@ -24,6 +24,18 @@ static Utf16String make_copy(Utf16String const& string)
|
|||
: Utf16String::from_utf16(string.utf16_view());
|
||||
}
|
||||
|
||||
TEST_CASE(short_ascii_literal_is_constexpr)
|
||||
{
|
||||
// The _utf16 UDL folds short ASCII literals to compile-time constants,
|
||||
// both for char and char16_t inputs.
|
||||
static constexpr Utf16String from_char = "foo"_utf16;
|
||||
static constexpr Utf16String from_char16 = u"bar"_utf16;
|
||||
EXPECT_EQ(from_char, "foo"sv);
|
||||
EXPECT_EQ(from_char16, "bar"sv);
|
||||
EXPECT_EQ(from_char.length_in_code_units(), 3u);
|
||||
EXPECT_EQ(from_char16.length_in_code_units(), 3u);
|
||||
}
|
||||
|
||||
TEST_CASE(empty_string)
|
||||
{
|
||||
Utf16String string {};
|
||||
|
|
|
|||
Loading…
Reference in a new issue