LibWeb: Make RopeString subclass so PrimitiveString can be smaller

By moving the LHS and RHS pointers used by rope strings into a
RopeString subclass, we shrink PrimitiveString by 16 bytes. Most strings
are not rope strings, so this ends up saving quite a bit of memory.
This commit is contained in:
Andreas Kling 2025-03-30 00:08:39 +00:00 committed by Andreas Kling
commit 152691f9eb
Notes: github-actions[bot] 2025-03-30 06:17:46 +00:00
2 changed files with 56 additions and 23 deletions

View file

@ -20,14 +20,17 @@
namespace JS { namespace JS {
GC_DEFINE_ALLOCATOR(PrimitiveString); GC_DEFINE_ALLOCATOR(PrimitiveString);
GC_DEFINE_ALLOCATOR(RopeString);
PrimitiveString::PrimitiveString(PrimitiveString& lhs, PrimitiveString& rhs) RopeString::RopeString(GC::Ref<PrimitiveString> lhs, GC::Ref<PrimitiveString> rhs)
: m_is_rope(true) : PrimitiveString(RopeTag::Rope)
, m_lhs(&lhs) , m_lhs(lhs)
, m_rhs(&rhs) , m_rhs(rhs)
{ {
} }
RopeString::~RopeString() = default;
PrimitiveString::PrimitiveString(String string) PrimitiveString::PrimitiveString(String string)
: m_utf8_string(move(string)) : m_utf8_string(move(string))
{ {
@ -46,13 +49,11 @@ PrimitiveString::~PrimitiveString()
vm().utf16_string_cache().remove(*m_utf16_string); vm().utf16_string_cache().remove(*m_utf16_string);
} }
void PrimitiveString::visit_edges(Cell::Visitor& visitor) void RopeString::visit_edges(Cell::Visitor& visitor)
{ {
Base::visit_edges(visitor); Base::visit_edges(visitor);
if (m_is_rope) { visitor.visit(m_lhs);
visitor.visit(m_lhs); visitor.visit(m_rhs);
visitor.visit(m_rhs);
}
} }
bool PrimitiveString::is_empty() const bool PrimitiveString::is_empty() const
@ -192,7 +193,7 @@ GC::Ref<PrimitiveString> PrimitiveString::create(VM& vm, PrimitiveString& lhs, P
if (rhs_empty) if (rhs_empty)
return lhs; return lhs;
return vm.heap().allocate<PrimitiveString>(lhs, rhs); return vm.heap().allocate<RopeString>(lhs, rhs);
} }
void PrimitiveString::resolve_rope_if_needed(EncodingPreference preference) const void PrimitiveString::resolve_rope_if_needed(EncodingPreference preference) const
@ -200,6 +201,13 @@ void PrimitiveString::resolve_rope_if_needed(EncodingPreference preference) cons
if (!m_is_rope) if (!m_is_rope)
return; return;
auto const& rope_string = static_cast<RopeString const&>(*this);
return rope_string.resolve(preference);
}
void RopeString::resolve(EncodingPreference preference) const
{
// This vector will hold all the pieces of the rope that need to be assembled // This vector will hold all the pieces of the rope that need to be assembled
// into the resolved string. // into the resolved string.
Vector<PrimitiveString const*> pieces; Vector<PrimitiveString const*> pieces;
@ -213,8 +221,9 @@ void PrimitiveString::resolve_rope_if_needed(EncodingPreference preference) cons
while (!stack.is_empty()) { while (!stack.is_empty()) {
auto const* current = stack.take_last(); auto const* current = stack.take_last();
if (current->m_is_rope) { if (current->m_is_rope) {
stack.append(current->m_rhs); auto& current_rope_string = static_cast<RopeString const&>(*current);
stack.append(current->m_lhs); stack.append(current_rope_string.m_rhs);
stack.append(current_rope_string.m_lhs);
continue; continue;
} }

View file

@ -19,7 +19,7 @@
namespace JS { namespace JS {
class PrimitiveString final : public Cell { class PrimitiveString : public Cell {
GC_CELL(PrimitiveString, Cell); GC_CELL(PrimitiveString, Cell);
GC_DECLARE_ALLOCATOR(PrimitiveString); GC_DECLARE_ALLOCATOR(PrimitiveString);
@ -47,26 +47,50 @@ public:
ThrowCompletionOr<Optional<Value>> get(VM&, PropertyKey const&) const; ThrowCompletionOr<Optional<Value>> get(VM&, PropertyKey const&) const;
private: protected:
explicit PrimitiveString(PrimitiveString&, PrimitiveString&); enum class RopeTag { Rope };
explicit PrimitiveString(String); explicit PrimitiveString(RopeTag)
explicit PrimitiveString(Utf16String); : m_is_rope(true)
{
}
virtual void visit_edges(Cell::Visitor&) override; mutable bool m_is_rope { false };
mutable Optional<String> m_utf8_string;
mutable Optional<Utf16String> m_utf16_string;
enum class EncodingPreference { enum class EncodingPreference {
UTF8, UTF8,
UTF16, UTF16,
}; };
void resolve_rope_if_needed(EncodingPreference) const;
mutable bool m_is_rope { false }; private:
friend class RopeString;
explicit PrimitiveString(String);
explicit PrimitiveString(Utf16String);
void resolve_rope_if_needed(EncodingPreference) const;
};
class RopeString final : public PrimitiveString {
GC_CELL(RopeString, PrimitiveString);
GC_DECLARE_ALLOCATOR(RopeString);
public:
virtual ~RopeString() override;
private:
friend class PrimitiveString;
explicit RopeString(GC::Ref<PrimitiveString>, GC::Ref<PrimitiveString>);
virtual void visit_edges(Visitor&) override;
void resolve(EncodingPreference) const;
mutable GC::Ptr<PrimitiveString> m_lhs; mutable GC::Ptr<PrimitiveString> m_lhs;
mutable GC::Ptr<PrimitiveString> m_rhs; mutable GC::Ptr<PrimitiveString> m_rhs;
mutable Optional<String> m_utf8_string;
mutable Optional<Utf16String> m_utf16_string;
}; };
} }