Changeset 254926 in webkit


Ignore:
Timestamp:
Jan 22, 2020 9:06:32 AM (4 years ago)
Author:
sbarati@apple.com
Message:

Throw away baseline code if there is an optimized replacement
https://bugs.webkit.org/show_bug.cgi?id=202503
<rdar://problem/58552041>

Reviewed by Yusuke Suzuki.

This patch's goal is to help us save JIT executable memory by throwing
away baseline code when it has an optimized replacement. To make it
easy to reason about, we do this when finalizing a GC, when the CodeBlock
is not on the stack, and when no OSR exits are linked to jump to the baseline
code. Also, as a measure to combat a performance regression, we only throw
away code on the second GC cycle in which it is eligible for this.
When we downgrade Baseline to LLInt, we also throw away all JIT data
and unlink all incoming calls.

  • bytecode/CodeBlock.cpp:

(JSC::CodeBlock::CodeBlock):
(JSC::CodeBlock::finishCreation):
(JSC::CodeBlock::finalizeUnconditionally):
(JSC::CodeBlock::resetJITData):
(JSC::CodeBlock::optimizedReplacement):
(JSC::CodeBlock::hasOptimizedReplacement):
(JSC::CodeBlock::tallyFrequentExitSites):

  • bytecode/CodeBlock.h:

(JSC::CodeBlock::setJITCode):

  • dfg/DFGDriver.cpp:

(JSC::DFG::compileImpl):

  • dfg/DFGOSRExitCompilerCommon.cpp:

(JSC::DFG::callerReturnPC):
(JSC::DFG::adjustAndJumpToTarget):

  • heap/CodeBlockSet.cpp:

(JSC::CodeBlockSet::isCurrentlyExecuting):

  • heap/CodeBlockSet.h:
  • heap/Heap.cpp:

(JSC::Heap::finalizeUnconditionalFinalizers):
(JSC::Heap::runEndPhase):

