Lazily decode cached bytecode
authortzagallo@apple.com <tzagallo@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 7 Mar 2019 19:43:43 +0000 (19:43 +0000)
committertzagallo@apple.com <tzagallo@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 7 Mar 2019 19:43:43 +0000 (19:43 +0000)
https://bugs.webkit.org/show_bug.cgi?id=194810

Reviewed by Saam Barati.

Like lazy parsing, we should pause at code block boundaries. Instead
of always eagerly decoding UnlinkedFunctionExecutable's UnlinkedCodeBlocks,
we store their offsets in the executable and lazily decode them on the next
call to `unlinkedCodeBlockFor`.

* bytecode/UnlinkedFunctionExecutable.cpp:
(JSC::UnlinkedFunctionExecutable::UnlinkedFunctionExecutable):
(JSC::UnlinkedFunctionExecutable::~UnlinkedFunctionExecutable):
(JSC::UnlinkedFunctionExecutable::visitChildren):
(JSC::UnlinkedFunctionExecutable::unlinkedCodeBlockFor):
(JSC::UnlinkedFunctionExecutable::decodeCachedCodeBlocks):
* bytecode/UnlinkedFunctionExecutable.h:
* runtime/CachedTypes.cpp:
(JSC::Decoder::Decoder):
(JSC::Decoder::~Decoder):
(JSC::Decoder::create):
(JSC::Decoder::offsetOf):
(JSC::Decoder::cacheOffset):
(JSC::Decoder::ptrForOffsetFromBase):
(JSC::Decoder::handleForEnvironment const):
(JSC::Decoder::setHandleForEnvironment):
(JSC::Decoder::addFinalizer):
(JSC::VariableLengthObject::isEmpty const):
(JSC::CachedWriteBarrier::isEmpty const):
(JSC::CachedFunctionExecutable::unlinkedCodeBlockForCall const):
(JSC::CachedFunctionExecutable::unlinkedCodeBlockForConstruct const):
(JSC::CachedFunctionExecutable::decode const):
(JSC::UnlinkedFunctionExecutable::UnlinkedFunctionExecutable):
(JSC::decodeCodeBlockImpl):
(JSC::isCachedBytecodeStillValid):
(JSC::decodeFunctionCodeBlock):
* runtime/CachedTypes.h:
(JSC::Decoder::vm):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@242605 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/bytecode/UnlinkedFunctionExecutable.cpp
Source/JavaScriptCore/bytecode/UnlinkedFunctionExecutable.h
Source/JavaScriptCore/runtime/CachedTypes.cpp
Source/JavaScriptCore/runtime/CachedTypes.h

index 7ac7a76..9c65026 100644 (file)
@@ -1,3 +1,44 @@
+2019-03-07  Tadeu Zagallo  <tzagallo@apple.com>
+
+        Lazily decode cached bytecode
+        https://bugs.webkit.org/show_bug.cgi?id=194810
+
+        Reviewed by Saam Barati.
+
+        Like lazy parsing, we should pause at code block boundaries. Instead
+        of always eagerly decoding UnlinkedFunctionExecutable's UnlinkedCodeBlocks,
+        we store their offsets in the executable and lazily decode them on the next
+        call to `unlinkedCodeBlockFor`.
+
+        * bytecode/UnlinkedFunctionExecutable.cpp:
+        (JSC::UnlinkedFunctionExecutable::UnlinkedFunctionExecutable):
+        (JSC::UnlinkedFunctionExecutable::~UnlinkedFunctionExecutable):
+        (JSC::UnlinkedFunctionExecutable::visitChildren):
+        (JSC::UnlinkedFunctionExecutable::unlinkedCodeBlockFor):
+        (JSC::UnlinkedFunctionExecutable::decodeCachedCodeBlocks):
+        * bytecode/UnlinkedFunctionExecutable.h:
+        * runtime/CachedTypes.cpp:
+        (JSC::Decoder::Decoder):
+        (JSC::Decoder::~Decoder):
+        (JSC::Decoder::create):
+        (JSC::Decoder::offsetOf):
+        (JSC::Decoder::cacheOffset):
+        (JSC::Decoder::ptrForOffsetFromBase):
+        (JSC::Decoder::handleForEnvironment const):
+        (JSC::Decoder::setHandleForEnvironment):
+        (JSC::Decoder::addFinalizer):
+        (JSC::VariableLengthObject::isEmpty const):
+        (JSC::CachedWriteBarrier::isEmpty const):
+        (JSC::CachedFunctionExecutable::unlinkedCodeBlockForCall const):
+        (JSC::CachedFunctionExecutable::unlinkedCodeBlockForConstruct const):
+        (JSC::CachedFunctionExecutable::decode const):
+        (JSC::UnlinkedFunctionExecutable::UnlinkedFunctionExecutable):
+        (JSC::decodeCodeBlockImpl):
+        (JSC::isCachedBytecodeStillValid):
+        (JSC::decodeFunctionCodeBlock):
+        * runtime/CachedTypes.h:
+        (JSC::Decoder::vm):
+
 2019-03-06  Mark Lam  <mark.lam@apple.com>
 
         Exception is a JSCell, not a JSObject.
