From dfd7895d26ef0deeebfd41ea851f14db0fe3a748 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Sun, 23 Aug 2026 11:12:10 +0000 Subject: [PATCH] UnlinkedMetadataTable: hold only the offset table while no CodeBlock is linked finalize() (and the bytecode-cache decoder) allocated the full value-profile + LinkingData + metadata buffer up front and the UnlinkedCodeBlock kept it for its whole life, including after every CodeBlock linked from it had been destroyed, and for cache-decoded functions that were never linked at all. The unlinked table now holds just the offset table; link() allocates the full buffer (zeroed, as before) and unlink() of the owning MetadataTable shrinks back to the offset table. Decoding every function of a 75 MB bytecode cache (48k UnlinkedCodeBlocks, none linked): peak RSS 217 MB -> 175 MB. --- .../bytecode/UnlinkedMetadataTable.cpp | 16 +++++--------- .../bytecode/UnlinkedMetadataTable.h | 14 +++++++----- .../bytecode/UnlinkedMetadataTableInlines.h | 22 +++++++++++-------- Source/JavaScriptCore/runtime/CachedTypes.cpp | 2 +- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Source/JavaScriptCore/bytecode/UnlinkedMetadataTable.cpp b/Source/JavaScriptCore/bytecode/UnlinkedMetadataTable.cpp index 9796701f7d646..2940c875f0aa6 100644 --- a/Source/JavaScriptCore/bytecode/UnlinkedMetadataTable.cpp +++ b/Source/JavaScriptCore/bytecode/UnlinkedMetadataTable.cpp @@ -148,23 +148,17 @@ void UnlinkedMetadataTable::finalize() }); #endif - unsigned valueProfileSize = m_numValueProfiles * sizeof(ValueProfile); + ASSERT(!m_isLinked); if (m_is32Bit) { - // offset already accounts for s_offset16TableSize - uint8_t* newBuffer = reinterpret_cast_ptr(MetadataTableMalloc::malloc(valueProfileSize + sizeof(LinkingData) + s_offset32TableSize + offset)); - memset(newBuffer, 0, valueProfileSize + sizeof(LinkingData) + s_offset16TableSize); - memset(newBuffer + valueProfileSize + sizeof(LinkingData) + s_offset16TableSize + s_offset32TableSize, 0, offset - s_offset16TableSize); - Offset32* buffer = std::bit_cast(newBuffer + valueProfileSize + sizeof(LinkingData) + s_offset16TableSize); + uint8_t* newBuffer = reinterpret_cast_ptr(MetadataTableMalloc::zeroedMalloc(s_offset16TableSize + s_offset32TableSize)); + Offset32* buffer = std::bit_cast(newBuffer + s_offset16TableSize); for (unsigned i = 0; i < s_offsetTableEntries; ++i) buffer[i] = preprocessBuffer()[i] + s_offset32TableSize; MetadataTableMalloc::free(m_rawBuffer); m_rawBuffer = newBuffer; } else { - // offset already accounts for s_offset16TableSize - uint8_t* newBuffer = reinterpret_cast_ptr(MetadataTableMalloc::malloc(valueProfileSize + sizeof(LinkingData) + offset)); - memset(newBuffer, 0, valueProfileSize + sizeof(LinkingData)); - memset(newBuffer + valueProfileSize + sizeof(LinkingData) + s_offset16TableSize, 0, offset - s_offset16TableSize); - Offset16* buffer = std::bit_cast(newBuffer + valueProfileSize + sizeof(LinkingData)); + uint8_t* newBuffer = reinterpret_cast_ptr(MetadataTableMalloc::malloc(s_offset16TableSize)); + Offset16* buffer = std::bit_cast(newBuffer); for (unsigned i = 0; i < s_offsetTableEntries; ++i) buffer[i] = preprocessBuffer()[i]; MetadataTableMalloc::free(m_rawBuffer); diff --git a/Source/JavaScriptCore/bytecode/UnlinkedMetadataTable.h b/Source/JavaScriptCore/bytecode/UnlinkedMetadataTable.h index 4ff59f77ff002..9e8da87c45266 100644 --- a/Source/JavaScriptCore/bytecode/UnlinkedMetadataTable.h +++ b/Source/JavaScriptCore/bytecode/UnlinkedMetadataTable.h @@ -105,12 +105,12 @@ class UnlinkedMetadataTable : public ThreadSafeRefCounted enum EmptyTag { Empty }; UnlinkedMetadataTable(); - UnlinkedMetadataTable(bool is32Bit, unsigned numValueProfiles, unsigned lastOffset); + UnlinkedMetadataTable(bool is32Bit, unsigned numValueProfiles); UnlinkedMetadataTable(EmptyTag); - static Ref create(bool is32Bit, unsigned numValueProfiles, unsigned lastOffset) + static Ref create(bool is32Bit, unsigned numValueProfiles) { - return adoptRef(*new UnlinkedMetadataTable(is32Bit, numValueProfiles, lastOffset)); + return adoptRef(*new UnlinkedMetadataTable(is32Bit, numValueProfiles)); } static Ref empty() @@ -151,18 +151,20 @@ class UnlinkedMetadataTable : public ThreadSafeRefCounted // Then, s_offset16TableSize and s_offset16TableSize + s_offset32TableSize offer the same alignment characteristics for subsequent Metadata. static constexpr unsigned s_offset32TableSize = roundUpToMultipleOf(s_offsetTableEntries * sizeof(Offset32)); - void* buffer() const { return m_rawBuffer + m_numValueProfiles * sizeof(ValueProfile) + sizeof(LinkingData); } + // While no MetadataTable shares m_rawBuffer (!m_isLinked), the buffer holds only the offset table. + unsigned prefixSize() const { return m_isLinked ? m_numValueProfiles * sizeof(ValueProfile) + sizeof(LinkingData) : 0; } + void* buffer() const { return m_rawBuffer + prefixSize(); } Offset32* preprocessBuffer() const { return std::bit_cast(m_rawBuffer); } Offset16* offsetTable16() const { ASSERT(!m_is32Bit); - return std::bit_cast(m_rawBuffer + m_numValueProfiles * sizeof(ValueProfile) + sizeof(LinkingData)); + return std::bit_cast(m_rawBuffer + prefixSize()); } Offset32* offsetTable32() const { ASSERT(m_is32Bit); - return std::bit_cast(m_rawBuffer + m_numValueProfiles * sizeof(ValueProfile) + sizeof(LinkingData) + s_offset16TableSize); + return std::bit_cast(m_rawBuffer + prefixSize() + s_offset16TableSize); } bool m_hasMetadata : 1; diff --git a/Source/JavaScriptCore/bytecode/UnlinkedMetadataTableInlines.h b/Source/JavaScriptCore/bytecode/UnlinkedMetadataTableInlines.h index 2c3a3b446265d..bc81cb1b4b317 100644 --- a/Source/JavaScriptCore/bytecode/UnlinkedMetadataTableInlines.h +++ b/Source/JavaScriptCore/bytecode/UnlinkedMetadataTableInlines.h @@ -42,13 +42,15 @@ ALWAYS_INLINE UnlinkedMetadataTable::UnlinkedMetadataTable() { } -ALWAYS_INLINE UnlinkedMetadataTable::UnlinkedMetadataTable(bool is32Bit, unsigned numValueProfiles, unsigned lastOffset) +// A finalized table as it comes out of the bytecode cache: the caller fills in the offset table; the value-profile / +// metadata buffer is allocated at link(). +ALWAYS_INLINE UnlinkedMetadataTable::UnlinkedMetadataTable(bool is32Bit, unsigned numValueProfiles) : m_hasMetadata(true) , m_isFinalized(false) , m_isLinked(false) , m_is32Bit(is32Bit) , m_numValueProfiles(numValueProfiles) - , m_rawBuffer(static_cast(MetadataTableMalloc::zeroedMalloc((sizeof(ValueProfile) * numValueProfiles) + sizeof(LinkingData) + lastOffset))) + , m_rawBuffer(static_cast(MetadataTableMalloc::zeroedMalloc(is32Bit ? s_offset16TableSize + s_offset32TableSize : s_offset16TableSize))) { } @@ -123,21 +125,18 @@ ALWAYS_INLINE RefPtr UnlinkedMetadataTable::link() unsigned totalSize = this->totalSize(); unsigned offsetTableSize = this->offsetTableSize(); unsigned valueProfileSize = m_numValueProfiles * sizeof(ValueProfile); - uint8_t* buffer; + uint8_t* buffer = static_cast(MetadataTableMalloc::zeroedMalloc(sizeof(LinkingData) + totalSize)); + memcpy(buffer + valueProfileSize + sizeof(LinkingData), this->buffer(), offsetTableSize); if (!m_isLinked) { + MetadataTableMalloc::free(m_rawBuffer); + m_rawBuffer = buffer; m_isLinked = true; - buffer = m_rawBuffer; } else { #if ENABLE(METADATA_STATISTICS) MetadataStatistics::numberOfCopiesFromLinking++; MetadataStatistics::linkingCopyMemory += sizeof(LinkingData) + totalSize; #endif - buffer = static_cast(MetadataTableMalloc::malloc(sizeof(LinkingData) + totalSize)); - memcpy(buffer + valueProfileSize + sizeof(LinkingData), m_rawBuffer + valueProfileSize + sizeof(LinkingData), offsetTableSize); } - // FIXME: Is this needed since we'll clear the data in the CodeBlock Constructor... Plus I could see caching value profiles being profitable. - memset(buffer, 0, valueProfileSize); - memset(buffer + valueProfileSize + sizeof(LinkingData) + offsetTableSize, 0, totalSize - offsetTableSize - valueProfileSize); return adoptRef(*new (buffer + valueProfileSize + sizeof(LinkingData)) MetadataTable(*this)); } @@ -149,6 +148,11 @@ ALWAYS_INLINE void UnlinkedMetadataTable::unlink(MetadataTable& metadataTable) if (metadataTable.buffer() == buffer()) { ASSERT(m_isLinked); + unsigned offsetTableSize = this->offsetTableSize(); + uint8_t* compact = static_cast(MetadataTableMalloc::malloc(offsetTableSize)); + memcpy(compact, buffer(), offsetTableSize); + MetadataTableMalloc::free(m_rawBuffer); + m_rawBuffer = compact; m_isLinked = false; return; } diff --git a/Source/JavaScriptCore/runtime/CachedTypes.cpp b/Source/JavaScriptCore/runtime/CachedTypes.cpp index 29d4751b3cb72..e636031fc8742 100644 --- a/Source/JavaScriptCore/runtime/CachedTypes.cpp +++ b/Source/JavaScriptCore/runtime/CachedTypes.cpp @@ -1614,7 +1614,7 @@ class CachedMetadataTable : public CachedObject { if (!m_hasMetadata) return UnlinkedMetadataTable::empty(); - Ref metadataTable = UnlinkedMetadataTable::create(m_is32Bit, m_numValueProfiles, m_metadata[UnlinkedMetadataTable::s_offsetTableEntries - 1]); + Ref metadataTable = UnlinkedMetadataTable::create(m_is32Bit, m_numValueProfiles); metadataTable->m_isFinalized = true; metadataTable->m_isLinked = false; metadataTable->m_hasMetadata = m_hasMetadata;