Location:
trunk/Source/JavaScriptCore
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/ChangeLog

    r254899 r254926  
     12020-01-22  Saam Barati  <sbarati@apple.com>
     2
     3        Throw away baseline code if there is an optimized replacement
     4        https://bugs.webkit.org/show_bug.cgi?id=202503
     5        <rdar://problem/58552041>
     6
     7        Reviewed by Yusuke Suzuki.
     8
     9        This patch's goal is to help us save JIT executable memory by throwing
     10        away baseline code when it has an optimized replacement. To make it
     11        easy to reason about, we do this when finalizing a GC, when the CodeBlock
     12        is not on the stack, and when no OSR exits are linked to jump to the baseline
     13        code. Also, as a measure to combat a performance regression, we only throw
     14        away code on the second GC cycle in which it is eligible for this.
     15        When we downgrade Baseline to LLInt, we also throw away all JIT data
     16        and unlink all incoming calls.
     17
     18        * bytecode/CodeBlock.cpp:
     19        (JSC::CodeBlock::CodeBlock):
     20        (JSC::CodeBlock::finishCreation):
     21        (JSC::CodeBlock::finalizeUnconditionally):
     22        (JSC::CodeBlock::resetJITData):
     23        (JSC::CodeBlock::optimizedReplacement):
     24        (JSC::CodeBlock::hasOptimizedReplacement):
     25        (JSC::CodeBlock::tallyFrequentExitSites):
     26        * bytecode/CodeBlock.h:
     27        (JSC::CodeBlock::setJITCode):
     28        * dfg/DFGDriver.cpp:
     29        (JSC::DFG::compileImpl):
     30        * dfg/DFGOSRExitCompilerCommon.cpp:
     31        (JSC::DFG::callerReturnPC):
     32        (JSC::DFG::adjustAndJumpToTarget):
     33        * heap/CodeBlockSet.cpp:
     34        (JSC::CodeBlockSet::isCurrentlyExecuting):
     35        * heap/CodeBlockSet.h:
     36        * heap/Heap.cpp:
     37        (JSC::Heap::finalizeUnconditionalFinalizers):
     38        (JSC::Heap::runEndPhase):
     39
    1402020-01-21  Ross Kirsling  <ross.kirsling@sony.com>
    241
  • trunk/Source/JavaScriptCore/bytecode/CodeBlock.cpp

    r254735 r254926  
    296296    , m_didFailFTLCompilation(false)
    297297    , m_hasBeenCompiledWithFTL(false)
     298    , m_hasLinkedOSRExit(false)
     299    , m_isEligibleForLLIntDowngrade(false)
    298300    , m_numCalleeLocals(other.m_numCalleeLocals)
    299301    , m_numVars(other.m_numVars)
     
    355357    , m_didFailFTLCompilation(false)
    356358    , m_hasBeenCompiledWithFTL(false)
     359    , m_hasLinkedOSRExit(false)
     360    , m_isEligibleForLLIntDowngrade(false)
    357361    , m_numCalleeLocals(unlinkedCodeBlock->numCalleeLocals())
    358362    , m_numVars(unlinkedCodeBlock->numVars())
     
    443447                HandlerInfo& handler = m_rareData->m_exceptionHandlers[i];
    444448#if ENABLE(JIT)
    445                 auto instruction = instructions().at(unlinkedHandler.target);
    446                 MacroAssemblerCodePtr<BytecodePtrTag> codePtr;
    447                 if (instruction->isWide32())
    448                     codePtr = LLInt::getWide32CodePtr<BytecodePtrTag>(op_catch);
    449                 else if (instruction->isWide16())
    450                     codePtr = LLInt::getWide16CodePtr<BytecodePtrTag>(op_catch);
    451                 else
    452                     codePtr = LLInt::getCodePtr<BytecodePtrTag>(op_catch);
     449                auto& instruction = *instructions().at(unlinkedHandler.target).ptr();
     450                MacroAssemblerCodePtr<BytecodePtrTag> codePtr = LLInt::getCodePtr<BytecodePtrTag>(instruction);
    453451                handler.initialize(unlinkedHandler, CodeLocationLabel<ExceptionHandlerPtrTag>(codePtr.retagged<ExceptionHandlerPtrTag>()));
    454452#else
     
    13821380
    13831381    updateAllPredictions();
     1382
     1383#if ENABLE(JIT)
     1384    bool isEligibleForLLIntDowngrade = m_isEligibleForLLIntDowngrade;
     1385    m_isEligibleForLLIntDowngrade = false;
     1386    // If BaselineJIT code is not executing, and an optimized replacement exists, we attempt
     1387    // to discard baseline JIT code and reinstall LLInt code to save JIT memory.
     1388    if (Options::useLLInt() && !m_hasLinkedOSRExit && jitType() == JITType::BaselineJIT && !m_vm->heap.codeBlockSet().isCurrentlyExecuting(this)) {
     1389        if (CodeBlock* optimizedCodeBlock = optimizedReplacement()) {
     1390            if (!optimizedCodeBlock->m_osrExitCounter) {
     1391                if (isEligibleForLLIntDowngrade) {
     1392                    m_jitCode = nullptr;
     1393                    LLInt::setEntrypoint(this);
     1394                    RELEASE_ASSERT(jitType() == JITType::InterpreterThunk);
     1395
     1396                    for (size_t i = 0; i < m_unlinkedCode->numberOfExceptionHandlers(); i++) {
     1397                        const UnlinkedHandlerInfo& unlinkedHandler = m_unlinkedCode->exceptionHandler(i);
     1398                        HandlerInfo& handler = m_rareData->m_exceptionHandlers[i];
     1399                        auto& instruction = *instructions().at(unlinkedHandler.target).ptr();
     1400                        MacroAssemblerCodePtr<BytecodePtrTag> codePtr = LLInt::getCodePtr<BytecodePtrTag>(instruction);
     1401                        handler.initialize(unlinkedHandler, CodeLocationLabel<ExceptionHandlerPtrTag>(codePtr.retagged<ExceptionHandlerPtrTag>()));
     1402                    }
     1403
     1404                    unlinkIncomingCalls();
     1405
     1406                    // It's safe to clear these out here because in finalizeUnconditionally all compiler threads
     1407                    // are safepointed, meaning they're running either before or after bytecode parser, and bytecode
     1408                    // parser is the only data structure pointing into the various *infos.
     1409                    resetJITData();
     1410                } else
     1411                    m_isEligibleForLLIntDowngrade = true;
     1412            }
     1413        }
     1414    }
     1415
     1416#endif
    13841417   
    13851418    if (JITCode::couldBeInterpreted(jitType()))
     
    15921625        // link infos, or by val infos if we don't have JIT code. Attempts to query these data
    15931626        // structures using the concurrent API (getICStatusMap and friends) will return nothing if we
    1594         // don't have JIT code.
    1595         jitData->m_stubInfos.clear();
    1596         jitData->m_callLinkInfos.clear();
    1597         jitData->m_byValInfos.clear();
     1627        // don't have JIT code. So it's safe to call this if we fail a baseline JIT compile.
     1628        //
     1629        // We also call this from finalizeUnconditionally when we degrade from baseline JIT to LLInt
     1630        // code. This is safe to do since all compiler threads are safepointed in finalizeUnconditionally,
     1631        // which means we've made it past bytecode parsing. Only the bytecode parser will hold onto
     1632        // references to these various *infos via its use of ICStatusMap. Also, OSR exit might point to
     1633        // these *infos, but when we have an OSR exit linked to this CodeBlock, we won't downgrade
     1634        // to LLInt.
     1635
     1636        for (StructureStubInfo* stubInfo : jitData->m_stubInfos) {
     1637            stubInfo->aboutToDie();
     1638            stubInfo->deref();
     1639        }
     1640
    15981641        // We can clear this because the DFG's queries to these data structures are guarded by whether
    15991642        // there is JIT code.
    1600         jitData->m_rareCaseProfiles.clear();
     1643
     1644        m_jitData = nullptr;
    16011645    }
    16021646}
     
    17301774
    17311775#if ENABLE(JIT)
     1776CodeBlock* CodeBlock::optimizedReplacement(JITType typeToReplace)
     1777{
     1778    CodeBlock* replacement = this->replacement();
     1779    if (!replacement)
     1780        return nullptr;
     1781    if (JITCode::isHigherTier(replacement->jitType(), typeToReplace))
     1782        return replacement;
     1783    return nullptr;
     1784}
     1785
     1786CodeBlock* CodeBlock::optimizedReplacement()
     1787{
     1788    return optimizedReplacement(jitType());
     1789}
     1790
    17321791bool CodeBlock::hasOptimizedReplacement(JITType typeToReplace)
    17331792{
    1734     CodeBlock* replacement = this->replacement();
    1735     return replacement && JITCode::isHigherTier(replacement->jitType(), typeToReplace);
     1793    return !!optimizedReplacement(typeToReplace);
    17361794}
    17371795
     
    27772835{
    27782836    ASSERT(JITCode::isOptimizingJIT(jitType()));
    2779     ASSERT(alternative()->jitType() == JITType::BaselineJIT);
     2837    ASSERT(JITCode::isBaselineCode(alternative()->jitType()));
    27802838   
    27812839    CodeBlock* profiledBlock = alternative();
  • trunk/Source/JavaScriptCore/bytecode/CodeBlock.h

    r254735 r254926  
    259259    Optional<BytecodeIndex> bytecodeIndexFromCallSiteIndex(CallSiteIndex);
    260260
     261    // Because we might throw out baseline JIT code and all its baseline JIT data (m_jitData),
     262    // you need to be careful about the lifetime of when you use the return value of this function.
     263    // The return value may have raw pointers into this data structure that gets thrown away.
     264    // Specifically, you need to ensure that no GC can be finalized (typically that means no
     265    // allocations) between calling this and the last use of it.
    261266    void getICStatusMap(const ConcurrentJSLocker&, ICStatusMap& result);
    262267    void getICStatusMap(ICStatusMap& result);
     
    413418    void setJITCode(Ref<JITCode>&& code)
    414419    {
    415         ASSERT(heap()->isDeferred());
    416420        if (!code->isShared())
    417421            heap()->reportExtraMemoryAllocated(code->size());
     
    445449    DFG::CapabilityLevel capabilityLevelState() { return static_cast<DFG::CapabilityLevel>(m_capabilityLevelState); }
    446450
     451    CodeBlock* optimizedReplacement(JITType typeToReplace);
     452    CodeBlock* optimizedReplacement(); // the typeToReplace is my JITType
    447453    bool hasOptimizedReplacement(JITType typeToReplace);
    448454    bool hasOptimizedReplacement(); // the typeToReplace is my JITType
     
    864870    bool m_hasBeenCompiledWithFTL : 1;
    865871
     872    bool m_hasLinkedOSRExit : 1;
     873    bool m_isEligibleForLLIntDowngrade : 1;
     874
    866875    // Internal methods for use by validation code. It would be private if it wasn't
    867876    // for the fact that we use it from anonymous namespaces.
  • trunk/Source/JavaScriptCore/dfg/DFGDriver.cpp

    r254735 r254926  
    8282    ASSERT(codeBlock);
    8383    ASSERT(codeBlock->alternative());
    84     ASSERT(codeBlock->alternative()->jitType() == JITType::BaselineJIT);
     84    ASSERT(JITCode::isBaselineCode(codeBlock->alternative()->jitType()));
    8585    ASSERT(!profiledDFGCodeBlock || profiledDFGCodeBlock->jitType() == JITType::DFGJIT);
    8686   
  • trunk/Source/JavaScriptCore/dfg/DFGOSRExitCompilerCommon.cpp

    r254735 r254926  
    194194
    195195    } else {
     196        baselineCodeBlockForCaller->m_hasLinkedOSRExit = true;
     197
    196198        switch (trueCallerCallKind) {
    197199        case InlineCallFrame::Call:
     
    414416        jumpTarget = destination.retagged<OSRExitPtrTag>().executableAddress();
    415417    } else {
     418        codeBlockForExit->m_hasLinkedOSRExit = true;
     419
    416420        BytecodeIndex exitIndex = exit.m_codeOrigin.bytecodeIndex();
    417421        MacroAssemblerCodePtr<JSEntryPtrTag> destination;
  • trunk/Source/JavaScriptCore/heap/CodeBlockSet.cpp

    r254558 r254926  
    5656}
    5757
     58bool CodeBlockSet::isCurrentlyExecuting(CodeBlock* codeBlock)
     59{
     60    return m_currentlyExecuting.contains(codeBlock);
     61}
     62
    5863void CodeBlockSet::dump(PrintStream& out) const
    5964{
  • trunk/Source/JavaScriptCore/heap/CodeBlockSet.h

    r254558 r254926  
    5757    Lock& getLock() { return m_lock; }
    5858
     59    // This is expected to run only when we're not adding to the set for now. If
     60    // this needs to run concurrently in the future, we'll need to lock around this.
     61    bool isCurrentlyExecuting(CodeBlock*);
     62
    5963    // Visits each CodeBlock in the heap until the visitor function returns true
    6064    // to indicate that it is done iterating, or until every CodeBlock has been
  • trunk/Source/JavaScriptCore/heap/Heap.cpp

    r254735 r254926  
    606606    finalizeMarkedUnconditionalFinalizers<FunctionExecutable>(vm().functionExecutableSpace.space);
    607607    finalizeMarkedUnconditionalFinalizers<SymbolTable>(vm().symbolTableSpace);
     608    finalizeMarkedUnconditionalFinalizers<ExecutableToCodeBlockEdge>(vm().executableToCodeBlockEdgesWithFinalizers); // We run this before CodeBlock's unconditional finalizer since CodeBlock looks at the owner executable's installed CodeBlock in its finalizeUnconditionally.
    608609    vm().forEachCodeBlockSpace(
    609610        [&] (auto& space) {
    610611            this->finalizeMarkedUnconditionalFinalizers<CodeBlock>(space.set);
    611612        });
    612     finalizeMarkedUnconditionalFinalizers<ExecutableToCodeBlockEdge>(vm().executableToCodeBlockEdgesWithFinalizers);
    613613    finalizeMarkedUnconditionalFinalizers<StructureRareData>(vm().structureRareDataSpace);
    614614    finalizeMarkedUnconditionalFinalizers<UnlinkedFunctionExecutable>(vm().unlinkedFunctionExecutableSpace.set);
     
    15101510    sweepArrayBuffers();
    15111511    snapshotUnswept();
    1512     finalizeUnconditionalFinalizers();
     1512    finalizeUnconditionalFinalizers(); // We rely on these unconditional finalizers running before clearCurrentlyExecuting since CodeBlock's finalizer relies on querying currently executing.
    15131513    removeDeadCompilerWorklistEntries();
    15141514    notifyIncrementalSweeper();
Note: See TracChangeset for help on using the changeset viewer.