Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions Source/JavaScriptCore/runtime/CachedTypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2272,6 +2272,9 @@ enum class CachedCodeBlockTag {
CachedProgramCodeBlockTag,
CachedModuleCodeBlockTag,
CachedEvalCodeBlockTag,
#if USE(BUN_JSC_ADDITIONS)
CachedFunctionExecutableTag,
#endif
};

static CachedCodeBlockTag NODELETE tagFromSourceCodeType(SourceCodeType type)
Expand All @@ -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<CachedCodeBlockTag>(-1);
Expand Down Expand Up @@ -2591,6 +2598,9 @@ class CachedSourceCodeKey : public CachedObject<SourceCodeKey> {
class GenericCacheEntry {
public:
bool decode(Decoder&, std::pair<SourceCodeKey, UnlinkedCodeBlock*>&) const;
#if USE(BUN_JSC_ADDITIONS)
bool decode(Decoder&, std::pair<SourceCodeKey, UnlinkedFunctionExecutable*>&) const;
#endif
bool decode(Decoder&, SourceCodeKey&) const;
bool isStillValid(Decoder&, const SourceCodeKey&, CachedCodeBlockTag) const;

Expand Down Expand Up @@ -2667,6 +2677,56 @@ class CacheEntry : public GenericCacheEntry {
static_assert(alignof(CacheEntry<UnlinkedProgramCodeBlock>) <= alignof(std::max_align_t));
static_assert(alignof(CacheEntry<UnlinkedModuleProgramCodeBlock>) <= 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<SourceCodeKey, const UnlinkedFunctionExecutable*> 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<SourceCodeKey, UnlinkedFunctionExecutable*>& 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<CachedFunctionExecutable> m_executable;
};

static_assert(alignof(FunctionExecutableCacheEntry) <= alignof(std::max_align_t));
#endif

Comment thread
coderabbitai[bot] marked this conversation as resolved.
bool GenericCacheEntry::decode(Decoder& decoder, std::pair<SourceCodeKey, UnlinkedCodeBlock*>& result) const
{
if (!isUpToDate(decoder))
Expand All @@ -2677,6 +2737,11 @@ bool GenericCacheEntry::decode(Decoder& decoder, std::pair<SourceCodeKey, Unlink
return std::bit_cast<const CacheEntry<UnlinkedProgramCodeBlock>*>(this)->decode(decoder, reinterpret_cast<std::pair<SourceCodeKey, UnlinkedProgramCodeBlock*>&>(result));
case CachedCodeBlockTag::CachedModuleCodeBlockTag:
return std::bit_cast<const CacheEntry<UnlinkedModuleProgramCodeBlock>*>(this)->decode(decoder, reinterpret_cast<std::pair<SourceCodeKey, UnlinkedModuleProgramCodeBlock*>&>(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();
Expand All @@ -2685,6 +2750,19 @@ bool GenericCacheEntry::decode(Decoder& decoder, std::pair<SourceCodeKey, Unlink
return false;
}

#if USE(BUN_JSC_ADDITIONS)
bool GenericCacheEntry::decode(Decoder& decoder, std::pair<SourceCodeKey, UnlinkedFunctionExecutable*>& result) const
{
if (!isUpToDate(decoder))
return false;

if (m_tag != CachedCodeBlockTag::CachedFunctionExecutableTag)
return false;

return std::bit_cast<const FunctionExecutableCacheEntry*>(this)->decode(decoder, result);
}
#endif

bool GenericCacheEntry::decode(Decoder& decoder, SourceCodeKey& key) const
{
if (!isUpToDate(decoder))
Expand All @@ -2695,6 +2773,10 @@ bool GenericCacheEntry::decode(Decoder& decoder, SourceCodeKey& key) const
return std::bit_cast<const CacheEntry<UnlinkedProgramCodeBlock>*>(this)->decode(decoder, key);
case CachedCodeBlockTag::CachedModuleCodeBlockTag:
return std::bit_cast<const CacheEntry<UnlinkedModuleProgramCodeBlock>*>(this)->decode(decoder, key);
#if USE(BUN_JSC_ADDITIONS)
case CachedCodeBlockTag::CachedFunctionExecutableTag:
return std::bit_cast<const FunctionExecutableCacheEntry*>(this)->decode(decoder, key);
#endif
case CachedCodeBlockTag::CachedEvalCodeBlockTag:
// We do not cache eval code blocks
return false;
Expand All @@ -2713,6 +2795,10 @@ bool GenericCacheEntry::isStillValid(Decoder& decoder, const SourceCodeKey& key,
return std::bit_cast<const CacheEntry<UnlinkedProgramCodeBlock>*>(this)->isStillValid(decoder, key);
case CachedCodeBlockTag::CachedModuleCodeBlockTag:
return std::bit_cast<const CacheEntry<UnlinkedModuleProgramCodeBlock>*>(this)->isStillValid(decoder, key);
#if USE(BUN_JSC_ADDITIONS)
case CachedCodeBlockTag::CachedFunctionExecutableTag:
return std::bit_cast<const FunctionExecutableCacheEntry*>(this)->isStillValid(decoder, key);
#endif
case CachedCodeBlockTag::CachedEvalCodeBlockTag:
// We do not cache eval code blocks
RELEASE_ASSERT_NOT_REACHED();
Expand Down Expand Up @@ -2758,6 +2844,35 @@ RefPtr<CachedBytecode> encodeFunctionCodeBlock(VM& vm, const UnlinkedFunctionCod
return encoder.release(error);
}

#if USE(BUN_JSC_ADDITIONS)
RefPtr<CachedBytecode> encodeFunctionExecutable(VM& vm, const SourceCodeKey& key, const UnlinkedFunctionExecutable* executable, BytecodeCacheError& error)
{
FileSystem::FileHandle invalidFileHandle;
Encoder encoder(vm, invalidFileHandle);
encoder.malloc<FunctionExecutableCacheEntry>(encoder)->encode(encoder, { key, executable });
return encoder.release(error);
}

UnlinkedFunctionExecutable* decodeFunctionExecutable(VM& vm, const SourceCodeKey& key, Ref<CachedBytecode> cachedBytecode)
{
if (cachedBytecode->size() < sizeof(FunctionExecutableCacheEntry))
return nullptr;

auto* cachedEntry = std::bit_cast<const GenericCacheEntry*>(cachedBytecode->span().data());
Ref decoder = Decoder::create(vm, WTF::move(cachedBytecode), &key.source().provider());
std::pair<SourceCodeKey, UnlinkedFunctionExecutable*> entry;
{
DeferGC deferGC(vm);
if (!cachedEntry->decode(decoder.get(), entry))
return nullptr;
}
if (entry.first != key)
return nullptr;

return entry.second;
}
#endif

std::optional<SourceCodeKey> decodeSourceCodeKey(VM& vm, Ref<CachedBytecode> cachedBytecode)
{
const auto* cachedEntry = std::bit_cast<const GenericCacheEntry*>(cachedBytecode->span().data());
Expand Down
15 changes: 15 additions & 0 deletions Source/JavaScriptCore/runtime/CachedTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,21 @@ JS_EXPORT_PRIVATE RefPtr<CachedBytecode> encodeFunctionCodeBlock(VM&, const Unli

JS_EXPORT_PRIVATE void decodeFunctionCodeBlock(Decoder&, int32_t cachedFunctionCodeBlockOffset, WriteBarrier<UnlinkedFunctionCodeBlock>&, 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<CachedBytecode> encodeFunctionExecutable(VM&, const SourceCodeKey&, const UnlinkedFunctionExecutable*, BytecodeCacheError&);

JS_EXPORT_PRIVATE UnlinkedFunctionExecutable* decodeFunctionExecutable(VM&, const SourceCodeKey&, Ref<CachedBytecode>);
#endif

Comment thread
coderabbitai[bot] marked this conversation as resolved.
bool isCachedBytecodeStillValid(VM&, Ref<CachedBytecode>, const SourceCodeKey&, SourceCodeType);

} // namespace JSC
25 changes: 25 additions & 0 deletions Source/JavaScriptCore/runtime/CodeCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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> 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;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
#endif

RefPtr<CachedBytecode> serializeBytecode(VM& vm, UnlinkedCodeBlock* codeBlock, const SourceCode& source, SourceCodeType codeType, LexicallyScopedFeatures lexicallyScopedFeatures, JSParserScriptMode scriptMode, FileSystem::FileHandle& fileHandle, BytecodeCacheError& error, OptionSet<CodeGenerationMode> codeGenerationMode)
{
return encodeCodeBlock(vm, sourceCodeKeyForSerializedBytecode(vm, source, codeType, lexicallyScopedFeatures, scriptMode, codeGenerationMode), codeBlock, fileHandle, error);
Expand Down
11 changes: 11 additions & 0 deletions Source/JavaScriptCore/runtime/CodeCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -280,4 +280,15 @@ RefPtr<CachedBytecode> 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
Loading