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:
Andreas Kling
2026-04-14 18:58:46 +02:00
committed by Andreas Kling
parent 7c1d359790
commit 0317007ee1
Notes: github-actions[bot] 2026-04-17 14:24:00 +00:00
8 changed files with 139 additions and 7 deletions

View File

@@ -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 });
}

View File

@@ -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 });
}

View File

@@ -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 });
}

View File

@@ -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 });
}