Title: [246272] trunk
Revision
246272
Author
[email protected]
Date
2019-06-10 12:49:58 -0700 (Mon, 10 Jun 2019)

Log Message

[JSC] UnlinkedCodeBlock should be eventually jettisoned in VM mini mode
https://bugs.webkit.org/show_bug.cgi?id=198023

Reviewed by Saam Barati.

JSTests:

* stress/reparsing-unlinked-codeblock.js: Added.
(shouldBe):
(hello):

Source/_javascript_Core:

While CodeBlock is periodically jettisoned, UnlinkedCodeBlock and UnlinkedFunctionExecutable can be retained almost forever in certain type of applications.
When we execute a program, which has UnlinkedProgramCodeBlock retained in CodeCache. And UnlinkedProgramCodeBlock holds array of UnlinkedFunctionExecutable.
And UnlinkedFunctionExecutables hold UnlinkedFunctionCodeBlocks once it is generated. So eventually, this tree gets larger and larger until we purge
UnlinkedProgramCodeBlock from CodeCache. This is OK in the browser case. We navigate to various other pages, and UnlinkedProgramCodeBlocks should eventually
be pruned from CodeCache with the new ones. So this tree won't be retained forever. But the behavior is different in the other applications that do not have
navigations. If they only have one program which holds all, we basically retain this tree during executing this application. The same thing can happen in
web applications which does not have navigation and keeps alive for a long time. Once we hit CodeCache limit by periodically executing a new script, we will
hit the uppermost of memory footprint. But until that, we increase our memory footprint.

However, destroying these UnlinkedCodeBlocks and UnlinkedFunctionExecutables causes a tricky problem. In the browser environment, navigation can happen at any
time. So even if the given UnlinkedCodeBlock seems unused in the current page, it can be used when navigating to a new page which is under the same domain.
One example is initializing function in a script. It is only executed once per page. So once it is executed, it seems that this UnlinkedCodeBlock is unused.
But this will be used when we navigate to a new page. Pruning code blocks based on usage could cause performance regression.

But if our VM is mini VM mode, the story is different. In mini VM mode, we focus on memory footprint rather than performance e.g. daemons. The daemon never
reuse these CodeCache since we do not have the navigation.

This patch logically makes UnlinkedFunctionExecutable -> UnlinkedCodeBlock reference weak when VM is mini mode. If UnlinkedCodeBlock is used in previous GC
cycle, we retain it. But if it is not used, and if UnlinkedFunctionExecutable is only the cell keeping UnlinkedCodeBlock alive, we destroy it. It is a
heuristic. In a super pathological case, it could increase memory footprint. Consider the following example.

    UnlinkedFunctionExecutable(A1) -> UnlinkedCodeBlock(B1) -> UnlinkedFunctionExecutable(C1) -> UnlinkedCodeBlock(D1)
                                                                                                     ^
                                                                                                 CodeBlock(E1)

We could delete A1, B1, and C1 while keeping D1. But if we eventually re-execute the same code corresponding to A1, B1, C1, they will be newly created, and
we will create duplicate UnlinkedCodeBlock and instructions stream for D1.

                                                                                                 UnlinkedCodeBlock(D1)
                                                                                                     ^
                                                                                                 CodeBlock(E1)

    UnlinkedFunctionExecutable(A2) -> UnlinkedCodeBlock(B2) -> UnlinkedFunctionExecutable(C2) -> UnlinkedCodeBlock(D2)

But this does not happen in practice and even it happens, we eventually discard D1 and D2 since CodeBlock E1 will be jettisoned anyway. So in practice, we do
not see memory footprint increase. We tested it in Gmail and the target application, but both said memory footprint reduction (30 MB / 400 MB and 1 MB /6 MB).
While this affects on performance much on tests which has navigation (1-3 % regression in Speedometer2, note that JetStream2 does not show regression in x64,
while it is not enabling mini mode), we do not apply this to non mini mode VM until we come up with a good strategy to fasten performance of re-generation.
Personally I think flushing destroyed UnlinkedCodeBlock to the disk sounds promising.

If UnlinkedCodeBlock is generated from bytecode cache, we do not make UnlinkedFunctionExecutable -> UnlinkedCodeBlock link weak because the decoder of the bytecode
cache assumes that generated JSCells won't be destroyed while the parent cells of that cell are live. This is true in the current implementation, and this assumption
will be broken with this patch. So, for now, we do not make this link weak. Currently, our target application does not use bytecode cache so it is OK.

