mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-21 23:20:20 +00:00
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:
parent
fcdf3014f1
commit
2457118024
Notes:
github-actions[bot]
2024-10-31 22:27:23 +00:00
Author: https://github.com/yyny Commit: https://github.com/LadybirdBrowser/ladybird/commit/2457118024a Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2032 Reviewed-by: https://github.com/Hendiadyoin1 Reviewed-by: https://github.com/awesomekling
8 changed files with 427 additions and 2 deletions
|
@ -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;
|
||||
|
|
118
AK/FlyString.h
118
AK/FlyString.h
|
@ -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<>
|
||||
|
|
|
@ -125,6 +125,12 @@ class NonnullOwnPtr;
|
|||
template<typename T>
|
||||
class Optional;
|
||||
|
||||
template<>
|
||||
class Optional<String>;
|
||||
|
||||
template<>
|
||||
class Optional<FlyString>;
|
||||
|
||||
template<typename T>
|
||||
class RefPtr;
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
112
AK/String.h
112
AK/String.h
|
@ -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<>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue