AK: Add template specializations for Optional<{,Fly}String>

Slice the size of `Optional<{,Fly}String>` in half by introducing
`UINTPTR_MAX` as an invalid bit pattern for these values.
This commit is contained in:
Jonne Ransijn 2024-10-28 22:53:16 +01:00 committed by Andreas Kling
parent fcdf3014f1
commit 2457118024
Notes: github-actions[bot] 2024-10-31 22:27:23 +00:00
8 changed files with 427 additions and 2 deletions

View file

@ -50,6 +50,8 @@ FlyString FlyString::from_utf8_without_validation(ReadonlyBytes string)
FlyString::FlyString(String const& string)
{
ASSERT(!string.is_invalid());
if (string.is_short_string()) {
m_data = string;
return;

View file

@ -8,6 +8,7 @@
#include <AK/Error.h>
#include <AK/Format.h>
#include <AK/Optional.h>
#include <AK/Platform.h>
#include <AK/String.h>
#include <AK/Traits.h>
@ -80,12 +81,129 @@ public:
}
private:
friend class Optional<FlyString>;
explicit FlyString(nullptr_t)
: m_data(Detail::StringBase(nullptr))
{
}
explicit FlyString(Detail::StringBase data)
: m_data(move(data))
{
}
Detail::StringBase m_data;
bool is_invalid() const { return m_data.is_invalid(); }
};
template<>
class Optional<FlyString> : public OptionalBase<FlyString> {
template<typename U>
friend class Optional;
public:
using ValueType = FlyString;
Optional() = default;
template<SameAs<OptionalNone> V>
Optional(V) { }
Optional(Optional<FlyString> const& other)
{
if (other.has_value())
m_value = other.m_value;
}
Optional(Optional&& other)
: m_value(other.m_value)
{
}
template<typename U = FlyString>
requires(!IsSame<OptionalNone, RemoveCVReference<U>>)
explicit(!IsConvertible<U&&, FlyString>) Optional(U&& value)
requires(!IsSame<RemoveCVReference<U>, Optional<FlyString>> && IsConstructible<FlyString, U &&>)
: m_value(forward<U>(value))
{
}
template<SameAs<OptionalNone> V>
Optional& operator=(V)
{
clear();
return *this;
}
Optional& operator=(Optional const& other)
{
if (this != &other) {
clear();
m_value = other.m_value;
}
return *this;
}
Optional& operator=(Optional&& other)
{
if (this != &other) {
clear();
m_value = other.m_value;
}
return *this;
}
template<typename O>
ALWAYS_INLINE bool operator==(Optional<O> const& other) const
{
return has_value() == other.has_value() && (!has_value() || value() == other.value());
}
template<typename O>
ALWAYS_INLINE bool operator==(O const& other) const
{
return has_value() && value() == other;
}
void clear()
{
m_value = FlyString(nullptr);
}
[[nodiscard]] bool has_value() const
{
return !m_value.is_invalid();
}
[[nodiscard]] FlyString& value() &
{
VERIFY(has_value());
return m_value;
}
[[nodiscard]] FlyString const& value() const&
{
VERIFY(has_value());
return m_value;
}
[[nodiscard]] FlyString value() &&
{
return release_value();
}
[[nodiscard]] FlyString release_value()
{
VERIFY(has_value());
FlyString released_value = m_value;
clear();
return released_value;
}
private:
FlyString m_value = FlyString(nullptr);
};
template<>

View file

@ -125,6 +125,12 @@ class NonnullOwnPtr;
template<typename T>
class Optional;
template<>
class Optional<String>;
template<>
class Optional<FlyString>;
template<typename T>
class RefPtr;

View file