This patch also introduce simple heuristic. We are counting UnlinkedCodeBlock's age. And once the age becomes maximum size, we make UnlinkedFunctionExecutable ->
UnlinkedCodeBlock link weak. We also use execution counter information to reset this age: CodeBlock will reset undelying UnlinkedCodeBlock's age if it has executed
While this heuristic is quite simple, it has some effect in practice. Basically what happens with this heuristic is that UnlinkedFunctionExecutable ->
UnlinkedCodeBlock link strong. When GC happens, we are executing some CodeBlocks, which become live. And ScriptExecutables -> UnlinkedFunctionExecutables held
by this CodeBlock become also live. Then UnlinkedFunctionExecutables can mark the child UnlinkedCodeBlocks if it is not so old.
If some of parent UnlinkedFunctionExecutable becomes dead, child UnlinkedCodeBlocks tends to be dead unless some live CodeBlock holds it. But it is OK for a first
heuristics since this means that parent code block is now considered old, reachable UnlinkedCodeBlock will be used when the parent is executed again. So destroying
the tree is OK even if the tree may include some new UnlinkedCodeBlock. While we could make more sophisticated mechanism to manage these lifetime, I think this is a
good starting point.

Based on measurement, we pick 7 as a maximum age. If we pick 0, we can get more memory reduction (1 - 1.5 MB!), while we ends up reparsing codes so many times.
It seems that 7 can reduce fair amount of memory while doing small # of reparsing on average (usually, 1, 2. Sometimes, 100. But not 300, which is the case in 0).
If we want to get more memory reduction for the sake of performance, we could decrease this age limit.

Since we do not have an automated script right now so it is a bit difficult to measure memory footprint precisely. But manual testing shows that this patch improves
memory footprint of our target application from about 6.5 MB to about 5.9 MB.

* bytecode/CodeBlock.cpp:
(JSC::CodeBlock::finalizeUnconditionally):
* bytecode/CodeBlock.h:
* bytecode/UnlinkedCodeBlock.cpp:
(JSC::UnlinkedCodeBlock::UnlinkedCodeBlock):
(JSC::UnlinkedCodeBlock::visitChildren):
* bytecode/UnlinkedCodeBlock.h:
(JSC::UnlinkedCodeBlock::age const):
(JSC::UnlinkedCodeBlock::resetAge):
* bytecode/UnlinkedFunctionExecutable.cpp:
(JSC::UnlinkedFunctionExecutable::UnlinkedFunctionExecutable):
(JSC::UnlinkedFunctionExecutable::visitChildren):
(JSC::UnlinkedFunctionExecutable::unlinkedCodeBlockFor):
(JSC::UnlinkedFunctionExecutable::decodeCachedCodeBlocks):
(JSC::UnlinkedFunctionExecutable::finalizeUnconditionally):
* bytecode/UnlinkedFunctionExecutable.h:
* heap/Heap.cpp:
(JSC::Heap::finalizeUnconditionalFinalizers):
* runtime/CachedTypes.cpp:
(JSC::UnlinkedCodeBlock::UnlinkedCodeBlock):
(JSC::UnlinkedFunctionExecutable::UnlinkedFunctionExecutable):
* runtime/CodeSpecializationKind.h:
* runtime/Options.h:
* runtime/VM.cpp:
(JSC::VM::isInMiniMode): Deleted.
* runtime/VM.h:
(JSC::VM::isInMiniMode):
(JSC::VM::useUnlinkedCodeBlockJettisoning):

Tools:

* Scripts/run-jsc-stress-tests:

Modified Paths

Added Paths

Diff

Modified: trunk/JSTests/ChangeLog (246271 => 246272)


--- trunk/JSTests/ChangeLog	2019-06-10 19:32:00 UTC (rev 246271)
+++ trunk/JSTests/ChangeLog	2019-06-10 19:49:58 UTC (rev 246272)
@@ -1,3 +1,14 @@
+2019-06-10  Yusuke Suzuki  <[email protected]>
+
+        [JSC] UnlinkedCodeBlock should be eventually jettisoned in VM mini mode
+        https://bugs.webkit.org/show_bug.cgi?id=198023
+
+        Reviewed by Saam Barati.
+
+        * stress/reparsing-unlinked-codeblock.js: Added.
+        (shouldBe):
+        (hello):
+
 2019-06-09  Yusuke Suzuki  <[email protected]>
 
         [JSC] Use mergePrediction in ValuePow prediction propagation

Added: trunk/JSTests/stress/reparsing-unlinked-codeblock.js (0 => 246272)


--- trunk/JSTests/stress/reparsing-unlinked-codeblock.js	                        (rev 0)
+++ trunk/JSTests/stress/reparsing-unlinked-codeblock.js	2019-06-10 19:49:58 UTC (rev 246272)
@@ -0,0 +1,24 @@
+//@ runDefault("--forceCodeBlockToJettisonDueToOldAge=1", "--useUnlinkedCodeBlockJettisoning=1")
+
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function hello()
+{
+    return (function () {
+        function world() {
+            return 42;
+        };
+        return world();
+    }());
+}
+
+// Compile hello and world function.
+shouldBe(hello(), 42);
+// Kick full GC 20 times to make UnlinkedCodeBlock aged and destroyed. Jettison hello CodeBlock, and underlying world UnlinkedCodeBlock.
+for (var i = 0; i < 20; ++i)
+    fullGC();
+// Recompile world.
+shouldBe(hello(), 42);

