diff options
author | Dominick Allen <djallen@librehumanitas.org> | 2024-10-29 10:28:11 -0500 |
---|---|---|
committer | Dominick Allen <djallen@librehumanitas.org> | 2024-10-29 10:28:11 -0500 |
commit | f281050ddb3b9d658cff67a254eedc3b79de5c5d (patch) | |
tree | 62c8673ca990a1df6169d08435924a69cc0b24b2 | |
parent | 24cd7c8896b2091114e89ffda06b5c63eb2827c7 (diff) |
Reduce string size, adopt clang-style SSO.
-rw-r--r-- | include/fud_allocator.hpp | 5 | ||||
-rw-r--r-- | include/fud_result.hpp | 4 | ||||
-rw-r--r-- | include/fud_string.hpp | 175 | ||||
-rw-r--r-- | source/fud_string.cpp | 364 |
4 files changed, 348 insertions, 200 deletions
diff --git a/include/fud_allocator.hpp b/include/fud_allocator.hpp index 693111b..d4feccf 100644 --- a/include/fud_allocator.hpp +++ b/include/fud_allocator.hpp @@ -26,7 +26,7 @@ namespace fud { -class alignas(size_t) Allocator { +class alignas(std::max_align_t) Allocator { public: virtual ~Allocator() = default; @@ -37,7 +37,8 @@ class alignas(size_t) Allocator { virtual bool isEqual(const Allocator& rhs) const = 0; }; -constexpr bool operator==(const Allocator& lhs, const Allocator& rhs) { +constexpr bool operator==(const Allocator& lhs, const Allocator& rhs) +{ return &lhs == &rhs; } diff --git a/include/fud_result.hpp b/include/fud_result.hpp index 95f3e5c..497b007 100644 --- a/include/fud_result.hpp +++ b/include/fud_result.hpp @@ -18,8 +18,8 @@ #ifndef FUD_RESULT_HPP #define FUD_RESULT_HPP -#include <variant> #include <utility> +#include <variant> namespace fud { @@ -159,7 +159,7 @@ class [[nodiscard]] Result { std::variant<T, E> m_value; }; -#define M_TakeOrReturn(HYGIENE_EXPRESSION) \ +#define M_TakeOrReturn(HYGIENE_EXPRESSION) \ ({ \ auto HYGIENE_RESULT{(HYGIENE_EXPRESSION)}; \ if (HYGIENE_RESULT.isError()) { \ diff --git a/include/fud_string.hpp b/include/fud_string.hpp index 55b1e86..0020c67 100644 --- a/include/fud_string.hpp +++ b/include/fud_string.hpp @@ -41,6 +41,8 @@ struct DrainResult { FudStatus status; }; +/* TODO: make SSO_BUF_LENGTH user configurable. */ + /** \brief The maximum length of a string using the small string optimization * buffer. */ constexpr size_t SSO_BUF_LENGTH = 15; @@ -49,6 +51,8 @@ constexpr size_t SSO_BUF_LENGTH = 15; * for the null terminator. */ constexpr size_t SSO_BUF_SIZE = SSO_BUF_LENGTH + 1; +static constexpr size_t SsoBufSize = 23; + class String; /** \brief A result containing a valid @String or the @FudStatus error @@ -130,6 +134,7 @@ class String { Array<size_t, sizeof...(cStrings)> lengths{}; Array<const char*, sizeof...(cStrings)> strPointers{}; size_t index = 0; + for (const auto* cStringItem : {cStrings...}) { const char* cString = nullptr; if constexpr (std::is_same_v<decltype(cStringItem), const char*>) { @@ -142,11 +147,12 @@ class String { strPointers[index] = cString; auto lengthResult = cStringLength(cString); - if (lengthResult < 0 || lengthResult >= SSIZE_MAX) { + + if (lengthResult < 0 || lengthResult >= std::numeric_limits<ssize_t>::max()) { return StringResult::error(FudStatus::ArgumentInvalid); } auto stringLength = static_cast<size_t>(lengthResult); - if (SIZE_MAX - totalLength < stringLength) { + if (maxStringLength - totalLength < stringLength) { return StringResult::error(FudStatus::Failure); } totalLength += stringLength; @@ -154,38 +160,48 @@ class String { index++; } + fudAssert(totalLength < maxStringLength); + String output{}; - output.m_length = totalLength; output.m_allocator = allocator; - if (output.m_length >= output.m_capacity) { - output.m_capacity = output.m_length + 1; - /* Avoid using compiler expansions in headers */ - auto dataResult = output.allocator()->allocate(output.m_capacity); + utf8* data{nullptr}; + size_t capacity = totalLength + 1; + bool isLarge = capacity > SsoBufSize; + if (isLarge) { + output.m_repr.large.capacity = capacity & largeStringCapacitymask; + output.m_repr.large.length = totalLength; + auto dataResult = output.allocator()->allocate(output.m_repr.large.capacity); if (dataResult.isError()) { return StringResult::error(dataResult.getError()); } - output.m_data = static_cast<utf8*>(dataResult.getOkay()); + output.m_repr.large.data = static_cast<utf8*>(dataResult.getOkay()); + output.m_repr.large.isLarge = 1; + data = output.m_repr.large.data; + } else { + capacity = SsoBufSize; + static_assert(SsoBufSize < std::numeric_limits<int8_t>::max()); + output.m_repr.small.isLarge = 0; + output.m_repr.small.length = static_cast<uint8_t>(totalLength) & smallStringLengthMask; + data = output.m_repr.small.buffer.data(); } - auto* data = output.dataMut(); + fudAssert(data != nullptr); + size_t cumulativeLength = 0; for (size_t idx = 0; idx < strPointers.size(); ++idx) { const auto* cString = strPointers[idx]; - auto copyStatus = copyMem( - data + cumulativeLength, - output.m_capacity - cumulativeLength, - cString, - lengths[idx]); + auto copyStatus = copyMem(data + cumulativeLength, capacity - cumulativeLength, cString, lengths[idx]); fudAssert(copyStatus == FudStatus::Success); cumulativeLength += lengths[idx]; } - auto terminateStatus = output.nullTerminate(); - fudAssert(terminateStatus == FudStatus::Success); + data[cumulativeLength] = '\0'; return StringResult::okay(std::move(output)); } + /** \brief Default constructs a small string of zero length using the global + * fud allocator. */ String() noexcept = default; String(const String& rhs) = delete; @@ -205,35 +221,40 @@ class String { FudStatus copy(const String& rhs); /** \brief The raw length of the string's data, excluding the null terminator. */ - [[nodiscard]] constexpr size_t length() const + [[nodiscard]] size_t length() const { - return m_length; + if (isLarge()) { + return m_repr.large.length; + } + return m_repr.small.length; } /** \brief Indicates if no characters are present in the string's data. */ - [[nodiscard]] constexpr bool empty() const + [[nodiscard]] bool empty() const { - return m_length == 0; + return length() == 0; } /** \brief The total size of the string's data, including the null terminator. */ - [[nodiscard]] constexpr size_t size() const + [[nodiscard]] size_t size() const { - return m_length + 1; + return length() + 1; } /** \brief The current capacity of the string, excluding the reserved slot * for the null terminator. */ - [[nodiscard]] constexpr size_t capacity() const + [[nodiscard]] size_t capacity() const { - fudAssert(m_capacity > 0); - return m_capacity - 1; + if (isLarge()) { + return m_repr.large.capacity - 1U; + } + return SsoBufSize - 1U; } /** \brief The underlying data, guaranteed to have c string representation. */ - [[nodiscard]] constexpr const utf8* data() const + [[nodiscard]] const utf8* data() const { - return isLarge() ? m_data : m_buffer.data(); + return isLarge() ? m_repr.large.data : m_repr.small.buffer.data(); } /** \brief The underlying data as an explicit c string. */ @@ -248,13 +269,13 @@ class String { [[nodiscard]] Option<utf8> back(); - [[nodiscard]] constexpr size_t remainingLength() const + [[nodiscard]] size_t remainingLength() const { - if (m_length >= m_capacity) { + if (length() > capacity()) { return 0; } - return m_capacity - 1U - m_length; + return capacity() - length(); } [[nodiscard]] inline StringView asView() const @@ -295,11 +316,19 @@ class String { const utf8* end() const; private: - static constexpr uint8_t capacityMask = 0x07; + static constexpr size_t maxStringLength = (static_cast<size_t>(1) << 63) - 1; + static constexpr size_t largeStringCapacitymask = (static_cast<size_t>(1) << 63) - 1; + static constexpr uint8_t maxSmallStringLength = SsoBufSize; + static constexpr uint8_t smallStringLengthMask = 0x7F; [[nodiscard]] static bool allocatorValid(Allocator* allocator) { - return ((reinterpret_cast<uintptr_t>(allocator) & capacityMask) == 0); + return allocator != nullptr; + } + + Allocator* allocator() const + { + return m_allocator; } [[nodiscard]] bool nullTerminated() const; @@ -308,52 +337,78 @@ class String { FudStatus nullTerminate(); - Allocator* allocator() const - { - constexpr uintptr_t ALLOCATOR_MASK = ~static_cast<uintptr_t>(capacityMask); - const auto allocptr = reinterpret_cast<uintptr_t>(m_allocator); - return reinterpret_cast<Allocator*>(allocptr & ALLOCATOR_MASK); - } - /** \brief The underlying data, guaranteed to have c string * representation. */ - [[nodiscard]] constexpr utf8* dataMut() + [[nodiscard]] utf8* dataMut() { - return isLarge() ? m_data : m_buffer.data(); + return isLarge() ? m_repr.large.data : m_repr.small.buffer.data(); } void cleanup(); FudStatus resize(size_t newCapacity); - void setLength(size_t newLength) - { - m_length = newLength; - } - /** \brief The allocator used to get storage for characters when the string * is large. */ Allocator* m_allocator{&globalFudAllocator}; - using BufType = Array<utf8, SSO_BUF_SIZE>; + using BufType = Array<utf8, SsoBufSize>; union { - /** \brief The storage for string characters when using SSO. */ - BufType m_buffer{BufType::constFill(0)}; - /** \brief The storage for string characters when the string is - * large. */ - utf8* m_data; - }; + struct { + uint8_t isLarge : 1; + size_t capacity : 63; + size_t length; + utf8* data; + } large; + struct { + uint8_t isLarge : 1 = 0; + uint8_t length : 7 = 0; + BufType buffer{}; + } small{}; + } m_repr{}; + + /** \brief Whether or not the string must use its allocator for storage. */ + [[nodiscard]] bool isLarge() const + { + struct { + uint8_t isLarge : 1; + uint8_t length : 7; + } determinant; + copyMem<1>(determinant, m_repr); + return determinant.isLarge; + } - /** \brief The length of the string excluding the null terminator. */ - size_t m_length{0}; + [[nodiscard]] size_t smallLength() const + { + struct { + uint8_t isLarge : 1; + uint8_t length : 7; + } determinant; + copyMem<1>(determinant, m_repr); + return determinant.isLarge; + } - /** \brief The capacity of the string, including the null terminator. */ - size_t m_capacity{SSO_BUF_SIZE}; + void addToLength(size_t augend) + { + if (isLarge()) { + fudAssert(m_repr.large.length + augend < maxStringLength); + m_repr.large.length += augend; + } else { + fudAssert(m_repr.small.length + augend < maxSmallStringLength); + m_repr.small.length = static_cast<decltype(m_repr.small.length)>((m_repr.small.length + augend)) & + smallStringLengthMask; + } + } - /** \brief Whether or not the string must use its allocator for storage. */ - [[nodiscard]] constexpr bool isLarge() const + void setLength(size_t newLength) { - return m_capacity > SSO_BUF_SIZE; + if (isLarge()) { + fudAssert(newLength < maxStringLength); + m_repr.large.length = newLength; + } else { + fudAssert(newLength < maxSmallStringLength); + m_repr.small.length = static_cast<uint8_t>(newLength) & smallStringLengthMask; + } } }; diff --git a/source/fud_string.cpp b/source/fud_string.cpp index 994689e..4943dac 100644 --- a/source/fud_string.cpp +++ b/source/fud_string.cpp @@ -39,22 +39,44 @@ StringResult String::makeFromCString(const char* cString, Allocator* allocator) } auto lenResult = cStringLength(cString); - if (lenResult < 0 || lenResult >= SSIZE_MAX) { + if (lenResult < 0 || lenResult >= std::numeric_limits<ssize_t>::max()) { return StringResult::error(FudStatus::ArgumentInvalid); } + auto length = static_cast<size_t>(lenResult); + if constexpr (static_cast<size_t>(std::numeric_limits<ssize_t>::max()) > maxStringLength) { + if (length > maxStringLength) { + return StringResult::error(FudStatus::ArgumentInvalid); + } + } + String output{}; output.m_allocator = allocator; - output.m_length = static_cast<size_t>(lenResult); - - if (output.m_length >= output.m_capacity) { - output.m_capacity = output.m_length + 1; - output.m_data = static_cast<utf8*>(M_TakeOrReturn(output.allocator()->allocate(output.m_capacity))); - fudAssert(output.m_data != nullptr); + utf8* data{nullptr}; + size_t capacity = length + 1; + bool isLarge = capacity > SsoBufSize; + if (isLarge) { + output.m_repr.large.capacity = capacity & largeStringCapacitymask; + output.m_repr.large.length = length; + auto dataResult = output.allocator()->allocate(output.m_repr.large.capacity); + if (dataResult.isError()) { + return StringResult::error(dataResult.getError()); + } + output.m_repr.large.data = static_cast<utf8*>(dataResult.getOkay()); + output.m_repr.large.isLarge = 1; + data = output.m_repr.large.data; + } else { + capacity = SsoBufSize; + static_assert(SsoBufSize < std::numeric_limits<int8_t>::max()); + output.m_repr.small.isLarge = 0; + output.m_repr.small.length = static_cast<uint8_t>(length) & smallStringLengthMask; + data = output.m_repr.small.buffer.data(); } - auto copyStatus = copyMem(output.dataMut(), output.m_capacity, cString, output.m_length); + fudAssert(data != nullptr); + + auto copyStatus = copyMem(data, capacity, cString, length); fudAssert(copyStatus == FudStatus::Success); auto terminateStatus = output.nullTerminate(); @@ -70,16 +92,26 @@ StringResult String::from(const String& rhs) } String output{}; - output.m_length = rhs.m_length; - output.m_capacity = rhs.m_capacity; output.m_allocator = rhs.m_allocator; - + utf8* data{nullptr}; + size_t capacity{0}; + size_t length{0}; if (rhs.isLarge()) { - output.m_data = static_cast<utf8*>(M_TakeOrReturn(output.allocator()->allocate(output.m_capacity))); - fudAssert(output.m_data != nullptr); + output.m_repr.large = rhs.m_repr.large; + output.m_repr.large.data = static_cast<utf8*>( + M_TakeOrReturn(output.allocator()->allocate(output.m_repr.large.capacity))); + data = output.m_repr.large.data; + capacity = output.m_repr.large.capacity; + length = output.m_repr.large.length; + } else { + output.m_repr.small = rhs.m_repr.small; + data = output.m_repr.small.buffer.data(); + capacity = SsoBufSize; + length = output.m_repr.small.length; } + fudAssert(data != nullptr); - auto copyResult = copyMem(output.dataMut(), output.m_capacity, rhs.data(), output.m_length); + auto copyResult = copyMem(output.dataMut(), capacity, rhs.data(), length); fudAssert(copyResult == FudStatus::Success); auto nullTerminateStatus = output.nullTerminate(); @@ -98,18 +130,30 @@ StringResult String::from(StringView view, Allocator* allocator) return StringResult::error(FudStatus::ArgumentInvalid); } - String output{}; - output.m_allocator = allocator; - - if (view.m_length >= output.capacity() + 1U) { - output.m_capacity = view.m_length + 1; - - output.m_data = static_cast<utf8*>(M_TakeOrReturn(output.allocator()->allocate(output.m_capacity))); - fudAssert(output.m_data != nullptr); + if (view.nullTerminated()) { + return StringResult::error(FudStatus::ArgumentInvalid); } - output.setLength(view.m_length); - auto copyStatus = copyMem(output.dataMut(), output.m_capacity, view.m_data, output.m_length); + String output{}; + output.m_allocator = allocator; + size_t capacity = view.length() + 1U; + bool isLarge = capacity > SsoBufSize; + utf8* data{nullptr}; + if (isLarge) { + output.m_repr.large.capacity = capacity & largeStringCapacitymask; + output.m_repr.large.length = view.length(); + output.m_repr.large.data = static_cast<utf8*>(M_TakeOrReturn(output.allocator()->allocate(capacity))); + output.m_repr.large.isLarge = 1; + data = output.m_repr.large.data; + } else { + capacity = SsoBufSize; + static_assert(SsoBufSize < std::numeric_limits<int8_t>::max()); + output.m_repr.small.isLarge = 0; + output.m_repr.small.length = static_cast<uint8_t>(view.length()) & smallStringLengthMask; + data = output.m_repr.small.buffer.data(); + } + fudAssert(data != nullptr); + auto copyStatus = copyMem(data, capacity, view.m_data, view.length()); fudAssert(copyStatus == FudStatus::Success); auto terminateStatus = output.nullTerminate(); @@ -118,15 +162,10 @@ StringResult String::from(StringView view, Allocator* allocator) return StringResult::okay(std::move(output)); } -String::String(String&& rhs) noexcept : m_allocator{rhs.m_allocator}, m_length{rhs.m_length}, m_capacity{rhs.m_capacity} +String::String(String&& rhs) noexcept : m_allocator{rhs.m_allocator}, m_repr{std::move(rhs.m_repr)} { - if (rhs.isLarge()) { - m_data = rhs.m_data; - rhs.m_data = nullptr; - } else { - m_buffer = rhs.m_buffer; - auto terminateStatus = nullTerminate(); - fudAssert(terminateStatus == FudStatus::Success); + if (isLarge()) { + rhs.m_repr.large.data = nullptr; } } @@ -147,15 +186,27 @@ FudStatus String::copy(const String& rhs) cleanup(); - m_length = rhs.m_length; - m_capacity = rhs.m_capacity; m_allocator = rhs.m_allocator; + m_repr = rhs.m_repr; + + utf8* data{nullptr}; + size_t capacity{}; + size_t length{}; if (isLarge()) { - m_data = static_cast<utf8*>(M_TakeOrReturn(allocator()->allocate(m_capacity))); + m_repr.large.data = static_cast<utf8*>(M_TakeOrReturn(allocator()->allocate(m_repr.large.capacity))); + capacity = m_repr.large.capacity; + length = m_repr.large.length; + data = m_repr.large.data; + } else { + capacity = SsoBufSize; + length = m_repr.small.length; + data = m_repr.small.buffer.data(); } - auto copyResult = copyMem(dataMut(), m_capacity, rhs.data(), m_length); + fudAssert(data != nullptr); + + auto copyResult = copyMem(dataMut(), capacity, rhs.data(), length); fudAssert(copyResult == FudStatus::Success); auto nullTerminateStatus = nullTerminate(); fudAssert(nullTerminateStatus == FudStatus::Success); @@ -171,15 +222,11 @@ String& String::operator=(String&& rhs) noexcept cleanup(); - m_length = rhs.m_length; - m_capacity = rhs.m_capacity; m_allocator = rhs.m_allocator; - if (rhs.isLarge()) { - m_data = rhs.m_data; - rhs.m_data = nullptr; - } else { - m_buffer = rhs.m_buffer; - fudAssert(nullTerminate() == FudStatus::Success); + m_repr = std::move(rhs.m_repr); + + if (isLarge()) { + rhs.m_repr.large.data = nullptr; } return *this; } @@ -187,10 +234,10 @@ String& String::operator=(String&& rhs) noexcept void String::cleanup() { const auto* allocPtr = allocator(); - if (isLarge() && m_data != nullptr && allocPtr != nullptr) { - auto deallocStatus = allocator()->deallocate(m_data, m_capacity); + if (isLarge() && m_repr.large.data != nullptr && allocPtr != nullptr) { + auto deallocStatus = allocator()->deallocate(m_repr.large.data, m_repr.large.capacity); static_cast<void>(deallocStatus); - m_data = nullptr; + m_repr.large.data = nullptr; } } @@ -200,26 +247,27 @@ FudStatus String::resize(size_t newCapacity) return FudStatus::StringInvalid; } - if (m_length >= newCapacity) { + if (length() >= newCapacity) { return FudStatus::OperationInvalid; } if (!isLarge() && newCapacity <= SSO_BUF_SIZE) { - m_capacity = SSO_BUF_SIZE; return FudStatus::Success; } if (newCapacity <= SSO_BUF_SIZE) { + fudAssert(isLarge()); + + auto len = static_cast<uint8_t>(length()); BufType temp{BufType::constFill(0)}; - auto copyResult = copyMem(dataMut(), temp.size(), temp.data(), length()); + auto copyResult = copyMem(dataMut(), temp.size(), temp.data(), len); fudAssert(copyResult == FudStatus::Success); - auto deallocStatus = allocator()->deallocate(m_data, m_capacity); - m_capacity = SSO_BUF_SIZE; - m_data = nullptr; - - copyMem(m_buffer, temp); - dataMut()[m_length] = '\0'; + auto deallocStatus = allocator()->deallocate(m_repr.large.data, m_repr.large.capacity); + m_repr.small.isLarge = 0; + m_repr.small.length = len & smallStringLengthMask; + copyMem(m_repr.small.buffer, temp); + m_repr.small.buffer[len] = '\0'; return deallocStatus != FudStatus::Success ? FudStatus::DeallocFailure : FudStatus::Success; } @@ -227,17 +275,21 @@ FudStatus String::resize(size_t newCapacity) auto* newData = static_cast<utf8*>(M_TakeOrReturn(allocator()->allocate(newCapacity))); fudAssert(newData != nullptr); - auto copyResult = copyMem(newData, newCapacity, dataMut(), length()); + auto copyResult = copyMem(newData, newCapacity, data(), length()); fudAssert(copyResult == FudStatus::Success); auto deallocStatus = FudStatus::Success; if (isLarge()) { - deallocStatus = allocator()->deallocate(dataMut(), m_capacity); + deallocStatus = allocator()->deallocate(dataMut(), m_repr.large.capacity); } - m_capacity = newCapacity; - m_data = newData; - dataMut()[m_length] = '\0'; + size_t len = length(); + + m_repr.large.isLarge = 1; + m_repr.large.capacity = newCapacity & largeStringCapacitymask; + m_repr.large.data = newData; + m_repr.large.length = len; + m_repr.large.data[m_repr.large.length] = '\0'; fudAssert(valid()); return deallocStatus != FudStatus::Success ? FudStatus::DeallocFailure : FudStatus::Success; @@ -245,7 +297,7 @@ FudStatus String::resize(size_t newCapacity) bool String::nullTerminated() const { - return data() != nullptr && m_length < m_capacity && data()[m_length] == '\0'; + return data() != nullptr && length() <= capacity() && data()[length()] == '\0'; } bool String::valid() const @@ -265,8 +317,8 @@ bool String::utf8Valid() const FudStatus String::nullTerminate() { - if (m_length < m_capacity) { - dataMut()[m_length] = '\0'; + if (length() <= capacity()) { + dataMut()[length()] = '\0'; return FudStatus::Success; } return FudStatus::StringInvalid; @@ -278,7 +330,7 @@ FudStatus String::reserve(size_t newCapacity) return FudStatus::StringInvalid; } - if (newCapacity < m_capacity) { + if (newCapacity < capacity()) { return FudStatus::Success; } @@ -291,7 +343,7 @@ FudStatus String::reserve(size_t newCapacity) return NullOpt; } - utf8 backChar = dataMut()[m_length - 1]; + utf8 backChar = dataMut()[length() - 1]; if (Ascii::valid(backChar)) { return backChar; } @@ -305,13 +357,23 @@ Option<utf8> String::pop() return NullOpt; } - if (m_length < 1) { - return NullOpt; + utf8 letter{}; + if (isLarge()) { + if (m_repr.large.length < 1) { + return NullOpt; + } + m_repr.large.length--; + letter = m_repr.large.data[m_repr.large.length]; + m_repr.large.data[m_repr.large.length] = '\0'; + } else { + if (m_repr.small.length < 1) { + return NullOpt; + } + m_repr.small.length--; + letter = m_repr.small.buffer[m_repr.small.length]; + m_repr.small.buffer[m_repr.small.length] = '\0'; } - m_length--; - auto letter = dataMut()[m_length]; - dataMut()[m_length] = '\0'; return letter; } @@ -327,17 +389,25 @@ FudStatus String::pushBack(utf8 letter) } if (remainingLength() < 1) { - auto newCapacity = m_capacity < SIZE_MAX / 2 ? m_capacity * 2 : SIZE_MAX; + auto cap = capacity() + 1U; + auto newCapacity = cap < maxStringLength / 2 ? cap * 2 : maxStringLength - 1U; const auto resizeStatus = resize(newCapacity); if (resizeStatus != FudStatus::Success) { return resizeStatus; } - fudAssert(m_capacity == newCapacity); + fudAssert(isLarge()); + fudAssert(m_repr.large.capacity == newCapacity); } - dataMut()[m_length] = letter; - m_length++; - dataMut()[m_length] = '\0'; + if (isLarge()) { + m_repr.large.data[m_repr.large.length] = letter; + m_repr.large.length++; + m_repr.large.data[m_repr.large.length] = '\0'; + } else { + m_repr.small.buffer[m_repr.small.length] = letter; + m_repr.small.length++; + m_repr.small.buffer[m_repr.small.length] = '\0'; + } return FudStatus::Success; } @@ -359,25 +429,31 @@ FudStatus String::pushBack(const FudUtf8& letter) auto letterSize = letter.size(); if (remainingLength() < letterSize) { - auto newCapacity = m_capacity < SIZE_MAX / 2 ? m_capacity * 2 : SIZE_MAX; - if ((newCapacity - m_capacity) < (letterSize + 1)) { + auto cap = capacity() + 1U; + auto newCapacity = cap < maxStringLength / 2 ? cap * 2 : maxStringLength - 1U; + if ((newCapacity - cap) < (letterSize + 1)) { return FudStatus::OperationInvalid; } const auto resizeStatus = resize(newCapacity); if (resizeStatus != FudStatus::Success) { return resizeStatus; } - fudAssert(m_capacity == newCapacity); + fudAssert(isLarge()); + fudAssert(m_repr.large.capacity == newCapacity); } - auto copyStatus = copyMem(m_data + m_length, remainingLength(), letterData, letterSize); + auto copyStatus = copyMem(dataMut() + length(), remainingLength(), letterData, letterSize); if (copyStatus != FudStatus::Success) { return copyStatus; } - m_length += letterSize; - dataMut()[m_length] = '\0'; + addToLength(letterSize); + if (isLarge()) { + m_repr.large.data[m_repr.large.length] = '\0'; + } else { + m_repr.small.buffer[m_repr.small.length] = '\0'; + } return FudStatus::Success; } @@ -416,21 +492,26 @@ FudStatus String::append(StringView source) if (newSize < newLength) { // cppcheck-suppress knownConditionTrueFalse return FudStatus::OperationInvalid; } - if (newSize >= m_capacity) { + if (newSize > capacity()) { auto newCapacity = newSize < SIZE_MAX / 2 ? newSize * 2 : SIZE_MAX; const auto resizeStatus = resize(newCapacity); if (resizeStatus != FudStatus::Success) { return resizeStatus; } - fudAssert(m_capacity == newCapacity); + fudAssert(isLarge()); + fudAssert(m_repr.large.capacity == newCapacity); } auto* destPtr = dataMut() + length(); auto status = copyMem(destPtr, remainingLength(), source.data(), source.length()); fudAssert(status == FudStatus::Success); - m_length += source.length(); - status = nullTerminate(); + addToLength(source.length()); + if (isLarge()) { + m_repr.large.data[m_repr.large.length] = '\0'; + } else { + m_repr.small.buffer[m_repr.small.length] = '\0'; + } return status; } @@ -475,7 +556,7 @@ DrainResult String::drain(StringView source) auto* destPtr = dataMut() + length(); auto status = copyMem(destPtr, remainingLength(), firstPart.m_data, firstPart.m_length); fudAssert(status == FudStatus::Success); - m_length += firstPart.m_length; + addToLength(firstPart.m_length); result.bytesWritten += firstPart.m_length; status = nullTerminate(); source.advanceUnsafe(firstPart.m_length); @@ -496,23 +577,24 @@ DrainResult String::drain(StringView source) result.status = FudStatus::OperationInvalid; return result; } - if (newSize >= m_capacity) { + if (newSize > capacity()) { auto newCapacity = newSize < SIZE_MAX / 2 ? newSize * 2 : SIZE_MAX; const auto resizeStatus = resize(newCapacity); if (resizeStatus != FudStatus::Success) { result.status = resizeStatus; return result; } - fudAssert(m_capacity == newCapacity); + fudAssert(isLarge()); + fudAssert(m_repr.large.capacity == newCapacity); } auto* destPtr = dataMut() + length(); - auto status = copyMem(destPtr, remainingLength(), source.data(), source.length()); - fudAssert(status == FudStatus::Success); + result.status = copyMem(destPtr, remainingLength(), source.data(), source.length()); + fudAssert(result.status == FudStatus::Success); result.bytesWritten += source.length(); - m_length = newLength; - status = nullTerminate(); + setLength(newLength); + result.status = nullTerminate(); return result; } @@ -524,45 +606,46 @@ StringResult String::catenate(const char* rhs) const } auto lenResult = cStringLength(rhs); - if (lenResult < 0 || lenResult >= SSIZE_MAX) { + if (lenResult < 0 || lenResult >= static_cast<ssize_t>(maxStringLength)) { return StringResult::error(FudStatus::ArgumentInvalid); } size_t rhsLength = static_cast<size_t>(lenResult); - String output{}; - - if (SIZE_MAX - m_length < rhsLength) { + if (maxStringLength - length() < rhsLength) { return StringResult::error(FudStatus::Failure); } - output.m_allocator = m_allocator; - - output.m_length = m_length + rhsLength; - if (output.m_length > output.m_capacity) { - output.m_capacity = output.m_length + 1; - } - - if (!output.isLarge()) { - auto status = copyMem(output.m_buffer.data(), output.m_capacity, data(), length()); - fudAssert(status == FudStatus::Success); - - status = copyMem(output.m_buffer.data() + length(), output.m_capacity - length(), rhs, rhsLength); - fudAssert(status == FudStatus::Success); + size_t outputLength = rhsLength + length(); - auto terminateStatus = output.nullTerminate(); - fudAssert(terminateStatus == FudStatus::Success); - - return StringResult::okay(std::move(output)); + String output{}; + output.m_allocator = m_allocator; + size_t outputCapacity = outputLength + 1; + utf8* outputData{nullptr}; + if (outputCapacity > SsoBufSize) { + output.m_repr.large.capacity = outputCapacity & largeStringCapacitymask; + output.m_repr.large.length = outputLength; + auto dataResult = output.allocator()->allocate(output.m_repr.large.capacity); + if (dataResult.isError()) { + return StringResult::error(dataResult.getError()); + } + output.m_repr.large.data = static_cast<utf8*>(dataResult.getOkay()); + output.m_repr.large.isLarge = 1; + outputData = output.m_repr.large.data; + } else { + outputCapacity = SsoBufSize; + static_assert(SsoBufSize < std::numeric_limits<int8_t>::max()); + output.m_repr.small.isLarge = 0; + output.m_repr.small.length = static_cast<uint8_t>(outputLength) & smallStringLengthMask; + outputData = output.m_repr.small.buffer.data(); } - output.m_data = static_cast<utf8*>(M_TakeOrReturn(output.allocator()->allocate(output.m_capacity))); - fudAssert(output.m_data != nullptr); + fudAssert(outputData != nullptr); - auto status = copyMem(output.m_data, m_capacity, data(), length()); - fudAssert(status == FudStatus::Success); + auto copyStatus = copyMem(outputData, outputCapacity, data(), length()); + fudAssert(copyStatus == FudStatus::Success); - status = copyMem(output.m_data + length(), output.m_capacity - length(), rhs, rhsLength); - fudAssert(status == FudStatus::Success); + copyStatus = copyMem(outputData + length(), outputCapacity - length(), rhs, rhsLength); + fudAssert(copyStatus == FudStatus::Success); auto terminateStatus = output.nullTerminate(); fudAssert(terminateStatus == FudStatus::Success); @@ -576,29 +659,37 @@ StringResult String::catenate(const String& rhs) const return StringResult::error(FudStatus::ArgumentInvalid); } - if (SIZE_MAX - m_length < rhs.length()) { + if (maxStringLength - length() < rhs.length()) { return StringResult::error(FudStatus::Failure); } String output{}; output.m_allocator = m_allocator; - output.m_length = m_length + rhs.length(); - output.m_capacity = output.m_length + 1; - - if (output.m_capacity < SSO_BUF_SIZE) { - output.m_capacity = SSO_BUF_SIZE; - } - - if (output.isLarge()) { - output.m_data = static_cast<utf8*>(M_TakeOrReturn(output.allocator()->allocate(output.m_capacity))); - fudAssert(output.m_data != nullptr); + size_t outputLength = length() + rhs.length(); + size_t outputCapacity = outputLength + 1; + utf8* outputData{nullptr}; + if (outputCapacity > SsoBufSize) { + output.m_repr.large.capacity = outputCapacity & largeStringCapacitymask; + output.m_repr.large.length = outputLength; + auto dataResult = output.allocator()->allocate(output.m_repr.large.capacity); + if (dataResult.isError()) { + return StringResult::error(dataResult.getError()); + } + output.m_repr.large.data = static_cast<utf8*>(dataResult.getOkay()); + output.m_repr.large.isLarge = 1; + outputData = output.m_repr.large.data; + } else { + outputCapacity = SsoBufSize; + static_assert(SsoBufSize < std::numeric_limits<int8_t>::max()); + output.m_repr.small.isLarge = 0; + output.m_repr.small.length = static_cast<uint8_t>(outputLength) & smallStringLengthMask; + outputData = output.m_repr.small.buffer.data(); } - auto* destPtr = output.dataMut(); - auto status = copyMem(destPtr, m_capacity, data(), length()); + auto status = copyMem(outputData, outputCapacity, data(), length()); fudAssert(status == FudStatus::Success); - status = copyMem(destPtr + length(), output.m_capacity - length(), rhs.data(), rhs.length()); + status = copyMem(outputData + length(), outputCapacity - length(), rhs.data(), rhs.length()); fudAssert(status == FudStatus::Success); auto terminateStatus = output.nullTerminate(); @@ -629,13 +720,14 @@ bool String::compare(const String& rhs) const return diffResult.getOkay() == 0; } -FudStatus String::clear() { +FudStatus String::clear() +{ if (!valid()) { return FudStatus::StringInvalid; } dataMut()[0] = '\0'; - m_length = 0; + setLength(0); return FudStatus::Success; } |