@ -211,7 +211,7 @@ public:
template<typename U>
requires(IsConstructible<T, U const&> && !IsSpecializationOf<T, Optional> && !IsSpecializationOf<U, Optional>) ALWAYS_INLINE explicit Optional(Optional<U> const& other)
: m_has_value(other.m_has_value)
: m_has_value(other.has_value())
{
if (other.has_value())
new (&m_storage) T(other.value());
@ -219,7 +219,7 @@ public:
template<typename U>
requires(IsConstructible<T, U &&> && !IsSpecializationOf<T, Optional> && !IsSpecializationOf<U, Optional>) ALWAYS_INLINE explicit Optional(Optional<U>&& other)
: m_has_value(other.m_has_value)
: m_has_value(other.has_value())
{
if (other.has_value())
new (&m_storage) T(other.release_value());

View file

@ -211,6 +211,7 @@ public:
private:
friend class ::AK::FlyString;
friend class Optional<String>;
using ShortString = Detail::ShortString;
@ -218,6 +219,117 @@ private:
: StringBase(move(base))
{
}
explicit constexpr String(nullptr_t)
: StringBase(nullptr)
{
}
};
template<>
class Optional<String> : public OptionalBase<String> {
template<typename U>
friend class Optional;
public:
using ValueType = String;
Optional() = default;
template<SameAs<OptionalNone> V>
Optional(V) { }
Optional(Optional<String> const& other)
{
if (other.has_value())
m_value = other.m_value;
}
Optional(Optional&& other)
: m_value(move(other.m_value))
{
}
template<typename U = String>
requires(!IsSame<OptionalNone, RemoveCVReference<U>>)
explicit(!IsConvertible<U&&, String>) Optional(U&& value)
requires(!IsSame<RemoveCVReference<U>, Optional<String>> && IsConstructible<String, U &&>)
: m_value(forward<U>(value))
{
}
template<SameAs<OptionalNone> V>
Optional& operator=(V)
{
clear();
return *this;
}
Optional& operator=(Optional const& other)
{
if (this != &other) {
m_value = other.m_value;
}
return *this;
}
Optional& operator=(Optional&& other)
{
if (this != &other) {
m_value = move(other.m_value);
}
return *this;
}
template<typename O>
ALWAYS_INLINE bool operator==(Optional<O> const& other) const
{
return has_value() == other.has_value() && (!has_value() || value() == other.value());
}
template<typename O>
ALWAYS_INLINE bool operator==(O const& other) const
{
return has_value() && value() == other;
}
void clear()
{
m_value = String(nullptr);
}
[[nodiscard]] bool has_value() const
{
return !m_value.is_invalid();
}
[[nodiscard]] String& value() &
{
VERIFY(has_value());
return m_value;
}
[[nodiscard]] String const& value() const&
{
VERIFY(has_value());
return m_value;
}
[[nodiscard]] String value() &&
{
return release_value();
}
[[nodiscard]] String release_value()
{
VERIFY(has_value());
String released_value = m_value;
clear();
return released_value;
}
private:
String m_value { nullptr };
};
template<>

View file

@ -58,6 +58,7 @@ StringBase& StringBase::operator=(StringBase const& other)
ReadonlyBytes StringBase::bytes() const
{
ASSERT(!is_invalid());
if (is_short_string())
return m_short_string.bytes();
return m_data->bytes();
@ -65,6 +66,7 @@ ReadonlyBytes StringBase::bytes() const
u32 StringBase::hash() const
{
ASSERT(!is_invalid());
if (is_short_string()) {
auto bytes = this->bytes();
return string_hash(reinterpret_cast<char const*>(bytes.data()), bytes.size());
@ -74,6 +76,7 @@ u32 StringBase::hash() const
size_t StringBase::byte_count() const
{
ASSERT(!is_invalid());
if (is_short_string())
return m_short_string.byte_count_and_short_string_flag >> 1;
return m_data->byte_count();
@ -81,6 +84,7 @@ size_t StringBase::byte_count() const
bool StringBase::operator==(StringBase const& other) const
{
ASSERT(!is_invalid());
if (is_short_string())
return m_data == other.m_data;
if (other.is_short_string())
@ -92,6 +96,7 @@ bool StringBase::operator==(StringBase const& other) const
void StringBase::replace_with_string_builder(StringBuilder& builder)
{
ASSERT(!is_invalid());
if (builder.length() <= MAX_SHORT_STRING_BYTE_COUNT) {
return replace_with_new_short_string(builder.length(), [&](Bytes buffer) {
builder.string_view().bytes().copy_to(buffer);
@ -105,6 +110,7 @@ void StringBase::replace_with_string_builder(StringBuilder& builder)
ErrorOr<Bytes> StringBase::replace_with_uninitialized_buffer(size_t byte_count)
{
ASSERT(!is_invalid());
if (byte_count <= MAX_SHORT_STRING_BYTE_COUNT)
return replace_with_uninitialized_short_string(byte_count);
@ -116,6 +122,7 @@ ErrorOr<Bytes> StringBase::replace_with_uninitialized_buffer(size_t byte_count)
ErrorOr<StringBase> StringBase::substring_from_byte_offset_with_shared_superstring(size_t start, size_t length) const
{
ASSERT(!is_invalid());
VERIFY(start + length <= byte_count());
if (length == 0)

View file

@ -75,6 +75,8 @@ public:
[[nodiscard]] ALWAYS_INLINE FlatPtr raw(Badge<FlyString>) const { return bit_cast<FlatPtr>(m_data); }
protected:
bool is_invalid() const { return m_invalid_tag == UINTPTR_MAX; }
template<typename Func>
ErrorOr<void> replace_with_new_string(size_t byte_count, Func&& callback)
{
@ -107,6 +109,11 @@ private:
explicit StringBase(NonnullRefPtr<Detail::StringData const>);
explicit constexpr StringBase(nullptr_t)
: m_invalid_tag(UINTPTR_MAX)
{
}
explicit constexpr StringBase(ShortString short_string)
: m_short_string(short_string)
{
@ -129,6 +136,7 @@ private:
union {
ShortString m_short_string;
Detail::StringData const* m_data { nullptr };
uintptr_t m_invalid_tag;
};
};

View file

@ -8,7 +8,9 @@
#include <LibTest/TestCase.h>
#include <AK/ByteString.h>
#include <AK/FlyString.h>
#include <AK/Optional.h>
#include <AK/String.h>
#include <AK/Vector.h>
TEST_CASE(basic_optional)
@ -269,3 +271,173 @@ TEST_CASE(comparison_reference)
EXPECT_EQ(opt1, opt2);
EXPECT_NE(opt1, opt3);
}
TEST_CASE(string_specialization)
{
EXPECT_EQ(sizeof(Optional<String>), sizeof(String));
{
Optional<String> foo;
EXPECT(!foo.has_value());
foo = "long_enough_to_be_allocated"_string;
EXPECT(foo.has_value());
EXPECT_EQ(foo.value(), "long_enough_to_be_allocated"sv);
}
{
Optional<String> foo = "initial_value"_string;
EXPECT(foo.has_value());
EXPECT_EQ(foo.value(), "initial_value"sv);
foo = "long_enough_to_be_allocated"_string;
EXPECT(foo.has_value());
EXPECT_EQ(foo.value(), "long_enough_to_be_allocated"sv);
}
{
Optional<String> foo;
EXPECT(!foo.has_value());
String bar = "long_enough_to_be_allocated"_string;
foo = bar;
EXPECT(foo.has_value());
EXPECT_EQ(foo.value(), "long_enough_to_be_allocated"sv);
}
{
Optional<String> foo;
EXPECT(!foo.has_value());
Optional<String> bar = "long_enough_to_be_allocated"_string;
foo = bar;
EXPECT(foo.has_value());
EXPECT_EQ(foo.value(), "long_enough_to_be_allocated"sv);
EXPECT(bar.has_value());
EXPECT_EQ(bar.value(), "long_enough_to_be_allocated"sv);
}
{
Optional<String> foo;
EXPECT(!foo.has_value());
foo = Optional<String> { "long_enough_to_be_allocated"_string };
EXPECT(foo.has_value());
EXPECT_EQ(foo.value(), "long_enough_to_be_allocated"sv);
}
{
Optional<String> foo = "long_enough_to_be_allocated"_string;
EXPECT_EQ(foo.value_or("fallback_value"_string), "long_enough_to_be_allocated"sv);
}
{
Optional<String> foo;
EXPECT_EQ(foo.value_or("fallback_value"_string), "fallback_value"sv);
}
{
EXPECT_EQ((Optional<String> { "long_enough_to_be_allocated"_string }).value_or("fallback_value"_string), "long_enough_to_be_allocated"sv);
}
{
EXPECT_EQ((Optional<String> {}).value_or("fallback_value"_string), "fallback_value"sv);
}
}
TEST_CASE(flystring_specialization)
{
EXPECT_EQ(sizeof(Optional<FlyString>), sizeof(FlyString));
{
Optional<FlyString> foo;
EXPECT(!foo.has_value());
foo = "long_enough_to_be_allocated"_fly_string;
EXPECT(foo.has_value());
EXPECT_EQ(foo.value(), "long_enough_to_be_allocated"sv);
}
{
Optional<FlyString> foo = "initial_value"_fly_string;
EXPECT(foo.has_value());
EXPECT_EQ(foo.value(), "initial_value"sv);
foo = "long_enough_to_be_allocated"_fly_string;
EXPECT(foo.has_value());
EXPECT_EQ(foo.value(), "long_enough_to_be_allocated"sv);
}
{
Optional<FlyString> foo;
EXPECT(!foo.has_value());
FlyString bar = "long_enough_to_be_allocated"_fly_string;
foo = bar;
EXPECT(foo.has_value());
EXPECT_EQ(foo.value(), "long_enough_to_be_allocated"sv);
}
{
Optional<FlyString> foo;
EXPECT(!foo.has_value());
Optional<FlyString> bar = "long_enough_to_be_allocated"_fly_string;
foo = bar;
EXPECT(bar.has_value());
EXPECT_EQ(bar.value(), "long_enough_to_be_allocated"sv);
EXPECT(foo.has_value());
EXPECT_EQ(foo.value(), "long_enough_to_be_allocated"sv);
}
{
Optional<FlyString> foo;
EXPECT(!foo.has_value());
foo = Optional<FlyString> { "long_enough_to_be_allocated"_fly_string };
EXPECT(foo.has_value());
EXPECT_EQ(foo.value(), "long_enough_to_be_allocated"sv);
}
{
Optional<FlyString> foo = "long_enough_to_be_allocated"_fly_string;
EXPECT_EQ(foo.value_or("fallback_value"_fly_string), "long_enough_to_be_allocated"sv);
}
{
Optional<FlyString> foo;
EXPECT_EQ(foo.value_or("fallback_value"_fly_string), "fallback_value"sv);
}
{
EXPECT_EQ((Optional<FlyString> { "long_enough_to_be_allocated"_fly_string }).value_or("fallback_value"_fly_string), "long_enough_to_be_allocated"sv);
}
{
EXPECT_EQ((Optional<FlyString> {}).value_or("fallback_value"_fly_string), "fallback_value"sv);
}
}