Modified: trunk/Source/_javascript_Core/ChangeLog (246271 => 246272)


--- trunk/Source/_javascript_Core/ChangeLog	2019-06-10 19:32:00 UTC (rev 246271)
+++ trunk/Source/_javascript_Core/ChangeLog	2019-06-10 19:49:58 UTC (rev 246272)
@@ -1,3 +1,100 @@
+2019-06-10  Yusuke Suzuki  <[email protected]>
+
+        [JSC] UnlinkedCodeBlock should be eventually jettisoned in VM mini mode
+        https://bugs.webkit.org/show_bug.cgi?id=198023
+
+        Reviewed by Saam Barati.
+
+        While CodeBlock is periodically jettisoned, UnlinkedCodeBlock and UnlinkedFunctionExecutable can be retained almost forever in certain type of applications.
+        When we execute a program, which has UnlinkedProgramCodeBlock retained in CodeCache. And UnlinkedProgramCodeBlock holds array of UnlinkedFunctionExecutable.
+        And UnlinkedFunctionExecutables hold UnlinkedFunctionCodeBlocks once it is generated. So eventually, this tree gets larger and larger until we purge
+        UnlinkedProgramCodeBlock from CodeCache. This is OK in the browser case. We navigate to various other pages, and UnlinkedProgramCodeBlocks should eventually
+        be pruned from CodeCache with the new ones. So this tree won't be retained forever. But the behavior is different in the other applications that do not have
+        navigations. If they only have one program which holds all, we basically retain this tree during executing this application. The same thing can happen in
+        web applications which does not have navigation and keeps alive for a long time. Once we hit CodeCache limit by periodically executing a new script, we will
+        hit the uppermost of memory footprint. But until that, we increase our memory footprint.
+
+        However, destroying these UnlinkedCodeBlocks and UnlinkedFunctionExecutables causes a tricky problem. In the browser environment, navigation can happen at any
+        time. So even if the given UnlinkedCodeBlock seems unused in the current page, it can be used when navigating to a new page which is under the same domain.
+        One example is initializing function in a script. It is only executed once per page. So once it is executed, it seems that this UnlinkedCodeBlock is unused.
+        But this will be used when we navigate to a new page. Pruning code blocks based on usage could cause performance regression.
+
+        But if our VM is mini VM mode, the story is different. In mini VM mode, we focus on memory footprint rather than performance e.g. daemons. The daemon never
+        reuse these CodeCache since we do not have the navigation.
+
+        This patch logically makes UnlinkedFunctionExecutable -> UnlinkedCodeBlock reference weak when VM is mini mode. If UnlinkedCodeBlock is used in previous GC
+        cycle, we retain it. But if it is not used, and if UnlinkedFunctionExecutable is only the cell keeping UnlinkedCodeBlock alive, we destroy it. It is a
+        heuristic. In a super pathological case, it could increase memory footprint. Consider the following example.
+
+            UnlinkedFunctionExecutable(A1) -> UnlinkedCodeBlock(B1) -> UnlinkedFunctionExecutable(C1) -> UnlinkedCodeBlock(D1)
+                                                                                                             ^
+                                                                                                         CodeBlock(E1)
+
+        We could delete A1, B1, and C1 while keeping D1. But if we eventually re-execute the same code corresponding to A1, B1, C1, they will be newly created, and
+        we will create duplicate UnlinkedCodeBlock and instructions stream for D1.
+
+                                                                                                         UnlinkedCodeBlock(D1)
+                                                                                                             ^
+                                                                                                         CodeBlock(E1)
+
+            UnlinkedFunctionExecutable(A2) -> UnlinkedCodeBlock(B2) -> UnlinkedFunctionExecutable(C2) -> UnlinkedCodeBlock(D2)
+
+        But this does not happen in practice and even it happens, we eventually discard D1 and D2 since CodeBlock E1 will be jettisoned anyway. So in practice, we do
+        not see memory footprint increase. We tested it in Gmail and the target application, but both said memory footprint reduction (30 MB / 400 MB and 1 MB /6 MB).
+        While this affects on performance much on tests which has navigation (1-3 % regression in Speedometer2, note that JetStream2 does not show regression in x64,
+        while it is not enabling mini mode), we do not apply this to non mini mode VM until we come up with a good strategy to fasten performance of re-generation.
+        Personally I think flushing destroyed UnlinkedCodeBlock to the disk sounds promising.
+
+        If UnlinkedCodeBlock is generated from bytecode cache, we do not make UnlinkedFunctionExecutable -> UnlinkedCodeBlock link weak because the decoder of the bytecode
+        cache assumes that generated JSCells won't be destroyed while the parent cells of that cell are live. This is true in the current implementation, and this assumption
+        will be broken with this patch. So, for now, we do not make this link weak. Currently, our target application does not use bytecode cache so it is OK.
+
+        This patch also introduce simple heuristic. We are counting UnlinkedCodeBlock's age. And once the age becomes maximum size, we make UnlinkedFunctionExecutable ->
+        UnlinkedCodeBlock link weak. We also use execution counter information to reset this age: CodeBlock will reset undelying UnlinkedCodeBlock's age if it has executed
+        While this heuristic is quite simple, it has some effect in practice. Basically what happens with this heuristic is that UnlinkedFunctionExecutable ->
+        UnlinkedCodeBlock link strong. When GC happens, we are executing some CodeBlocks, which become live. And ScriptExecutables -> UnlinkedFunctionExecutables held
+        by this CodeBlock become also live. Then UnlinkedFunctionExecutables can mark the child UnlinkedCodeBlocks if it is not so old.
+        If some of parent UnlinkedFunctionExecutable becomes dead, child UnlinkedCodeBlocks tends to be dead unless some live CodeBlock holds it. But it is OK for a first
+        heuristics since this means that parent code block is now considered old, reachable UnlinkedCodeBlock will be used when the parent is executed again. So destroying
+        the tree is OK even if the tree may include some new UnlinkedCodeBlock. While we could make more sophisticated mechanism to manage these lifetime, I think this is a
+        good starting point.
+
+        Based on measurement, we pick 7 as a maximum age. If we pick 0, we can get more memory reduction (1 - 1.5 MB!), while we ends up reparsing codes so many times.
+        It seems that 7 can reduce fair amount of memory while doing small # of reparsing on average (usually, 1, 2. Sometimes, 100. But not 300, which is the case in 0).
+        If we want to get more memory reduction for the sake of performance, we could decrease this age limit.
+
+        Since we do not have an automated script right now so it is a bit difficult to measure memory footprint precisely. But manual testing shows that this patch improves
+        memory footprint of our target application from about 6.5 MB to about 5.9 MB.
+
+        * bytecode/CodeBlock.cpp:
+        (JSC::CodeBlock::finalizeUnconditionally):
+        * bytecode/CodeBlock.h:
+        * bytecode/UnlinkedCodeBlock.cpp:
+        (JSC::UnlinkedCodeBlock::UnlinkedCodeBlock):
+        (JSC::UnlinkedCodeBlock::visitChildren):
+        * bytecode/UnlinkedCodeBlock.h:
+        (JSC::UnlinkedCodeBlock::age const):
+        (JSC::UnlinkedCodeBlock::resetAge):
+        * bytecode/UnlinkedFunctionExecutable.cpp:
+        (JSC::UnlinkedFunctionExecutable::UnlinkedFunctionExecutable):
+        (JSC::UnlinkedFunctionExecutable::visitChildren):
+        (JSC::UnlinkedFunctionExecutable::unlinkedCodeBlockFor):
+        (JSC::UnlinkedFunctionExecutable::decodeCachedCodeBlocks):
+        (JSC::UnlinkedFunctionExecutable::finalizeUnconditionally):
+        * bytecode/UnlinkedFunctionExecutable.h:
+        * heap/Heap.cpp:
+        (JSC::Heap::finalizeUnconditionalFinalizers):
+        * runtime/CachedTypes.cpp:
+        (JSC::UnlinkedCodeBlock::UnlinkedCodeBlock):
+        (JSC::UnlinkedFunctionExecutable::UnlinkedFunctionExecutable):
+        * runtime/CodeSpecializationKind.h:
+        * runtime/Options.h:
+        * runtime/VM.cpp:
+        (JSC::VM::isInMiniMode): Deleted.
+        * runtime/VM.h:
+        (JSC::VM::isInMiniMode):
+        (JSC::VM::useUnlinkedCodeBlockJettisoning):
+
 2019-06-10  Timothy Hatcher  <[email protected]>
 
         Integrate dark mode support for iOS.

