Changeset 246272 in webkit
- Timestamp:
- Jun 10, 2019 12:49:58 PM (5 years ago)
- Location:
- trunk
- Files:
-
- 1 added
- 16 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/JSTests/ChangeLog
r246237 r246272 1 2019-06-10 Yusuke Suzuki <ysuzuki@apple.com> 2 3 [JSC] UnlinkedCodeBlock should be eventually jettisoned in VM mini mode 4 https://bugs.webkit.org/show_bug.cgi?id=198023 5 6 Reviewed by Saam Barati. 7 8 * stress/reparsing-unlinked-codeblock.js: Added. 9 (shouldBe): 10 (hello): 11 1 12 2019-06-09 Yusuke Suzuki <ysuzuki@apple.com> 2 13 -
trunk/Source/JavaScriptCore/ChangeLog
r246270 r246272 1 2019-06-10 Yusuke Suzuki <ysuzuki@apple.com> 2 3 [JSC] UnlinkedCodeBlock should be eventually jettisoned in VM mini mode 4 https://bugs.webkit.org/show_bug.cgi?id=198023 5 6 Reviewed by Saam Barati. 7 8 While CodeBlock is periodically jettisoned, UnlinkedCodeBlock and UnlinkedFunctionExecutable can be retained almost forever in certain type of applications. 9 When we execute a program, which has UnlinkedProgramCodeBlock retained in CodeCache. And UnlinkedProgramCodeBlock holds array of UnlinkedFunctionExecutable. 10 And UnlinkedFunctionExecutables hold UnlinkedFunctionCodeBlocks once it is generated. So eventually, this tree gets larger and larger until we purge 11 UnlinkedProgramCodeBlock from CodeCache. This is OK in the browser case. We navigate to various other pages, and UnlinkedProgramCodeBlocks should eventually 12 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 13 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 14 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 15 hit the uppermost of memory footprint. But until that, we increase our memory footprint. 16 17 However, destroying these UnlinkedCodeBlocks and UnlinkedFunctionExecutables causes a tricky problem. In the browser environment, navigation can happen at any 18 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. 19 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. 20 But this will be used when we navigate to a new page. Pruning code blocks based on usage could cause performance regression. 21 22 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 23 reuse these CodeCache since we do not have the navigation. 24 25 This patch logically makes UnlinkedFunctionExecutable -> UnlinkedCodeBlock reference weak when VM is mini mode. If UnlinkedCodeBlock is used in previous GC 26 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 27 heuristic. In a super pathological case, it could increase memory footprint. Consider the following example. 28 29 UnlinkedFunctionExecutable(A1) -> UnlinkedCodeBlock(B1) -> UnlinkedFunctionExecutable(C1) -> UnlinkedCodeBlock(D1) 30 ^ 31 CodeBlock(E1) 32 33 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 34 we will create duplicate UnlinkedCodeBlock and instructions stream for D1. 35 36 UnlinkedCodeBlock(D1) 37 ^ 38 CodeBlock(E1) 39 40 UnlinkedFunctionExecutable(A2) -> UnlinkedCodeBlock(B2) -> UnlinkedFunctionExecutable(C2) -> UnlinkedCodeBlock(D2) 41 42 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 43 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). 44 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, 45 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. 46 Personally I think flushing destroyed UnlinkedCodeBlock to the disk sounds promising. 47 48 If UnlinkedCodeBlock is generated from bytecode cache, we do not make UnlinkedFunctionExecutable -> UnlinkedCodeBlock link weak because the decoder of the bytecode 49 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 50 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. 51 52 This patch also introduce simple heuristic. We are counting UnlinkedCodeBlock's age. And once the age becomes maximum size, we make UnlinkedFunctionExecutable -> 53 UnlinkedCodeBlock link weak. We also use execution counter information to reset this age: CodeBlock will reset undelying UnlinkedCodeBlock's age if it has executed 54 While this heuristic is quite simple, it has some effect in practice. Basically what happens with this heuristic is that UnlinkedFunctionExecutable -> 55 UnlinkedCodeBlock link strong. When GC happens, we are executing some CodeBlocks, which become live. And ScriptExecutables -> UnlinkedFunctionExecutables held 56 by this CodeBlock become also live. Then UnlinkedFunctionExecutables can mark the child UnlinkedCodeBlocks if it is not so old. 57 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 58 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 59 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 60 good starting point. 61 62 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. 63 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). 64 If we want to get more memory reduction for the sake of performance, we could decrease this age limit. 65 66 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 67 memory footprint of our target application from about 6.5 MB to about 5.9 MB. 68 69 * bytecode/CodeBlock.cpp: 70 (JSC::CodeBlock::finalizeUnconditionally): 71 * bytecode/CodeBlock.h: 72 * bytecode/UnlinkedCodeBlock.cpp: 73 (JSC::UnlinkedCodeBlock::UnlinkedCodeBlock): 74 (JSC::UnlinkedCodeBlock::visitChildren): 75 * bytecode/UnlinkedCodeBlock.h: 76 (JSC::UnlinkedCodeBlock::age const): 77 (JSC::UnlinkedCodeBlock::resetAge): 78 * bytecode/UnlinkedFunctionExecutable.cpp: 79 (JSC::UnlinkedFunctionExecutable::UnlinkedFunctionExecutable): 80 (JSC::UnlinkedFunctionExecutable::visitChildren): 81 (JSC::UnlinkedFunctionExecutable::unlinkedCodeBlockFor): 82 (JSC::UnlinkedFunctionExecutable::decodeCachedCodeBlocks): 83 (JSC::UnlinkedFunctionExecutable::finalizeUnconditionally): 84 * bytecode/UnlinkedFunctionExecutable.h: 85 * heap/Heap.cpp: 86 (JSC::Heap::finalizeUnconditionalFinalizers): 87 * runtime/CachedTypes.cpp: 88 (JSC::UnlinkedCodeBlock::UnlinkedCodeBlock): 89 (JSC::UnlinkedFunctionExecutable::UnlinkedFunctionExecutable): 90 * runtime/CodeSpecializationKind.h: 91 * runtime/Options.h: 92 * runtime/VM.cpp: 93 (JSC::VM::isInMiniMode): Deleted. 94 * runtime/VM.h: 95 (JSC::VM::isInMiniMode): 96 (JSC::VM::useUnlinkedCodeBlockJettisoning): 97 1 98 2019-06-10 Timothy Hatcher <timothy@apple.com> 2 99 -
trunk/Source/JavaScriptCore/bytecode/CodeBlock.cpp
r245906 r246272 1374 1374 #endif // ENABLE(DFG_JIT) 1375 1375 1376 auto updateActivity = [&] { 1377 if (!VM::useUnlinkedCodeBlockJettisoning()) 1378 return; 1379 JITCode* jitCode = m_jitCode.get(); 1380 double count = 0; 1381 bool alwaysActive = false; 1382 switch (JITCode::jitTypeFor(jitCode)) { 1383 case JITType::None: 1384 case JITType::HostCallThunk: 1385 return; 1386 case JITType::InterpreterThunk: 1387 count = m_llintExecuteCounter.count(); 1388 break; 1389 case JITType::BaselineJIT: 1390 count = m_jitExecuteCounter.count(); 1391 break; 1392 case JITType::DFGJIT: 1393 count = static_cast<DFG::JITCode*>(jitCode)->tierUpCounter.count(); 1394 break; 1395 case JITType::FTLJIT: 1396 alwaysActive = true; 1397 break; 1398 } 1399 if (alwaysActive || m_previousCounter < count) { 1400 // CodeBlock is active right now, so resetting UnlinkedCodeBlock's age. 1401 m_unlinkedCode->resetAge(); 1402 } 1403 m_previousCounter = count; 1404 }; 1405 updateActivity(); 1406 1376 1407 VM::SpaceAndSet::setFor(*subspace()).remove(this); 1377 1408 } -
trunk/Source/JavaScriptCore/bytecode/CodeBlock.h
r245906 r246272 1011 1011 1012 1012 MonotonicTime m_creationTime; 1013 double m_previousCounter { 0 }; 1013 1014 1014 1015 std::unique_ptr<RareData> m_rareData; -
trunk/Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.cpp
r244915 r246272 72 72 , m_codeType(static_cast<unsigned>(codeType)) 73 73 , m_didOptimize(static_cast<unsigned>(MixedTriState)) 74 , m_age(0) 74 75 , m_parseMode(info.parseMode()) 75 76 , m_codeGenerationMode(codeGenerationMode) … … 89 90 Base::visitChildren(thisObject, visitor); 90 91 auto locker = holdLock(thisObject->cellLock()); 92 thisObject->m_age = std::min<unsigned>(static_cast<unsigned>(thisObject->m_age) + 1, maxAge); 91 93 for (FunctionExpressionVector::iterator ptr = thisObject->m_functionDecls.begin(), end = thisObject->m_functionDecls.end(); ptr != end; ++ptr) 92 94 visitor.append(*ptr); -
trunk/Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.h
r244915 r246272 340 340 void setDidOptimize(TriState didOptimize) { m_didOptimize = static_cast<unsigned>(didOptimize); } 341 341 342 static constexpr unsigned maxAge = 7; 343 344 unsigned age() const { return m_age; } 345 void resetAge() { m_age = 0; } 346 342 347 void dump(PrintStream&) const; 343 348 … … 404 409 void getLineAndColumn(const ExpressionRangeInfo&, unsigned& line, unsigned& column) const; 405 410 BytecodeLivenessAnalysis& livenessAnalysisSlow(CodeBlock*); 411 406 412 407 413 VirtualRegister m_thisRegister; … … 425 431 unsigned m_codeType : 2; 426 432 unsigned m_didOptimize : 2; 433 unsigned m_age : 3; 434 static_assert(((1U << 3) - 1) >= maxAge); 427 435 public: 428 436 ConcurrentJSLock m_lock; -
trunk/Source/JavaScriptCore/bytecode/UnlinkedFunctionExecutable.cpp
r245288 r246272 108 108 , m_functionMode(static_cast<unsigned>(node->functionMode())) 109 109 , m_derivedContextType(static_cast<unsigned>(derivedContextType)) 110 , m_isGeneratedFromCache(false) 110 111 , m_unlinkedCodeBlockForCall() 111 112 , m_unlinkedCodeBlockForConstruct() … … 143 144 ASSERT_GC_OBJECT_INHERITS(thisObject, info()); 144 145 Base::visitChildren(thisObject, visitor); 145 if (!thisObject->m_isCached) { 146 147 if (thisObject->codeBlockEdgeMayBeWeak()) { 148 auto markIfProfitable = [&] (WriteBarrier<UnlinkedFunctionCodeBlock>& unlinkedCodeBlock) { 149 if (!unlinkedCodeBlock) 150 return; 151 if (unlinkedCodeBlock->didOptimize() == TrueTriState) 152 visitor.append(unlinkedCodeBlock); 153 else if (unlinkedCodeBlock->age() < UnlinkedCodeBlock::maxAge) 154 visitor.append(unlinkedCodeBlock); 155 }; 156 markIfProfitable(thisObject->m_unlinkedCodeBlockForCall); 157 markIfProfitable(thisObject->m_unlinkedCodeBlockForConstruct); 158 } else if (!thisObject->m_isCached) { 146 159 visitor.append(thisObject->m_unlinkedCodeBlockForCall); 147 160 visitor.append(thisObject->m_unlinkedCodeBlockForConstruct); … … 198 211 } 199 212 200 UnlinkedFunctionCodeBlock* UnlinkedFunctionExecutable::unlinkedCodeBlockFor(CodeSpecializationKind specializationKind)201 {202 switch (specializationKind) {203 case CodeForCall:204 return m_unlinkedCodeBlockForCall.get();205 case CodeForConstruct:206 return m_unlinkedCodeBlockForConstruct.get();207 }208 ASSERT_NOT_REACHED();209 return nullptr;210 }211 212 213 UnlinkedFunctionCodeBlock* UnlinkedFunctionExecutable::unlinkedCodeBlockFor( 213 214 VM& vm, const SourceCode& source, CodeSpecializationKind specializationKind, … … 215 216 { 216 217 if (m_isCached) 217 decodeCachedCodeBlocks( );218 decodeCachedCodeBlocks(vm); 218 219 switch (specializationKind) { 219 220 case CodeForCall: … … 247 248 } 248 249 249 void UnlinkedFunctionExecutable::decodeCachedCodeBlocks( )250 void UnlinkedFunctionExecutable::decodeCachedCodeBlocks(VM& vm) 250 251 { 251 252 ASSERT(m_isCached); … … 257 258 int32_t cachedCodeBlockForConstructOffset = m_cachedCodeBlockForConstructOffset; 258 259 259 DeferGC deferGC( decoder->vm().heap);260 DeferGC deferGC(vm.heap); 260 261 261 262 // No need to clear m_unlinkedCodeBlockForCall here, since we moved the decoder out of the same slot … … 269 270 WTF::storeStoreFence(); 270 271 m_isCached = false; 271 decoder->vm().heap.writeBarrier(this);272 vm.heap.writeBarrier(this); 272 273 } 273 274 … … 285 286 } 286 287 288 void UnlinkedFunctionExecutable::finalizeUnconditionally(VM& vm) 289 { 290 if (codeBlockEdgeMayBeWeak()) { 291 bool isCleared = false; 292 bool isStillValid = false; 293 auto clearIfDead = [&] (WriteBarrier<UnlinkedFunctionCodeBlock>& unlinkedCodeBlock) { 294 if (!unlinkedCodeBlock) 295 return; 296 if (!vm.heap.isMarked(unlinkedCodeBlock.get())) { 297 unlinkedCodeBlock.clear(); 298 isCleared = true; 299 } else 300 isStillValid = true; 301 }; 302 clearIfDead(m_unlinkedCodeBlockForCall); 303 clearIfDead(m_unlinkedCodeBlockForConstruct); 304 if (isCleared && !isStillValid) 305 vm.unlinkedFunctionExecutableSpace.set.remove(this); 306 } 307 } 308 287 309 } // namespace JSC -
trunk/Source/JavaScriptCore/bytecode/UnlinkedFunctionExecutable.h
r245288 r246272 115 115 void setInvalidTypeProfilingOffsets(); 116 116 117 UnlinkedFunctionCodeBlock* unlinkedCodeBlockFor(CodeSpecializationKind);118 119 117 UnlinkedFunctionCodeBlock* unlinkedCodeBlockFor( 120 118 VM&, const SourceCode&, CodeSpecializationKind, OptionSet<CodeGenerationMode>, … … 190 188 } 191 189 190 void finalizeUnconditionally(VM&); 191 192 192 struct RareData { 193 193 WTF_MAKE_STRUCT_FAST_ALLOCATED; … … 203 203 UnlinkedFunctionExecutable(Decoder&, const CachedFunctionExecutable&); 204 204 205 void decodeCachedCodeBlocks(); 205 void decodeCachedCodeBlocks(VM&); 206 207 bool codeBlockEdgeMayBeWeak() const 208 { 209 // Currently, bytecode cache assumes that the tree of UnlinkedFunctionExecutable and UnlinkedCodeBlock will not be destroyed while the parent is live. 210 // Bytecode cache uses this asumption to avoid duplicate materialization by bookkeeping the heap cells in the offste-to-pointer map. 211 return VM::useUnlinkedCodeBlockJettisoning() && !m_isGeneratedFromCache; 212 } 206 213 207 214 unsigned m_firstLineOffset : 31; … … 229 236 unsigned m_functionMode : 2; // FunctionMode 230 237 unsigned m_derivedContextType: 2; 238 unsigned m_isGeneratedFromCache : 1; 231 239 232 240 union { -
trunk/Source/JavaScriptCore/heap/Heap.cpp
r246073 r246272 607 607 finalizeMarkedUnconditionalFinalizers<ExecutableToCodeBlockEdge>(vm()->executableToCodeBlockEdgesWithFinalizers); 608 608 finalizeMarkedUnconditionalFinalizers<StructureRareData>(vm()->structureRareDataSpace); 609 finalizeMarkedUnconditionalFinalizers<UnlinkedFunctionExecutable>(vm()->unlinkedFunctionExecutableSpace.set); 609 610 if (vm()->m_weakSetSpace) 610 611 finalizeMarkedUnconditionalFinalizers<JSWeakSet>(*vm()->m_weakSetSpace); -
trunk/Source/JavaScriptCore/runtime/CachedTypes.cpp
r246060 r246272 2032 2032 2033 2033 , m_didOptimize(static_cast<unsigned>(MixedTriState)) 2034 , m_age(0) 2034 2035 2035 2036 , m_features(cachedCodeBlock.features()) … … 2159 2160 , m_functionMode(cachedExecutable.functionMode()) 2160 2161 , m_derivedContextType(cachedExecutable.derivedContextType()) 2162 , m_isGeneratedFromCache(true) 2161 2163 , m_unlinkedCodeBlockForCall() 2162 2164 , m_unlinkedCodeBlockForConstruct() -
trunk/Source/JavaScriptCore/runtime/CodeSpecializationKind.h
r206525 r246272 28 28 namespace JSC { 29 29 30 enum CodeSpecializationKind { CodeForCall, CodeForConstruct };30 enum CodeSpecializationKind : uint8_t { CodeForCall, CodeForConstruct }; 31 31 32 32 inline CodeSpecializationKind specializationFromIsCall(bool isCall) -
trunk/Source/JavaScriptCore/runtime/Options.h
r246003 r246272 521 521 v(optionString, dumpJITMemoryPath, nullptr, Restricted, nullptr) \ 522 522 v(double, dumpJITMemoryFlushInterval, 10, Restricted, "Maximum time in between flushes of the JIT memory dump in seconds.") \ 523 v(bool, useUnlinkedCodeBlockJettisoning, false, Normal, "If true, UnlinkedCodeBlock can be jettisoned.") \ 523 524 524 525 -
trunk/Source/JavaScriptCore/runtime/VM.cpp
r246073 r246272 233 233 } 234 234 235 bool VM::isInMiniMode()236 {237 return !canUseJIT() || Options::forceMiniVMMode();238 }239 240 235 inline unsigned VM::nextID() 241 236 { -
trunk/Source/JavaScriptCore/runtime/VM.h
r246073 r246272 633 633 634 634 static JS_EXPORT_PRIVATE bool canUseAssembler(); 635 static JS_EXPORT_PRIVATE bool isInMiniMode(); 635 static bool isInMiniMode() 636 { 637 return !canUseJIT() || Options::forceMiniVMMode(); 638 } 639 640 static bool useUnlinkedCodeBlockJettisoning() 641 { 642 return Options::useUnlinkedCodeBlockJettisoning() || isInMiniMode(); 643 } 636 644 637 645 static void computeCanUseJIT(); -
trunk/Tools/ChangeLog
r246270 r246272 1 2019-06-10 Yusuke Suzuki <ysuzuki@apple.com> 2 3 [JSC] UnlinkedCodeBlock should be eventually jettisoned in VM mini mode 4 https://bugs.webkit.org/show_bug.cgi?id=198023 5 6 Reviewed by Saam Barati. 7 8 * Scripts/run-jsc-stress-tests: 9 1 10 2019-06-10 Timothy Hatcher <timothy@apple.com> 2 11 -
trunk/Tools/Scripts/run-jsc-stress-tests
r245879 r246272 781 781 end 782 782 783 def runMiniMode(*optionalTestSpecificOptions) 784 run("mini-mode", "--forceMiniVMMode=true", *optionalTestSpecificOptions) 785 end 786 783 787 def defaultRun 784 788 if $mode == "quick" … … 787 791 runDefault 788 792 runBytecodeCache 793 runMiniMode 789 794 if $jitTests 790 795 runNoLLInt
Note: See TracChangeset
for help on using the changeset viewer.