From e086adf41061804e36424a5b7ecda63f9cc07579 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Sun, 23 Aug 2026 11:02:28 +0000 Subject: [PATCH] Bytecode cache: write each code block's records contiguously, bodies breadth-first, expression info last The encoder recursed into a nested function's body as soon as it wrote that function's executable record, so the N records a code block reads when it is decoded were interleaved with N whole subtrees. Decoding one block of a mapped payload touched pages across most of the file (measured: decoding only the top-level block of a 75 MB cache made 33 MB of it resident). The encoder now writes a block's own arrays and all of its children's executable records back to back, queues the children's bodies and emits them breadth-first, and emits every block's ExpressionInfo (read only when a stack trace is built) after all bodies. Offsets in the format are relative, so the decoder is unchanged and decoded objects are identical. Same 75 MB cache: top-level decode 32.7 MB resident / 143 ms cold -> 1.9 MB / 10 ms; one level deeper 50.4 MB / 280 ms -> 17.8 MB / 117 ms; full decode 785 ms -> 465 ms. --- Source/JavaScriptCore/runtime/CachedTypes.cpp | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/Source/JavaScriptCore/runtime/CachedTypes.cpp b/Source/JavaScriptCore/runtime/CachedTypes.cpp index 7782b062ed60..dd2c2329912c 100644 --- a/Source/JavaScriptCore/runtime/CachedTypes.cpp +++ b/Source/JavaScriptCore/runtime/CachedTypes.cpp @@ -25,6 +25,8 @@ #include "config.h" #include "CachedTypes.h" +#include +#include #include "BaselineJITCode.h" #include "BuiltinNames.h" @@ -164,6 +166,22 @@ class Encoder { m_leafExecutables.add(executable, offset); } + // Layout: a code block's own arrays and its children's executable records are written contiguously; the children's + // bodies follow breadth-first, and data that is only read on rare paths (expression info) goes after every body. + // Decoding one block then reads one contiguous run of the payload rather than records scattered through every + // descendant's subtree, so a mapped payload pages in only what is decoded. + void deferBody(Function&& encodeBody) { m_bodies.append(WTF::move(encodeBody)); } + void deferCold(Function&& encodeCold) { m_cold.append(WTF::move(encodeCold)); } + void encodeDeferred() + { + while (!m_bodies.isEmpty()) + m_bodies.takeFirst()(); + while (!m_cold.isEmpty()) { + m_cold.takeFirst()(); + RELEASE_ASSERT(m_bodies.isEmpty()); + } + } + RefPtr release(BytecodeCacheError& error) { if (!m_currentPage) @@ -291,6 +309,8 @@ class Encoder { Vector m_pages; UncheckedKeyHashMap m_ptrToOffsetMap; LeafExecutableMap m_leafExecutables; + Deque> m_bodies; + Deque> m_cold; }; Decoder::Decoder(VM& vm, Ref cachedBytecode, RefPtr provider) @@ -2530,11 +2550,13 @@ ALWAYS_INLINE void CachedFunctionExecutable::encode(Encoder& encoder, const Unli m_ecmaName.encode(encoder, executable.ecmaName()); m_parentScopeTDZVariables.encode(encoder, executable.m_parentScopeTDZVariables); - m_unlinkedCodeBlockForCall.encode(encoder, executable.m_unlinkedCodeBlockForCall); - m_unlinkedCodeBlockForConstruct.encode(encoder, executable.m_unlinkedCodeBlockForConstruct); - if (!executable.m_unlinkedCodeBlockForCall || !executable.m_unlinkedCodeBlockForConstruct) encoder.addLeafExecutable(&executable, encoder.offsetOf(this)); + + encoder.deferBody([this, &encoder, forCall = executable.m_unlinkedCodeBlockForCall, forConstruct = executable.m_unlinkedCodeBlockForConstruct] { + m_unlinkedCodeBlockForCall.encode(encoder, forCall); + m_unlinkedCodeBlockForConstruct.encode(encoder, forConstruct); + }); } ALWAYS_INLINE UnlinkedFunctionExecutable* CachedFunctionExecutable::decode(Decoder& decoder) const @@ -2656,7 +2678,9 @@ ALWAYS_INLINE void CachedCodeBlock::encode(Encoder& encoder, cons m_instructions.encode(encoder, codeBlock.m_instructions.get()); m_constantRegisters.encode(encoder, codeBlock.m_constantRegisters); m_constantsSourceCodeRepresentation.encode(encoder, codeBlock.m_constantsSourceCodeRepresentation); - m_expressionInfo.encode(encoder, codeBlock.m_expressionInfo.get()); + encoder.deferCold([this, &encoder, expressionInfo = codeBlock.m_expressionInfo.get()] { + m_expressionInfo.encode(encoder, expressionInfo); + }); m_jumpTargets.encode(encoder, codeBlock.m_jumpTargets); m_outOfLineJumpTargets.encode(encoder, codeBlock.m_outOfLineJumpTargets); @@ -2844,6 +2868,7 @@ RefPtr encodeCodeBlock(VM& vm, const SourceCodeKey& key, const U encodeCodeBlock(encoder, key, codeBlock); else ASSERT(classInfo == UnlinkedEvalCodeBlock::info()); + encoder.encodeDeferred(); return encoder.release(error); } @@ -2860,6 +2885,7 @@ RefPtr encodeFunctionCodeBlock(VM& vm, const UnlinkedFunctionCod FileSystem::FileHandle invalidFileHandle; Encoder encoder(vm, invalidFileHandle); encoder.malloc()->encode(encoder, *codeBlock); + encoder.encodeDeferred(); return encoder.release(error); }