Modified: trunk/Source/_javascript_Core/bytecode/CodeBlock.cpp (246271 => 246272)


--- trunk/Source/_javascript_Core/bytecode/CodeBlock.cpp	2019-06-10 19:32:00 UTC (rev 246271)
+++ trunk/Source/_javascript_Core/bytecode/CodeBlock.cpp	2019-06-10 19:49:58 UTC (rev 246272)
@@ -1373,6 +1373,37 @@
     }
 #endif // ENABLE(DFG_JIT)
 
+    auto updateActivity = [&] {
+        if (!VM::useUnlinkedCodeBlockJettisoning())
+            return;
+        JITCode* jitCode = m_jitCode.get();
+        double count = 0;
+        bool alwaysActive = false;
+        switch (JITCode::jitTypeFor(jitCode)) {
+        case JITType::None:
+        case JITType::HostCallThunk:
+            return;
+        case JITType::InterpreterThunk:
+            count = m_llintExecuteCounter.count();
+            break;
+        case JITType::BaselineJIT:
+            count = m_jitExecuteCounter.count();
+            break;
+        case JITType::DFGJIT:
+            count = static_cast<DFG::JITCode*>(jitCode)->tierUpCounter.count();
+            break;
+        case JITType::FTLJIT:
+            alwaysActive = true;
+            break;
+        }
+        if (alwaysActive || m_previousCounter < count) {
+            // CodeBlock is active right now, so resetting UnlinkedCodeBlock's age.
+            m_unlinkedCode->resetAge();
+        }
+        m_previousCounter = count;
+    };
+    updateActivity();
+
     VM::SpaceAndSet::setFor(*subspace()).remove(this);
 }
 