index a142c24..9629c46 100644 (file)
@@ -28,6 +28,7 @@
 
 #include "BuiltinExecutables.h"
 #include "BytecodeGenerator.h"
+#include "CachedTypes.h"
 #include "ClassInfo.h"
 #include "CodeCache.h"
 #include "Debugger.h"
@@ -103,6 +104,9 @@ UnlinkedFunctionExecutable::UnlinkedFunctionExecutable(VM* vm, Structure* struct
     , m_scriptMode(static_cast<unsigned>(scriptMode))
     , m_superBinding(static_cast<unsigned>(node->superBinding()))
     , m_derivedContextType(static_cast<unsigned>(derivedContextType))
+    , m_isCached(false)
+    , m_unlinkedCodeBlockForCall()
+    , m_unlinkedCodeBlockForConstruct()
     , m_name(node->ident())
     , m_ecmaName(node->ecmaName())
     , m_inferredName(node->inferredName())
@@ -120,6 +124,12 @@ UnlinkedFunctionExecutable::UnlinkedFunctionExecutable(VM* vm, Structure* struct
         setClassSource(node->classSource());
 }
 
+UnlinkedFunctionExecutable::~UnlinkedFunctionExecutable()
+{
+    if (m_isCached)
+        m_decoder.~RefPtr();
+}
+
 void UnlinkedFunctionExecutable::destroy(JSCell* cell)
 {
     static_cast<UnlinkedFunctionExecutable*>(cell)->~UnlinkedFunctionExecutable();
@@ -130,8 +140,10 @@ void UnlinkedFunctionExecutable::visitChildren(JSCell* cell, SlotVisitor& visito
     UnlinkedFunctionExecutable* thisObject = jsCast<UnlinkedFunctionExecutable*>(cell);
     ASSERT_GC_OBJECT_INHERITS(thisObject, info());
     Base::visitChildren(thisObject, visitor);
-    visitor.append(thisObject->m_unlinkedCodeBlockForCall);
-    visitor.append(thisObject->m_unlinkedCodeBlockForConstruct);
+    if (!thisObject->m_isCached) {
+        visitor.append(thisObject->m_unlinkedCodeBlockForCall);
+        visitor.append(thisObject->m_unlinkedCodeBlockForConstruct);
+    }
 }
 
 SourceCode UnlinkedFunctionExecutable::linkedSourceCode(const SourceCode& passedParentSource) const
@@ -213,6 +225,8 @@ UnlinkedFunctionCodeBlock* UnlinkedFunctionExecutable::unlinkedCodeBlockFor(
     VM& vm, const SourceCode& source, CodeSpecializationKind specializationKind, 
     DebuggerMode debuggerMode, ParserError& error, SourceParseMode parseMode)
 {
+    if (m_isCached)
+        decodeCachedCodeBlocks();
     switch (specializationKind) {
     case CodeForCall:
         if (UnlinkedFunctionCodeBlock* codeBlock = m_unlinkedCodeBlockForCall.get())
@@ -244,6 +258,31 @@ UnlinkedFunctionCodeBlock* UnlinkedFunctionExecutable::unlinkedCodeBlockFor(
     return result;
 }
 
+void UnlinkedFunctionExecutable::decodeCachedCodeBlocks()
+{
+    ASSERT(m_isCached);
+    ASSERT(m_decoder);
+    ASSERT(m_cachedCodeBlockForCallOffset || m_cachedCodeBlockForConstructOffset);
+
+    RefPtr<Decoder> decoder = WTFMove(m_decoder);
+    int32_t cachedCodeBlockForCallOffset = m_cachedCodeBlockForCallOffset;
+    int32_t cachedCodeBlockForConstructOffset = m_cachedCodeBlockForConstructOffset;
+
+    DeferGC deferGC(decoder->vm().heap);
+
+    // No need to clear m_unlinkedCodeBlockForCall here, since we moved the decoder out of the same slot
+    if (cachedCodeBlockForCallOffset)
+        decodeFunctionCodeBlock(*decoder, cachedCodeBlockForCallOffset, m_unlinkedCodeBlockForCall, this);
+    if (cachedCodeBlockForConstructOffset)
+        decodeFunctionCodeBlock(*decoder, cachedCodeBlockForConstructOffset, m_unlinkedCodeBlockForConstruct, this);
+    else
+        m_unlinkedCodeBlockForConstruct.clear();
+
+    WTF::storeStoreFence();
+    m_isCached = false;
+    decoder->vm().heap.writeBarrier(this);
+}
+
 UnlinkedFunctionExecutable::RareData& UnlinkedFunctionExecutable::ensureRareDataSlow()
 {
     ASSERT(!m_rareData);
index b0c305e..39a73f2 100644 (file)
@@ -75,6 +75,8 @@ public:
         return instance;
     }
 
+    ~UnlinkedFunctionExecutable();
+
     const Identifier& name() const { return m_name; }
     const Identifier& ecmaName() const { return m_ecmaName; }
     void setEcmaName(const Identifier& name) { m_ecmaName = name; }
@@ -193,6 +195,8 @@ private:
     UnlinkedFunctionExecutable(VM*, Structure*, const SourceCode&, FunctionMetadataNode*, UnlinkedFunctionKind, ConstructAbility, JSParserScriptMode, CompactVariableMap::Handle,  JSC::DerivedContextType, bool isBuiltinDefaultClassConstructor);
     UnlinkedFunctionExecutable(Decoder&, CompactVariableMap::Handle, const CachedFunctionExecutable&);
 
+    void decodeCachedCodeBlocks();
+
     unsigned m_firstLineOffset;
     unsigned m_lineCount;
     unsigned m_unlinkedFunctionNameStart;
@@ -216,9 +220,20 @@ private:
     unsigned m_scriptMode: 1; // JSParserScriptMode
     unsigned m_superBinding : 1;
     unsigned m_derivedContextType: 2;
+    bool m_isCached : 1;
 
-    WriteBarrier<UnlinkedFunctionCodeBlock> m_unlinkedCodeBlockForCall;
-    WriteBarrier<UnlinkedFunctionCodeBlock> m_unlinkedCodeBlockForConstruct;
+    union {
+        WriteBarrier<UnlinkedFunctionCodeBlock> m_unlinkedCodeBlockForCall;
+        RefPtr<Decoder> m_decoder;
+    };
+
+    union {
+        WriteBarrier<UnlinkedFunctionCodeBlock> m_unlinkedCodeBlockForConstruct;
+        struct {
+            int32_t m_cachedCodeBlockForCallOffset;
+            int32_t m_cachedCodeBlockForConstructOffset;
+        };
+    };
 
     Identifier m_name;
     Identifier m_ecmaName;
index e874ab7..0344869 100644 (file)
@@ -211,78 +211,73 @@ private:
     HashMap<const void*, ptrdiff_t> m_ptrToOffsetMap;
 };
 
-class Decoder {
-    WTF_MAKE_NONCOPYABLE(Decoder);
-    WTF_FORBID_HEAP_ALLOCATION;
-
-public:
-    Decoder(VM& vm, const void* baseAddress, size_t size)
-        : m_vm(vm)
-        , m_baseAddress(static_cast<const uint8_t*>(baseAddress))
+Decoder::Decoder(VM& vm, const void* baseAddress, size_t size)
+    : m_vm(vm)
+    , m_baseAddress(reinterpret_cast<const uint8_t*>(baseAddress))
 #ifndef NDEBUG
-        , m_size(size)
+    , m_size(size)
 #endif
-    {
-        UNUSED_PARAM(size);
-    }
+{
+    UNUSED_PARAM(size);
+}
 
-    ~Decoder()
-    {
-        for (auto& finalizer : m_finalizers)
-            finalizer();
-    }
+Decoder::~Decoder()
+{
+    for (auto& finalizer : m_finalizers)
+        finalizer();
+}
 
-    VM& vm() { return m_vm; }
+Ref<Decoder> Decoder::create(VM& vm, const void* baseAddress, size_t size)
+{
+    return adoptRef(*new Decoder(vm, baseAddress, size));
+}
 
-    ptrdiff_t offsetOf(const void* ptr)
-    {
-        const uint8_t* addr = static_cast<const uint8_t*>(ptr);
-        ASSERT(addr >= m_baseAddress && addr < m_baseAddress + m_size);
-        return addr - m_baseAddress;
-    }
+ptrdiff_t Decoder::offsetOf(const void* ptr)
+{
+    const uint8_t* addr = static_cast<const uint8_t*>(ptr);
+    ASSERT(addr >= m_baseAddress && addr < m_baseAddress + m_size);
+    return addr - m_baseAddress;
+}
 
-    void cacheOffset(ptrdiff_t offset, void* ptr)
-    {
-        m_offsetToPtrMap.add(offset, ptr);
-    }
+void Decoder::cacheOffset(ptrdiff_t offset, void* ptr)
+{
+    m_offsetToPtrMap.add(offset, ptr);
+}
 
-    Optional<void*> cachedPtrForOffset(ptrdiff_t offset)
-    {
-        auto it = m_offsetToPtrMap.find(offset);
-        if (it == m_offsetToPtrMap.end())
-            return WTF::nullopt;
-        return { it->value };
-    }
+WTF::Optional<void*> Decoder::cachedPtrForOffset(ptrdiff_t offset)
+{
+    auto it = m_offsetToPtrMap.find(offset);
+    if (it == m_offsetToPtrMap.end())
+        return WTF::nullopt;
+    return { it->value };
+}
 
-    template<typename Functor>
-    void addFinalizer(const Functor& finalizer)
-    {
-        m_finalizers.append(finalizer);
-    }
+const void* Decoder::ptrForOffsetFromBase(ptrdiff_t offset)
+{
+#ifndef NDEBUG
+    ASSERT(offset > 0 && static_cast<size_t>(offset) < m_size);
+#endif
+    return m_baseAddress + offset;
+}
 
-    CompactVariableMap::Handle handleForEnvironment(CompactVariableEnvironment* environment) const
-    {
-        auto it = m_environmentToHandleMap.find(environment);
-        ASSERT(it != m_environmentToHandleMap.end());
-        return it->value;
-    }
+CompactVariableMap::Handle Decoder::handleForEnvironment(CompactVariableEnvironment* environment) const
+{
+    auto it = m_environmentToHandleMap.find(environment);
+    ASSERT(it != m_environmentToHandleMap.end());
+    return it->value;
+}
 
-    void setHandleForEnvironment(CompactVariableEnvironment* environment, const CompactVariableMap::Handle& handle)
-    {
-        auto addResult = m_environmentToHandleMap.add(environment, handle);
-        ASSERT_UNUSED(addResult, addResult.isNewEntry);
-    }
+void Decoder::setHandleForEnvironment(CompactVariableEnvironment* environment, const CompactVariableMap::Handle& handle)
+{
+    auto addResult = m_environmentToHandleMap.add(environment, handle);
+    ASSERT_UNUSED(addResult, addResult.isNewEntry);
+}
 
-private:
-    VM& m_vm;
-    const uint8_t* m_baseAddress;
-#ifndef NDEBUG
-    size_t m_size;
-#endif
-    HashMap<ptrdiff_t, void*> m_offsetToPtrMap;
-    Vector<std::function<void()>> m_finalizers;
-    HashMap<CompactVariableEnvironment*, CompactVariableMap::Handle> m_environmentToHandleMap;
-};
+template<typename Functor>
+void Decoder::addFinalizer(const Functor& fn)
+{
+    m_finalizers.append(fn);
+}
 
 template<typename T>
 static std::enable_if_t<std::is_same<T, SourceType<T>>::value> encode(Encoder&, T& dst, const SourceType<T>& src)
@@ -345,6 +340,12 @@ class VariableLengthObject : public CachedObject<Source> {
     template<typename, typename>
     friend struct CachedPtr;
 
+public:
+    bool isEmpty() const
+    {
+        return m_offset == s_invalidOffset;
+    }
+
 protected:
     const uint8_t* buffer() const
     {
@@ -373,11 +374,6 @@ protected:
         return new (result) T[size];
     }
 
-    bool isEmpty() const
-    {
-        return m_offset == s_invalidOffset;
-    }
-
 private:
     constexpr static ptrdiff_t s_invalidOffset = std::numeric_limits<ptrdiff_t>::max();
 
@@ -484,6 +480,8 @@ private:
 template<typename T, typename Source = SourceType<T>>
 class CachedWriteBarrier : public CachedObject<WriteBarrier<Source>> {
 public:
+    bool isEmpty() const { return m_ptr.isEmpty(); }
+
     void encode(Encoder& encoder, const WriteBarrier<Source> src)
     {
         m_ptr.encode(encoder, src.get());
@@ -1574,6 +1572,9 @@ public:
 
     UnlinkedFunctionExecutable::RareData* rareData(Decoder& decoder) const { return m_rareData.decodeAsPtr(decoder); }
 
+    const CachedWriteBarrier<CachedFunctionCodeBlock, UnlinkedFunctionCodeBlock>& unlinkedCodeBlockForCall() const { return m_unlinkedCodeBlockForCall; }
+    const CachedWriteBarrier<CachedFunctionCodeBlock, UnlinkedFunctionCodeBlock>& unlinkedCodeBlockForConstruct() const { return m_unlinkedCodeBlockForConstruct; }
+
 private:
     unsigned m_firstLineOffset;
     unsigned m_lineCount;
@@ -1968,9 +1969,6 @@ ALWAYS_INLINE UnlinkedFunctionExecutable* CachedFunctionExecutable::decode(Decod
     UnlinkedFunctionExecutable* executable = new (NotNull, allocateCell<UnlinkedFunctionExecutable>(decoder.vm().heap)) UnlinkedFunctionExecutable(decoder, WTFMove(env), *this);
     executable->finishCreation(decoder.vm());
 
-    m_unlinkedCodeBlockForCall.decode(decoder, executable->m_unlinkedCodeBlockForCall, executable);
-    m_unlinkedCodeBlockForConstruct.decode(decoder, executable->m_unlinkedCodeBlockForConstruct, executable);
-
     return executable;
 }
 
@@ -1999,6 +1997,9 @@ ALWAYS_INLINE UnlinkedFunctionExecutable::UnlinkedFunctionExecutable(Decoder& de
     , m_scriptMode(cachedExecutable.scriptMode())
     , m_superBinding(cachedExecutable.superBinding())
     , m_derivedContextType(cachedExecutable.derivedContextType())
+    , m_isCached(false)
+    , m_unlinkedCodeBlockForCall()
+    , m_unlinkedCodeBlockForConstruct()
 
     , m_name(cachedExecutable.name(decoder))
     , m_ecmaName(cachedExecutable.ecmaName(decoder))
@@ -2008,6 +2009,19 @@ ALWAYS_INLINE UnlinkedFunctionExecutable::UnlinkedFunctionExecutable(Decoder& de
 
     , m_rareData(cachedExecutable.rareData(decoder))
 {
+
+    if (!cachedExecutable.unlinkedCodeBlockForCall().isEmpty() || !cachedExecutable.unlinkedCodeBlockForConstruct().isEmpty()) {
+        m_isCached = true;
+        m_decoder = &decoder;
+        if (!cachedExecutable.unlinkedCodeBlockForCall().isEmpty())
+            m_cachedCodeBlockForCallOffset = decoder.offsetOf(&cachedExecutable.unlinkedCodeBlockForCall());
+        else
+            m_cachedCodeBlockForCallOffset = 0;
+        if (!cachedExecutable.unlinkedCodeBlockForConstruct().isEmpty())
+            m_cachedCodeBlockForConstructOffset = decoder.offsetOf(&cachedExecutable.unlinkedCodeBlockForConstruct());
+        else
+            m_cachedCodeBlockForConstructOffset = 0;
+    }
 }
 
 template<typename CodeBlockType>
@@ -2218,11 +2232,11 @@ std::pair<MallocPtr<uint8_t>, size_t> encodeCodeBlock(VM& vm, const SourceCodeKe
 UnlinkedCodeBlock* decodeCodeBlockImpl(VM& vm, const SourceCodeKey& key, const void* buffer, size_t size)
 {
     const auto* cachedEntry = bitwise_cast<const GenericCacheEntry*>(buffer);
-    Decoder decoder(vm, buffer, size);
+    Ref<Decoder> decoder = Decoder::create(vm, buffer, size);
     std::pair<SourceCodeKey, UnlinkedCodeBlock*> entry;
     {
         DeferGC deferGC(vm.heap);
-        if (!cachedEntry->decode(decoder, entry))
+        if (!cachedEntry->decode(decoder.get(), entry))
             return nullptr;
     }
 
@@ -2238,8 +2252,15 @@ bool isCachedBytecodeStillValid(VM& vm, const CachedBytecode& cachedBytecode, co
     if (!size)
         return false;
     const auto* cachedEntry = bitwise_cast<const GenericCacheEntry*>(buffer);
-    Decoder decoder(vm, buffer, size);
-    return cachedEntry->isStillValid(decoder, key, tagFromSourceCodeType(type));
+    Ref<Decoder> decoder = Decoder::create(vm, buffer, size);
+    return cachedEntry->isStillValid(decoder.get(), key, tagFromSourceCodeType(type));
+}
+
+void decodeFunctionCodeBlock(Decoder& decoder, int32_t cachedFunctionCodeBlockOffset, WriteBarrier<UnlinkedFunctionCodeBlock>& codeBlock, const JSCell* owner)
+{
+    ASSERT(decoder.vm().heap.isDeferred());
+    auto* cachedCodeBlock = static_cast<const CachedWriteBarrier<CachedFunctionCodeBlock, UnlinkedFunctionCodeBlock>*>(decoder.ptrForOffsetFromBase(cachedFunctionCodeBlockOffset));
+    cachedCodeBlock->decode(decoder, codeBlock, owner);
 }
 
 } // namespace JSC
index 7e92d55..9db3abd 100644 (file)
@@ -26,6 +26,8 @@
 #pragma once
 
 #include "JSCast.h"
+#include "VariableEnvironment.h"
+#include <wtf/HashMap.h>
 #include <wtf/MallocPtr.h>
 
 namespace JSC {
@@ -33,12 +35,48 @@ namespace JSC {
 class CachedBytecode;
 class SourceCodeKey;
 class UnlinkedCodeBlock;
+class UnlinkedFunctionCodeBlock;
+
+class Decoder : public RefCounted<Decoder> {
+    WTF_MAKE_NONCOPYABLE(Decoder);
+
+public:
+    static Ref<Decoder> create(VM&, const void*, size_t);
+
+    ~Decoder();
+
+    VM& vm() { return m_vm; }
+
+    ptrdiff_t offsetOf(const void*);
+    void cacheOffset(ptrdiff_t, void*);
+    WTF::Optional<void*> cachedPtrForOffset(ptrdiff_t);
+    const void* ptrForOffsetFromBase(ptrdiff_t);
+    CompactVariableMap::Handle handleForEnvironment(CompactVariableEnvironment*) const;
+    void setHandleForEnvironment(CompactVariableEnvironment*, const CompactVariableMap::Handle&);
+
+    template<typename Functor>
+    void addFinalizer(const Functor&);
+
+private:
+    Decoder(VM&, const void*, size_t);
+
+    VM& m_vm;
+    const uint8_t* m_baseAddress;
+#ifndef NDEBUG
+    size_t m_size;
+#endif
+    HashMap<ptrdiff_t, void*> m_offsetToPtrMap;
+    Vector<std::function<void()>> m_finalizers;
+    HashMap<CompactVariableEnvironment*, CompactVariableMap::Handle> m_environmentToHandleMap;
+};
 
 enum class SourceCodeType;
 
 std::pair<MallocPtr<uint8_t>, size_t> encodeCodeBlock(VM&, const SourceCodeKey&, const UnlinkedCodeBlock*);
+
 UnlinkedCodeBlock* decodeCodeBlockImpl(VM&, const SourceCodeKey&, const void*, size_t);
 
+void decodeFunctionCodeBlock(Decoder&, int32_t cachedFunctionCodeBlockOffset, WriteBarrier<UnlinkedFunctionCodeBlock>&, const JSCell*);
 
 template<typename UnlinkedCodeBlockType>
 UnlinkedCodeBlockType* decodeCodeBlock(VM& vm, const SourceCodeKey& key, const void* buffer, size_t size)