diff --git a/AK/ByteStringImpl.cpp b/AK/ByteStringImpl.cpp index 90eb183a64..07d50fbd62 100644 --- a/AK/ByteStringImpl.cpp +++ b/AK/ByteStringImpl.cpp @@ -47,7 +47,7 @@ ByteStringImpl::~ByteStringImpl() = default; NonnullRefPtr ByteStringImpl::create_uninitialized(size_t length, char*& buffer) { VERIFY(length); - void* slot = kmalloc(allocation_size_for_stringimpl(length)); + void* slot = kmalloc(HeapPartition::String, allocation_size_for_stringimpl(length)); VERIFY(slot); auto new_stringimpl = adopt_ref(*new (slot) ByteStringImpl(ConstructWithInlineBuffer, length)); buffer = const_cast(new_stringimpl->characters()); diff --git a/AK/StringBuilder.cpp b/AK/StringBuilder.cpp index 8cab6dd0fa..39a52fa790 100644 --- a/AK/StringBuilder.cpp +++ b/AK/StringBuilder.cpp @@ -17,11 +17,241 @@ #include #include #include +#include #include namespace AK { +StringBuilder::Buffer::Buffer(Buffer const& other) +{ + MUST(try_resize(other.size())); + if (other.size() != 0) + __builtin_memcpy(data(), other.data(), other.size()); +} + +StringBuilder::Buffer::Buffer(Buffer&& other) +{ + move_from(move(other)); +} + +StringBuilder::Buffer::~Buffer() +{ + clear(); +} + +auto StringBuilder::Buffer::operator=(Buffer const& other) -> Buffer& +{ + if (this != &other) { + if (m_size > other.size()) + trim(other.size(), true); + else + MUST(try_resize(other.size())); + + if (other.size() != 0) + __builtin_memcpy(data(), other.data(), other.size()); + } + + return *this; +} + +auto StringBuilder::Buffer::operator=(Buffer&& other) -> Buffer& +{ + if (this != &other) { + clear(); + move_from(move(other)); + } + + return *this; +} + +u8* StringBuilder::Buffer::data() +{ + return m_inline ? m_inline_buffer : m_outline_buffer; +} + +u8 const* StringBuilder::Buffer::data() const +{ + return m_inline ? m_inline_buffer : m_outline_buffer; +} + +Bytes StringBuilder::Buffer::span() +{ + return { data(), size() }; +} + +ReadonlyBytes StringBuilder::Buffer::span() const +{ + return { data(), size() }; +} + +void* StringBuilder::Buffer::end_pointer() +{ + return data() + m_size; +} + +void StringBuilder::Buffer::clear() +{ + if (!m_inline) { + kfree(m_outline_buffer); + m_inline = true; + } + m_size = 0; +} + +void StringBuilder::Buffer::resize(size_t new_size) +{ + MUST(try_resize(new_size)); +} + +void StringBuilder::Buffer::set_size(size_t new_size) +{ + ASSERT(new_size <= capacity()); + m_size = new_size; +} + +void StringBuilder::Buffer::ensure_capacity(size_t new_capacity) +{ + MUST(try_ensure_capacity(new_capacity)); +} + +ErrorOr StringBuilder::Buffer::try_resize(size_t new_size) +{ + if (new_size <= m_size) { + trim(new_size, false); + return {}; + } + + TRY(try_ensure_capacity(new_size)); + set_size(new_size); + return {}; +} + +ErrorOr StringBuilder::Buffer::try_ensure_capacity(size_t new_capacity) +{ + if (new_capacity <= capacity()) + return {}; + return try_ensure_capacity_slowpath(new_capacity); +} + +ErrorOr StringBuilder::Buffer::try_append(char byte) +{ + auto old_size = size(); + Checked new_size = old_size; + new_size += 1; + VERIFY(!new_size.has_overflow()); + + TRY(try_resize(new_size.value())); + data()[old_size] = static_cast(byte); + return {}; +} + +ErrorOr StringBuilder::Buffer::try_append(ReadonlyBytes bytes) +{ + return try_append(bytes.data(), bytes.size()); +} + +ErrorOr StringBuilder::Buffer::try_append(void const* data, size_t data_size) +{ + if (data_size == 0) + return {}; + VERIFY(data != nullptr); + + auto old_size = size(); + Checked new_size = old_size; + new_size += data_size; + VERIFY(!new_size.has_overflow()); + + TRY(try_resize(new_size.value())); + __builtin_memcpy(this->data() + old_size, data, data_size); + return {}; +} + +void StringBuilder::Buffer::append(char byte) +{ + MUST(try_append(byte)); +} + +void StringBuilder::Buffer::append(void const* data, size_t data_size) +{ + MUST(try_append(data, data_size)); +} + +auto StringBuilder::Buffer::leak_outline_buffer() -> Optional +{ + if (m_inline) + return {}; + + auto* outline_buffer = m_outline_buffer; + auto size = m_size; + auto outline_capacity = m_outline_capacity; + + m_inline = true; + m_size = 0; + + return OutlineBuffer { Bytes { outline_buffer, size }, outline_capacity }; +} + +void StringBuilder::Buffer::move_from(Buffer&& other) +{ + m_size = other.m_size; + m_inline = other.m_inline; + + if (other.m_inline) { + VERIFY(other.m_size <= inline_capacity); + if (other.m_size != 0) + __builtin_memcpy(m_inline_buffer, other.m_inline_buffer, other.m_size); + } else { + m_outline_buffer = other.m_outline_buffer; + m_outline_capacity = other.m_outline_capacity; + } + + other.m_size = 0; + other.m_inline = true; +} + +void StringBuilder::Buffer::trim(size_t size, bool may_discard_existing_data) +{ + VERIFY(size <= m_size); + if (!m_inline && size <= inline_capacity) + shrink_into_inline_buffer(size, may_discard_existing_data); + m_size = size; +} + +void StringBuilder::Buffer::shrink_into_inline_buffer(size_t size, bool may_discard_existing_data) +{ + auto* outline_buffer = m_outline_buffer; + if (!may_discard_existing_data) + __builtin_memcpy(m_inline_buffer, outline_buffer, size); + kfree(outline_buffer); + m_inline = true; +} + +ErrorOr StringBuilder::Buffer::try_ensure_capacity_slowpath(size_t new_capacity) +{ + new_capacity = max(new_capacity, (capacity() * 3) / 2); + new_capacity = kmalloc_good_size(new_capacity); + + if (m_inline) { + auto* new_buffer = static_cast(kmalloc(HeapPartition::String, new_capacity)); + if (!new_buffer) + return Error::from_errno(ENOMEM); + + __builtin_memcpy(new_buffer, data(), m_size); + m_outline_buffer = new_buffer; + } else { + auto* new_buffer = static_cast(krealloc(HeapPartition::String, m_outline_buffer, new_capacity)); + if (!new_buffer) + return Error::from_errno(ENOMEM); + + m_outline_buffer = new_buffer; + } + + m_outline_capacity = new_capacity; + m_inline = false; + return {}; +} + static constexpr size_t string_builder_prefix_size(StringBuilder::Mode mode) { switch (mode) { @@ -545,7 +775,7 @@ ErrorOr StringBuilder::try_append_escaped_for_json(StringView string) auto StringBuilder::leak_buffer_for_string_construction() -> Optional { - if (auto buffer = m_buffer.leak_outline_buffer({}); buffer.has_value()) { + if (auto buffer = m_buffer.leak_outline_buffer(); buffer.has_value()) { clear(); return buffer; } diff --git a/AK/StringBuilder.h b/AK/StringBuilder.h index 7a3c66479d..39e68eee5d 100644 --- a/AK/StringBuilder.h +++ b/AK/StringBuilder.h @@ -24,7 +24,64 @@ public: static constexpr auto DEFAULT_MODE = Mode::UTF8; static constexpr size_t inline_capacity = 256; - using Buffer = Detail::ByteBuffer; + class Buffer { + public: + Buffer() = default; + Buffer(Buffer const&); + Buffer(Buffer&&); + ~Buffer(); + + Buffer& operator=(Buffer const&); + Buffer& operator=(Buffer&&); + + [[nodiscard]] u8* data(); + [[nodiscard]] u8 const* data() const; + + [[nodiscard]] Bytes span() LIFETIME_BOUND; + [[nodiscard]] ReadonlyBytes span() const LIFETIME_BOUND; + + [[nodiscard]] void* end_pointer(); + + [[nodiscard]] size_t size() const { return m_size; } + [[nodiscard]] size_t capacity() const { return m_inline ? inline_capacity : m_outline_capacity; } + [[nodiscard]] bool is_inline() const { return m_inline; } + + void clear(); + void resize(size_t); + void set_size(size_t); + void ensure_capacity(size_t); + + ErrorOr try_resize(size_t); + ErrorOr try_ensure_capacity(size_t); + ErrorOr try_append(char); + ErrorOr try_append(ReadonlyBytes); + ErrorOr try_append(void const*, size_t); + + void append(char); + void append(void const*, size_t); + + struct OutlineBuffer { + Bytes buffer; + size_t capacity { 0 }; + }; + Optional leak_outline_buffer(); + + private: + void move_from(Buffer&&); + void trim(size_t, bool may_discard_existing_data); + void shrink_into_inline_buffer(size_t, bool may_discard_existing_data); + ErrorOr try_ensure_capacity_slowpath(size_t); + + union { + u8 m_inline_buffer[inline_capacity]; + struct { + u8* m_outline_buffer; + size_t m_outline_capacity; + }; + }; + size_t m_size { 0 }; + bool m_inline { true }; + }; StringBuilder(); explicit StringBuilder(size_t initial_capacity); diff --git a/AK/StringData.h b/AK/StringData.h index df80fbda3e..f30bf56289 100644 --- a/AK/StringData.h +++ b/AK/StringData.h @@ -27,7 +27,7 @@ public: VERIFY(byte_count); auto capacity = allocation_size_for_string_data(byte_count); - void* slot = kmalloc(capacity); + void* slot = kmalloc(HeapPartition::String, capacity); if (!slot) return Error::from_errno(ENOMEM); @@ -53,7 +53,7 @@ public: VERIFY(byte_count > MAX_SHORT_STRING_BYTE_COUNT); auto capacity = sizeof(StringData) + sizeof(StringData::SubstringData); - void* slot = kmalloc(capacity); + void* slot = kmalloc(HeapPartition::String, capacity); if (!slot) return Error::from_errno(ENOMEM); diff --git a/AK/Utf16StringData.cpp b/AK/Utf16StringData.cpp index 28ec462418..9337dc4f8d 100644 --- a/AK/Utf16StringData.cpp +++ b/AK/Utf16StringData.cpp @@ -21,7 +21,7 @@ NonnullRefPtr Utf16StringData::create_uninitialized(StorageType { auto allocation_size = allocation_size_for_string_data(storage_type == Utf16StringData::StorageType::ASCII, code_unit_length); - void* slot = kmalloc(allocation_size); + void* slot = kmalloc(HeapPartition::String, allocation_size); VERIFY(slot); return adopt_ref(*new (slot) Utf16StringData(storage_type, code_unit_length)); diff --git a/AK/kmalloc.cpp b/AK/kmalloc.cpp index 72ac67a94b..5b8ad3ab34 100644 --- a/AK/kmalloc.cpp +++ b/AK/kmalloc.cpp @@ -134,6 +134,9 @@ static mi_heap_t* heap_for_partition(HeapPartition partition) case HeapPartition::JSObjectStorage: static mi_heap_t* js_object_storage_heap = mi_heap_new(); return js_object_storage_heap; + case HeapPartition::String: + static thread_local mi_heap_t* string_heap = mi_heap_new(); + return string_heap; } VERIFY_NOT_REACHED(); } diff --git a/AK/kmalloc.h b/AK/kmalloc.h index 14e806c149..1c09a48e58 100644 --- a/AK/kmalloc.h +++ b/AK/kmalloc.h @@ -15,6 +15,7 @@ enum class HeapPartition { General, ArrayBuffer, JSObjectStorage, + String, }; [[nodiscard]] void* ak_kcalloc(size_t count, size_t size); diff --git a/Tests/AK/TestString.cpp b/Tests/AK/TestString.cpp index d926fc565e..4b3da7a2c0 100644 --- a/Tests/AK/TestString.cpp +++ b/Tests/AK/TestString.cpp @@ -282,6 +282,21 @@ TEST_CASE(string_builder) EXPECT_EQ(string.bytes().size(), 8u); } +TEST_CASE(string_builder_outlined_to_string) +{ + StringBuilder builder; + builder.append_repeated("abcd"sv, 100); + + auto string = MUST(builder.to_string()); + EXPECT_EQ(string.bytes_as_string_view().length(), 400u); + EXPECT(string.starts_with_bytes("abcd"sv)); + EXPECT(string.ends_with_bytes("abcd"sv)); + + builder.append("reused"sv); + auto reused_string = MUST(builder.to_string()); + EXPECT_EQ(reused_string, "reused"sv); +} + TEST_CASE(ak_format) { auto foo = MUST(String::formatted("Hello {}", "friends"_string));