Modified: trunk/Source/_javascript_Core/bytecode/CodeBlock.h (246271 => 246272)


--- trunk/Source/_javascript_Core/bytecode/CodeBlock.h	2019-06-10 19:32:00 UTC (rev 246271)
+++ trunk/Source/_javascript_Core/bytecode/CodeBlock.h	2019-06-10 19:49:58 UTC (rev 246272)
@@ -1010,6 +1010,7 @@
     RefPtr<MetadataTable> m_metadata;
 
     MonotonicTime m_creationTime;
+    double m_previousCounter { 0 };
 
     std::unique_ptr<RareData> m_rareData;
 };

Modified: trunk/Source/_javascript_Core/bytecode/UnlinkedCodeBlock.cpp (246271 => 246272)


--- trunk/Source/_javascript_Core/bytecode/UnlinkedCodeBlock.cpp	2019-06-10 19:32:00 UTC (rev 246271)
+++ trunk/Source/_javascript_Core/bytecode/UnlinkedCodeBlock.cpp	2019-06-10 19:49:58 UTC (rev 246272)
@@ -71,6 +71,7 @@
     , m_evalContextType(static_cast<unsigned>(info.evalContextType()))
     , m_codeType(static_cast<unsigned>(codeType))
     , m_didOptimize(static_cast<unsigned>(MixedTriState))
+    , m_age(0)
     , m_parseMode(info.parseMode())
     , m_codeGenerationMode(codeGenerationMode)
     , m_metadata(UnlinkedMetadataTable::create())
@@ -88,6 +89,7 @@
     ASSERT_GC_OBJECT_INHERITS(thisObject, info());
     Base::visitChildren(thisObject, visitor);
     auto locker = holdLock(thisObject->cellLock());
+    thisObject->m_age = std::min<unsigned>(static_cast<unsigned>(thisObject->m_age) + 1, maxAge);
     for (FunctionExpressionVector::iterator ptr = thisObject->m_functionDecls.begin(), end = thisObject->m_functionDecls.end(); ptr != end; ++ptr)
         visitor.append(*ptr);
     for (FunctionExpressionVector::iterator ptr = thisObject->m_functionExprs.begin(), end = thisObject->m_functionExprs.end(); ptr != end; ++ptr)

Modified: trunk/Source/_javascript_Core/bytecode/UnlinkedCodeBlock.h (246271 => 246272)


--- trunk/Source/_javascript_Core/bytecode/UnlinkedCodeBlock.h	2019-06-10 19:32:00 UTC (rev 246271)
+++ trunk/Source/_javascript_Core/bytecode/UnlinkedCodeBlock.h	2019-06-10 19:49:58 UTC (rev 246272)
@@ -339,6 +339,11 @@
     TriState didOptimize() const { return static_cast<TriState>(m_didOptimize); }
     void setDidOptimize(TriState didOptimize) { m_didOptimize = static_cast<unsigned>(didOptimize); }
 
+    static constexpr unsigned maxAge = 7;
+
+    unsigned age() const { return m_age; }
+    void resetAge() { m_age = 0; }
+
     void dump(PrintStream&) const;
 
     BytecodeLivenessAnalysis& livenessAnalysis(CodeBlock* codeBlock)
@@ -404,6 +409,7 @@
     void getLineAndColumn(const ExpressionRangeInfo&, unsigned& line, unsigned& column) const;
     BytecodeLivenessAnalysis& livenessAnalysisSlow(CodeBlock*);
 
+
     VirtualRegister m_thisRegister;
     VirtualRegister m_scopeRegister;
 
@@ -424,6 +430,8 @@
     unsigned m_evalContextType : 2;
     unsigned m_codeType : 2;
     unsigned m_didOptimize : 2;
+    unsigned m_age : 3;
+    static_assert(((1U << 3) - 1) >= maxAge);
 public:
     ConcurrentJSLock m_lock;
 private:

Modified: trunk/Source/_javascript_Core/bytecode/UnlinkedFunctionExecutable.cpp (246271 => 246272)


--- trunk/Source/_javascript_Core/bytecode/UnlinkedFunctionExecutable.cpp	2019-06-10 19:32:00 UTC (rev 246271)
+++ trunk/Source/_javascript_Core/bytecode/UnlinkedFunctionExecutable.cpp	2019-06-10 19:49:58 UTC (rev 246272)
@@ -107,6 +107,7 @@
     , m_constructorKind(static_cast<unsigned>(node->constructorKind()))
     , m_functionMode(static_cast<unsigned>(node->functionMode()))
     , m_derivedContextType(static_cast<unsigned>(derivedContextType))
