diff --git a/Source/JavaScriptCore/runtime/CachedTypes.cpp b/Source/JavaScriptCore/runtime/CachedTypes.cpp index 02b65a0ec650..ec117cf15ded 100644 --- a/Source/JavaScriptCore/runtime/CachedTypes.cpp +++ b/Source/JavaScriptCore/runtime/CachedTypes.cpp @@ -2272,6 +2272,9 @@ enum class CachedCodeBlockTag { CachedProgramCodeBlockTag, CachedModuleCodeBlockTag, CachedEvalCodeBlockTag, +#if USE(BUN_JSC_ADDITIONS) + CachedFunctionExecutableTag, +#endif }; static CachedCodeBlockTag NODELETE tagFromSourceCodeType(SourceCodeType type) @@ -2284,7 +2287,11 @@ static CachedCodeBlockTag NODELETE tagFromSourceCodeType(SourceCodeType type) case SourceCodeType::ModuleType: return CachedCodeBlockTag::CachedModuleCodeBlockTag; case SourceCodeType::FunctionType: +#if USE(BUN_JSC_ADDITIONS) + return CachedCodeBlockTag::CachedFunctionExecutableTag; +#else break; +#endif } ASSERT_NOT_REACHED(); return static_cast(-1); @@ -2591,6 +2598,9 @@ class CachedSourceCodeKey : public CachedObject { class GenericCacheEntry { public: bool decode(Decoder&, std::pair&) const; +#if USE(BUN_JSC_ADDITIONS) + bool decode(Decoder&, std::pair&) const; +#endif bool decode(Decoder&, SourceCodeKey&) const; bool isStillValid(Decoder&, const SourceCodeKey&, CachedCodeBlockTag) const; @@ -2667,6 +2677,56 @@ class CacheEntry : public GenericCacheEntry { static_assert(alignof(CacheEntry) <= alignof(std::max_align_t)); static_assert(alignof(CacheEntry) <= alignof(std::max_align_t)); +#if USE(BUN_JSC_ADDITIONS) +// A top-level cache entry whose payload is a single UnlinkedFunctionExecutable and the +// tree of UnlinkedFunctionCodeBlocks reachable from it. Programs, modules and eval are +// cached as code blocks because that is their top-level compilation unit; a builtin's +// top-level unit is a function, which has no code-block-shaped entry of its own. +class FunctionExecutableCacheEntry : public GenericCacheEntry { +public: + FunctionExecutableCacheEntry(Encoder& encoder) + : GenericCacheEntry(encoder, CachedCodeBlockTag::CachedFunctionExecutableTag) + { + } + + void encode(Encoder& encoder, std::pair pair) + { + m_key.encode(encoder, pair.first); + m_executable.encode(encoder, pair.second); + } + +private: + friend GenericCacheEntry; + + bool isStillValid(Decoder& decoder, const SourceCodeKey& key) const + { + SourceCodeKey decodedKey; + m_key.decode(decoder, decodedKey); + return decodedKey == key; + } + + bool decode(Decoder& decoder, std::pair& result) const + { + ASSERT(tag() == CachedCodeBlockTag::CachedFunctionExecutableTag); + SourceCodeKey decodedKey; + m_key.decode(decoder, decodedKey); + result = { WTF::move(decodedKey), m_executable.decode(decoder) }; + return true; + } + + bool decode(Decoder& decoder, SourceCodeKey& key) const + { + m_key.decode(decoder, key); + return true; + } + + CachedSourceCodeKey m_key; + CachedPtr m_executable; +}; + +static_assert(alignof(FunctionExecutableCacheEntry) <= alignof(std::max_align_t)); +#endif + bool GenericCacheEntry::decode(Decoder& decoder, std::pair& result) const { if (!isUpToDate(decoder)) @@ -2677,6 +2737,11 @@ bool GenericCacheEntry::decode(Decoder& decoder, std::pair*>(this)->decode(decoder, reinterpret_cast&>(result)); case CachedCodeBlockTag::CachedModuleCodeBlockTag: return std::bit_cast*>(this)->decode(decoder, reinterpret_cast&>(result)); +#if USE(BUN_JSC_ADDITIONS) + case CachedCodeBlockTag::CachedFunctionExecutableTag: + // The caller asked for a code block; this entry holds a function executable. + return false; +#endif case CachedCodeBlockTag::CachedEvalCodeBlockTag: // We do not cache eval code blocks RELEASE_ASSERT_NOT_REACHED(); @@ -2685,6 +2750,19 @@ bool GenericCacheEntry::decode(Decoder& decoder, std::pair& result) const +{ + if (!isUpToDate(decoder)) + return false; + + if (m_tag != CachedCodeBlockTag::CachedFunctionExecutableTag) + return false; + + return std::bit_cast(this)->decode(decoder, result); +} +#endif + bool GenericCacheEntry::decode(Decoder& decoder, SourceCodeKey& key) const { if (!isUpToDate(decoder)) @@ -2695,6 +2773,10 @@ bool GenericCacheEntry::decode(Decoder& decoder, SourceCodeKey& key) const return std::bit_cast*>(this)->decode(decoder, key); case CachedCodeBlockTag::CachedModuleCodeBlockTag: return std::bit_cast*>(this)->decode(decoder, key); +#if USE(BUN_JSC_ADDITIONS) + case CachedCodeBlockTag::CachedFunctionExecutableTag: + return std::bit_cast(this)->decode(decoder, key); +#endif case CachedCodeBlockTag::CachedEvalCodeBlockTag: // We do not cache eval code blocks return false; @@ -2713,6 +2795,10 @@ bool GenericCacheEntry::isStillValid(Decoder& decoder, const SourceCodeKey& key, return std::bit_cast*>(this)->isStillValid(decoder, key); case CachedCodeBlockTag::CachedModuleCodeBlockTag: return std::bit_cast*>(this)->isStillValid(decoder, key); +#if USE(BUN_JSC_ADDITIONS) + case CachedCodeBlockTag::CachedFunctionExecutableTag: + return std::bit_cast(this)->isStillValid(decoder, key); +#endif case CachedCodeBlockTag::CachedEvalCodeBlockTag: // We do not cache eval code blocks RELEASE_ASSERT_NOT_REACHED(); @@ -2758,6 +2844,35 @@ RefPtr encodeFunctionCodeBlock(VM& vm, const UnlinkedFunctionCod return encoder.release(error); } +#if USE(BUN_JSC_ADDITIONS) +RefPtr encodeFunctionExecutable(VM& vm, const SourceCodeKey& key, const UnlinkedFunctionExecutable* executable, BytecodeCacheError& error) +{ + FileSystem::FileHandle invalidFileHandle; + Encoder encoder(vm, invalidFileHandle); + encoder.malloc(encoder)->encode(encoder, { key, executable }); + return encoder.release(error); +} + +UnlinkedFunctionExecutable* decodeFunctionExecutable(VM& vm, const SourceCodeKey& key, Ref cachedBytecode) +{ + if (cachedBytecode->size() < sizeof(FunctionExecutableCacheEntry)) + return nullptr; + + auto* cachedEntry = std::bit_cast(cachedBytecode->span().data()); + Ref decoder = Decoder::create(vm, WTF::move(cachedBytecode), &key.source().provider()); + std::pair entry; + { + DeferGC deferGC(vm); + if (!cachedEntry->decode(decoder.get(), entry)) + return nullptr; + } + if (entry.first != key) + return nullptr; + + return entry.second; +} +#endif + std::optional decodeSourceCodeKey(VM& vm, Ref cachedBytecode) { const auto* cachedEntry = std::bit_cast(cachedBytecode->span().data()); diff --git a/Source/JavaScriptCore/runtime/CachedTypes.h b/Source/JavaScriptCore/runtime/CachedTypes.h index 3ce6592e5b92..be5dcae07485 100644 --- a/Source/JavaScriptCore/runtime/CachedTypes.h +++ b/Source/JavaScriptCore/runtime/CachedTypes.h @@ -128,6 +128,21 @@ JS_EXPORT_PRIVATE RefPtr encodeFunctionCodeBlock(VM&, const Unli JS_EXPORT_PRIVATE void decodeFunctionCodeBlock(Decoder&, int32_t cachedFunctionCodeBlockOffset, WriteBarrier&, const JSCell*); +#if USE(BUN_JSC_ADDITIONS) +// Serialize an UnlinkedFunctionExecutable together with the tree of +// UnlinkedFunctionCodeBlocks already generated beneath it, as a self-validating +// top-level cache entry. `key` must use SourceCodeType::FunctionType, and the +// SourceCode it carries is the executable's parent source. +// +// This exists for the JS builtins, whose top-level compilation unit is a builtin +// function rather than a program or a module. decodeFunctionExecutable() returns +// nullptr when the entry is stale or does not match `key`; the caller is then +// expected to compile from source as usual. +JS_EXPORT_PRIVATE RefPtr encodeFunctionExecutable(VM&, const SourceCodeKey&, const UnlinkedFunctionExecutable*, BytecodeCacheError&); + +JS_EXPORT_PRIVATE UnlinkedFunctionExecutable* decodeFunctionExecutable(VM&, const SourceCodeKey&, Ref); +#endif + bool isCachedBytecodeStillValid(VM&, Ref, const SourceCodeKey&, SourceCodeType); } // namespace JSC diff --git a/Source/JavaScriptCore/runtime/CodeCache.cpp b/Source/JavaScriptCore/runtime/CodeCache.cpp index 8cc0e2a078a2..7549b5162d6d 100644 --- a/Source/JavaScriptCore/runtime/CodeCache.cpp +++ b/Source/JavaScriptCore/runtime/CodeCache.cpp @@ -344,6 +344,31 @@ SourceCodeKey sourceCodeKeyForSerializedModule(VM& vm, const SourceCode& sourceC return sourceCodeKeyForSerializedBytecode(vm, sourceCode, SourceCodeType::ModuleType, StrictModeLexicallyScopedFeature, scriptMode, {}); } +#if USE(BUN_JSC_ADDITIONS) +SourceCodeKey sourceCodeKeyForSerializedFunctionExecutable(VM&, const SourceCode& sourceCode, const String& name) +{ + return SourceCodeKey( + sourceCode, name, SourceCodeType::FunctionType, NoLexicallyScopedFeatures, JSParserScriptMode::Classic, + DerivedContextType::None, EvalContextType::None, false, { }, + std::nullopt); +} + +UnlinkedFunctionCodeBlock* recursivelyGenerateUnlinkedCodeBlockForFunctionExecutable(VM& vm, UnlinkedFunctionExecutable* executable, const SourceCode& parentSource, ParserError& error) +{ + OptionSet codeGenerationMode = { }; + SourceCode source = executable->linkedSourceCode(parentSource); + UnlinkedFunctionCodeBlock* unlinkedCodeBlock = executable->unlinkedCodeBlockFor(vm, source, CodeSpecializationKind::CodeForCall, codeGenerationMode, error, executable->parseMode()); + if (!unlinkedCodeBlock || error.isValid()) + return nullptr; + + generateUnlinkedCodeBlockForFunctions(vm, unlinkedCodeBlock, source, codeGenerationMode, error); + if (error.isValid()) + return nullptr; + + return unlinkedCodeBlock; +} +#endif + RefPtr serializeBytecode(VM& vm, UnlinkedCodeBlock* codeBlock, const SourceCode& source, SourceCodeType codeType, LexicallyScopedFeatures lexicallyScopedFeatures, JSParserScriptMode scriptMode, FileSystem::FileHandle& fileHandle, BytecodeCacheError& error, OptionSet codeGenerationMode) { return encodeCodeBlock(vm, sourceCodeKeyForSerializedBytecode(vm, source, codeType, lexicallyScopedFeatures, scriptMode, codeGenerationMode), codeBlock, fileHandle, error); diff --git a/Source/JavaScriptCore/runtime/CodeCache.h b/Source/JavaScriptCore/runtime/CodeCache.h index 4c76dd3aceda..1e658410b6c3 100644 --- a/Source/JavaScriptCore/runtime/CodeCache.h +++ b/Source/JavaScriptCore/runtime/CodeCache.h @@ -280,4 +280,15 @@ RefPtr serializeBytecode(VM&, UnlinkedCodeBlock*, const SourceCo SourceCodeKey sourceCodeKeyForSerializedProgram(VM&, const SourceCode&); SourceCodeKey sourceCodeKeyForSerializedModule(VM&, const SourceCode&); +#if USE(BUN_JSC_ADDITIONS) +// The function-shaped counterparts of the two pairs above, for serializing a builtin +// with encodeFunctionExecutable(). recursivelyGenerate...() eagerly compiles `executable` +// and every function nested beneath it, which is what makes the whole tree serializable; +// `parentSource` is the source the executable was created from, not its own linked +// sub-range. sourceCodeKeyForSerializedFunctionExecutable() builds the key both the +// encoder and the decoder must agree on. +JS_EXPORT_PRIVATE UnlinkedFunctionCodeBlock* recursivelyGenerateUnlinkedCodeBlockForFunctionExecutable(VM&, UnlinkedFunctionExecutable*, const SourceCode& parentSource, ParserError&); +JS_EXPORT_PRIVATE SourceCodeKey sourceCodeKeyForSerializedFunctionExecutable(VM&, const SourceCode&, const String& name); +#endif + } // namespace JSC