+    , m_isGeneratedFromCache(false)
     , m_unlinkedCodeBlockForCall()
     , m_unlinkedCodeBlockForConstruct()
     , m_name(node->ident())
@@ -142,7 +143,19 @@
     UnlinkedFunctionExecutable* thisObject = jsCast<UnlinkedFunctionExecutable*>(cell);
     ASSERT_GC_OBJECT_INHERITS(thisObject, info());
     Base::visitChildren(thisObject, visitor);
-    if (!thisObject->m_isCached) {
+
+    if (thisObject->codeBlockEdgeMayBeWeak()) {
+        auto markIfProfitable = [&] (WriteBarrier<UnlinkedFunctionCodeBlock>& unlinkedCodeBlock) {
+            if (!unlinkedCodeBlock)
+                return;
+            if (unlinkedCodeBlock->didOptimize() == TrueTriState)
+                visitor.append(unlinkedCodeBlock);
+            else if (unlinkedCodeBlock->age() < UnlinkedCodeBlock::maxAge)
+                visitor.append(unlinkedCodeBlock);
+        };
+        markIfProfitable(thisObject->m_unlinkedCodeBlockForCall);
+        markIfProfitable(thisObject->m_unlinkedCodeBlockForConstruct);
+    } else if (!thisObject->m_isCached) {
         visitor.append(thisObject->m_unlinkedCodeBlockForCall);
         visitor.append(thisObject->m_unlinkedCodeBlockForConstruct);
     }
@@ -197,24 +210,12 @@
     return executable;
 }
 
-UnlinkedFunctionCodeBlock* UnlinkedFunctionExecutable::unlinkedCodeBlockFor(CodeSpecializationKind specializationKind)
-{
-    switch (specializationKind) {
-    case CodeForCall:
-        return m_unlinkedCodeBlockForCall.get();
-    case CodeForConstruct:
-        return m_unlinkedCodeBlockForConstruct.get();
-    }
-    ASSERT_NOT_REACHED();
-    return nullptr;
-}
-
 UnlinkedFunctionCodeBlock* UnlinkedFunctionExecutable::unlinkedCodeBlockFor(
     VM& vm, const SourceCode& source, CodeSpecializationKind specializationKind, 
     OptionSet<CodeGenerationMode> codeGenerationMode, ParserError& error, SourceParseMode parseMode)
 {
     if (m_isCached)
-        decodeCachedCodeBlocks();
+        decodeCachedCodeBlocks(vm);
     switch (specializationKind) {
     case CodeForCall:
         if (UnlinkedFunctionCodeBlock* codeBlock = m_unlinkedCodeBlockForCall.get())
@@ -246,7 +247,7 @@
     return result;
 }
 
-void UnlinkedFunctionExecutable::decodeCachedCodeBlocks()
+void UnlinkedFunctionExecutable::decodeCachedCodeBlocks(VM& vm)
 {
     ASSERT(m_isCached);
     ASSERT(m_decoder);
@@ -256,7 +257,7 @@
     int32_t cachedCodeBlockForCallOffset = m_cachedCodeBlockForCallOffset;
     int32_t cachedCodeBlockForConstructOffset = m_cachedCodeBlockForConstructOffset;
 
-    DeferGC deferGC(decoder->vm().heap);
+    DeferGC deferGC(vm.heap);
 
     // No need to clear m_unlinkedCodeBlockForCall here, since we moved the decoder out of the same slot
     if (cachedCodeBlockForCallOffset)
@@ -268,7 +269,7 @@
 
     WTF::storeStoreFence();
     m_isCached = false;
-    decoder->vm().heap.writeBarrier(this);
+    vm.heap.writeBarrier(this);
 }
 
 UnlinkedFunctionExecutable::RareData& UnlinkedFunctionExecutable::ensureRareDataSlow()
@@ -284,4 +285,25 @@
     m_typeProfilingEndOffset = std::numeric_limits<unsigned>::max();
 }
 
+void UnlinkedFunctionExecutable::finalizeUnconditionally(VM& vm)
+{
+    if (codeBlockEdgeMayBeWeak()) {
+        bool isCleared = false;
+        bool isStillValid = false;
+        auto clearIfDead = [&] (WriteBarrier<UnlinkedFunctionCodeBlock>& unlinkedCodeBlock) {
+            if (!unlinkedCodeBlock)
+                return;
+            if (!vm.heap.isMarked(unlinkedCodeBlock.get())) {
+                unlinkedCodeBlock.clear();
+                isCleared = true;
+            } else
+                isStillValid = true;
+        };
+        clearIfDead(m_unlinkedCodeBlockForCall);
+        clearIfDead(m_unlinkedCodeBlockForConstruct);
+        if (isCleared && !isStillValid)
+            vm.unlinkedFunctionExecutableSpace.set.remove(this);
+    }
+}
+
 } // namespace JSC

Modified: trunk/Source/_javascript_Core/bytecode/UnlinkedFunctionExecutable.h (246271 => 246272)


--- trunk/Source/_javascript_Core/bytecode/UnlinkedFunctionExecutable.h	2019-06-10 19:32:00 UTC (rev 246271)
+++ trunk/Source/_javascript_Core/bytecode/UnlinkedFunctionExecutable.h	2019-06-10 19:49:58 UTC (rev 246272)
@@ -114,8 +114,6 @@
     unsigned typeProfilingEndOffset() const { return m_typeProfilingEndOffset; }
     void setInvalidTypeProfilingOffsets();
 
-    UnlinkedFunctionCodeBlock* unlinkedCodeBlockFor(CodeSpecializationKind);
-
     UnlinkedFunctionCodeBlock* unlinkedCodeBlockFor(
         VM&, const SourceCode&, CodeSpecializationKind, OptionSet<CodeGenerationMode>,
         ParserError&, SourceParseMode);
@@ -189,6 +187,8 @@
         ensureRareData().m_sourceMappingURLDirective = sourceMappingURL;
     }
 
+    void finalizeUnconditionally(VM&);
+
     struct RareData {
         WTF_MAKE_STRUCT_FAST_ALLOCATED;
 
@@ -202,8 +202,15 @@
     UnlinkedFunctionExecutable(VM*, Structure*, const SourceCode&, FunctionMetadataNode*, UnlinkedFunctionKind, ConstructAbility, JSParserScriptMode, Optional<CompactVariableMap::Handle>,  JSC::DerivedContextType, bool isBuiltinDefaultClassConstructor);
     UnlinkedFunctionExecutable(Decoder&, const CachedFunctionExecutable&);
 
-    void decodeCachedCodeBlocks();
+    void decodeCachedCodeBlocks(VM&);
 
+    bool codeBlockEdgeMayBeWeak() const
+    {
+        // Currently, bytecode cache assumes that the tree of UnlinkedFunctionExecutable and UnlinkedCodeBlock will not be destroyed while the parent is live.
+        // Bytecode cache uses this asumption to avoid duplicate materialization by bookkeeping the heap cells in the offste-to-pointer map.
+        return VM::useUnlinkedCodeBlockJettisoning() && !m_isGeneratedFromCache;
+    }
+
     unsigned m_firstLineOffset : 31;
     unsigned m_isInStrictContext : 1;
     unsigned m_lineCount : 31;
@@ -228,6 +235,7 @@
     unsigned m_constructorKind : 2;
     unsigned m_functionMode : 2; // FunctionMode
     unsigned m_derivedContextType: 2;
+    unsigned m_isGeneratedFromCache : 1;
 
     union {
         WriteBarrier<UnlinkedFunctionCodeBlock> m_unlinkedCodeBlockForCall;

Modified: trunk/Source/_javascript_Core/heap/Heap.cpp (246271 => 246272)


--- trunk/Source/_javascript_Core/heap/Heap.cpp	2019-06-10 19:32:00 UTC (rev 246271)
+++ trunk/Source/_javascript_Core/heap/Heap.cpp	2019-06-10 19:49:58 UTC (rev 246272)
@@ -606,6 +606,7 @@
         });
     finalizeMarkedUnconditionalFinalizers<ExecutableToCodeBlockEdge>(vm()->executableToCodeBlockEdgesWithFinalizers);
     finalizeMarkedUnconditionalFinalizers<StructureRareData>(vm()->structureRareDataSpace);
+    finalizeMarkedUnconditionalFinalizers<UnlinkedFunctionExecutable>(vm()->unlinkedFunctionExecutableSpace.set);
     if (vm()->m_weakSetSpace)
         finalizeMarkedUnconditionalFinalizers<JSWeakSet>(*vm()->m_weakSetSpace);
     if (vm()->m_weakMapSpace)

Modified: trunk/Source/_javascript_Core/runtime/CachedTypes.cpp (246271 => 246272)


--- trunk/Source/_javascript_Core/runtime/CachedTypes.cpp	2019-06-10 19:32:00 UTC (rev 246271)
+++ trunk/Source/_javascript_Core/runtime/CachedTypes.cpp	2019-06-10 19:49:58 UTC (rev 246272)
@@ -2031,6 +2031,7 @@
     , m_codeType(cachedCodeBlock.codeType())
 
     , m_didOptimize(static_cast<unsigned>(MixedTriState))
+    , m_age(0)
 
     , m_features(cachedCodeBlock.features())
     , m_parseMode(cachedCodeBlock.parseMode())
@@ -2158,6 +2159,7 @@
     , m_constructorKind(cachedExecutable.constructorKind())
     , m_functionMode(cachedExecutable.functionMode())
     , m_derivedContextType(cachedExecutable.derivedContextType())
+    , m_isGeneratedFromCache(true)
     , m_unlinkedCodeBlockForCall()
     , m_unlinkedCodeBlockForConstruct()
 

Modified: trunk/Source/_javascript_Core/runtime/CodeSpecializationKind.h (246271 => 246272)


--- trunk/Source/_javascript_Core/runtime/CodeSpecializationKind.h	2019-06-10 19:32:00 UTC (rev 246271)
+++ trunk/Source/_javascript_Core/runtime/CodeSpecializationKind.h	2019-06-10 19:49:58 UTC (rev 246272)
@@ -27,7 +27,7 @@
 
 namespace JSC {
 
-enum CodeSpecializationKind { CodeForCall, CodeForConstruct };
+enum CodeSpecializationKind : uint8_t { CodeForCall, CodeForConstruct };
 
 inline CodeSpecializationKind specializationFromIsCall(bool isCall)
 {

Modified: trunk/Source/_javascript_Core/runtime/Options.h (246271 => 246272)


--- trunk/Source/_javascript_Core/runtime/Options.h	2019-06-10 19:32:00 UTC (rev 246271)
+++ trunk/Source/_javascript_Core/runtime/Options.h	2019-06-10 19:49:58 UTC (rev 246272)
@@ -520,6 +520,7 @@
     v(double, validateAbstractInterpreterStateProbability, 0.5, Normal, nullptr) \
     v(optionString, dumpJITMemoryPath, nullptr, Restricted, nullptr) \
     v(double, dumpJITMemoryFlushInterval, 10, Restricted, "Maximum time in between flushes of the JIT memory dump in seconds.") \
+    v(bool, useUnlinkedCodeBlockJettisoning, false, Normal, "If true, UnlinkedCodeBlock can be jettisoned.") \
 
 
 enum OptionEquivalence {

Modified: trunk/Source/_javascript_Core/runtime/VM.cpp (246271 => 246272)


--- trunk/Source/_javascript_Core/runtime/VM.cpp	2019-06-10 19:32:00 UTC (rev 246271)
+++ trunk/Source/_javascript_Core/runtime/VM.cpp	2019-06-10 19:49:58 UTC (rev 246272)
@@ -232,11 +232,6 @@
 #endif
 }
 
-bool VM::isInMiniMode()
-{
-    return !canUseJIT() || Options::forceMiniVMMode();
-}
-
 inline unsigned VM::nextID()
 {
     for (;;) {

Modified: trunk/Source/_javascript_Core/runtime/VM.h (246271 => 246272)


--- trunk/Source/_javascript_Core/runtime/VM.h	2019-06-10 19:32:00 UTC (rev 246271)
+++ trunk/Source/_javascript_Core/runtime/VM.h	2019-06-10 19:49:58 UTC (rev 246272)
@@ -632,8 +632,16 @@
     };
 
     static JS_EXPORT_PRIVATE bool canUseAssembler();
-    static JS_EXPORT_PRIVATE bool isInMiniMode();
+    static bool isInMiniMode()
+    {
+        return !canUseJIT() || Options::forceMiniVMMode();
+    }
 
+    static bool useUnlinkedCodeBlockJettisoning()
+    {
+        return Options::useUnlinkedCodeBlockJettisoning() || isInMiniMode();
+    }
+
     static void computeCanUseJIT();
     ALWAYS_INLINE static bool canUseJIT()
     {

Modified: trunk/Tools/ChangeLog (246271 => 246272)


--- trunk/Tools/ChangeLog	2019-06-10 19:32:00 UTC (rev 246271)
+++ trunk/Tools/ChangeLog	2019-06-10 19:49:58 UTC (rev 246272)
@@ -1,3 +1,12 @@
+2019-06-10  Yusuke Suzuki  <[email protected]>
+
+        [JSC] UnlinkedCodeBlock should be eventually jettisoned in VM mini mode
+        https://bugs.webkit.org/show_bug.cgi?id=198023
+
+        Reviewed by Saam Barati.
+
+        * Scripts/run-jsc-stress-tests:
+
 2019-06-10  Timothy Hatcher  <[email protected]>
 
         Integrate dark mode support for iOS.

Modified: trunk/Tools/Scripts/run-jsc-stress-tests (246271 => 246272)


--- trunk/Tools/Scripts/run-jsc-stress-tests	2019-06-10 19:32:00 UTC (rev 246271)
+++ trunk/Tools/Scripts/run-jsc-stress-tests	2019-06-10 19:49:58 UTC (rev 246272)
@@ -780,6 +780,10 @@
     run("shadow-chicken", "--useDFGJIT=false", "--alwaysUseShadowChicken=true", *optionalTestSpecificOptions)
 end
 
+def runMiniMode(*optionalTestSpecificOptions)
+    run("mini-mode", "--forceMiniVMMode=true", *optionalTestSpecificOptions)
+end
+
 def defaultRun
     if $mode == "quick"
         defaultQuickRun
@@ -786,6 +790,7 @@
     else
         runDefault
         runBytecodeCache
+        runMiniMode
         if $jitTests
             runNoLLInt
             runNoCJITValidatePhases
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to