ConcurrentDelazification.cpp (9840B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "vm/ConcurrentDelazification.h" 8 9 #include "mozilla/Assertions.h" // MOZ_ASSERT, MOZ_CRASH 10 #include "mozilla/RefPtr.h" // RefPtr 11 #include "mozilla/ReverseIterator.h" // mozilla::Reversed 12 #include "mozilla/ScopeExit.h" // mozilla::MakeScopeExit 13 14 #include <stddef.h> // size_t 15 #include <utility> // std::move, std::pair 16 17 #include "ds/LifoAlloc.h" // LifoAlloc 18 #include "frontend/BytecodeCompiler.h" // DelazifyCanonicalScriptedFunction, DelazifyFailureReason 19 #include "frontend/CompilationStencil.h" // CompilationStencil, ExtensibleCompilationStencil, BorrowingCompilationStencil, ScriptStencilRef 20 #include "frontend/FrontendContext.h" // JS::FrontendContext 21 #include "frontend/ScopeBindingCache.h" // StencilScopeBindingCache 22 #include "frontend/Stencil.h" // TaggedScriptThingIndex, ScriptStencilExtra 23 #include "js/AllocPolicy.h" // ReportOutOfMemory 24 #include "js/experimental/JSStencil.h" // RefPtrTraits<JS::Stencil> 25 #include "vm/JSContext.h" // JSContext 26 27 using namespace js; 28 29 // This is an equivalent of std::swap but this one should work with 30 // ScriptStencilRef constant fields by relying on placement-new trickery to 31 // "create" a new value instead of updating the value of each field. 32 template <typename T> 33 void const_swap(T& a, T& b) { 34 alignas(T) unsigned char buffer[sizeof(T)]; 35 T* temp = new (buffer) T(std::move(a)); 36 a.~T(); 37 new (&a) T(std::move(b)); 38 b.~T(); 39 new (&b) T(std::move(*temp)); 40 temp->~T(); 41 } 42 43 bool DelazifyStrategy::add(FrontendContext* fc, ScriptStencilRef& ref) { 44 using namespace js::frontend; 45 46 // Only functions with bytecode are allowed to be added. 47 MOZ_ASSERT(!ref.scriptDataFromEnclosing().isGhost()); 48 MOZ_ASSERT(ref.context()->scriptData[0].hasSharedData()); 49 50 // Lookup the gc-things range which are referenced by this script. 51 auto gcThingData = ref.gcThingsFromInitial(); 52 53 // Iterate over gc-things of the script and queue inner functions. 54 for (TaggedScriptThingIndex index : mozilla::Reversed(gcThingData)) { 55 if (!index.isFunction()) { 56 continue; 57 } 58 59 ScriptIndex innerIndex = index.toFunction(); 60 ScriptStencilRef innerRef{ref.stencils_, innerIndex}; 61 MOZ_ASSERT(innerRef.enclosingScript().scriptIndex_ == ref.scriptIndex_); 62 63 const ScriptStencil& innerScriptData = innerRef.scriptDataFromEnclosing(); 64 if (innerScriptData.isGhost() || 65 !innerScriptData.functionFlags.isInterpreted() || 66 !innerScriptData.wasEmittedByEnclosingScript()) { 67 continue; 68 } 69 if (innerScriptData.hasSharedData()) { 70 // The function has been parsed as part of its enclosing script, thus we 71 // should visit its inner function the same way. 72 if (!add(fc, innerRef)) { 73 return false; 74 } 75 continue; 76 } 77 78 // Maybe insert the new script index in the queue of functions to delazify. 79 if (!insert(innerRef)) { 80 ReportOutOfMemory(fc); 81 return false; 82 } 83 } 84 85 return true; 86 } 87 88 frontend::ScriptStencilRef LargeFirstDelazification::next() { 89 const_swap(heap.back(), heap[0]); 90 ScriptStencilRef result = heap.popCopy().second; 91 92 // NOTE: These are a heap indexes offseted by 1, such that we can manipulate 93 // the tree of heap-sorted values which bubble up the largest values towards 94 // the root of the tree. 95 size_t len = heap.length(); 96 size_t i = 1; 97 while (true) { 98 // NOTE: We write (n + 1) - 1, instead of n, to explicit that the 99 // manipualted indexes are all offseted by 1. 100 size_t n = 2 * i; 101 size_t largest; 102 if (n + 1 <= len && heap[(n + 1) - 1].first > heap[n - 1].first) { 103 largest = n + 1; 104 } else if (n <= len) { 105 // The condition is n <= len in case n + 1 is out of the heap vector, but 106 // not n, in which case we still want to check if the last element of the 107 // heap vector should be swapped. Otherwise heap[n - 1] represents a 108 // larger function than heap[(n + 1) - 1]. 109 largest = n; 110 } else { 111 // n is out-side the heap vector, thus our element is already in a leaf 112 // position and would not be moved any more. 113 break; 114 } 115 116 if (heap[i - 1].first < heap[largest - 1].first) { 117 // We found a function which has a larger body as a child of the current 118 // element. we swap it with the current element, such that the largest 119 // element is closer to the root of the tree. 120 const_swap(heap[i - 1], heap[largest - 1]); 121 i = largest; 122 } else { 123 // The largest function found as a child of the current node is smaller 124 // than the current node's function size. The heap tree is now organized 125 // as expected. 126 break; 127 } 128 } 129 130 return result; 131 } 132 133 bool LargeFirstDelazification::insert(frontend::ScriptStencilRef& ref) { 134 const frontend::ScriptStencilExtra& extra = ref.scriptExtra(); 135 SourceSize size = extra.extent.sourceEnd - extra.extent.sourceStart; 136 if (!heap.append(std::pair(size, ref))) { 137 return false; 138 } 139 140 // NOTE: These are a heap indexes offseted by 1, such that we can manipulate 141 // the tree of heap-sorted values which bubble up the largest values towards 142 // the root of the tree. 143 size_t i = heap.length(); 144 while (i > 1) { 145 if (heap[i - 1].first <= heap[(i / 2) - 1].first) { 146 return true; 147 } 148 149 const_swap(heap[i - 1], heap[(i / 2) - 1]); 150 i /= 2; 151 } 152 153 return true; 154 } 155 156 bool DelazificationContext::init( 157 const JS::ReadOnlyCompileOptions& options, 158 frontend::InitialStencilAndDelazifications* stencils) { 159 using namespace js::frontend; 160 161 stencils_ = stencils; 162 163 if (!fc_.allocateOwnedPool()) { 164 return false; 165 } 166 167 // Initialize the relative indexes which are necessary for walking 168 // delazification stencils from the CompilationInput. 169 auto indexesGuard = stencils->ensureRelativeIndexes(&fc_); 170 if (!indexesGuard) { 171 return false; 172 } 173 indexesGuard_.emplace(std::move(indexesGuard)); 174 175 switch (options.eagerDelazificationStrategy()) { 176 case JS::DelazificationOption::OnDemandOnly: 177 // OnDemandOnly will parse function as they are require to continue the 178 // execution on the main thread. 179 MOZ_CRASH("OnDemandOnly should not create a DelazificationContext."); 180 break; 181 case JS::DelazificationOption::CheckConcurrentWithOnDemand: 182 case JS::DelazificationOption::ConcurrentDepthFirst: 183 // ConcurrentDepthFirst visit all functions to be delazified, visiting the 184 // inner functions before the siblings functions. 185 strategy_ = fc_.getAllocator()->make_unique<DepthFirstDelazification>(); 186 break; 187 case JS::DelazificationOption::ConcurrentLargeFirst: 188 // ConcurrentLargeFirst visit all functions to be delazified, visiting the 189 // largest function first. 190 strategy_ = fc_.getAllocator()->make_unique<LargeFirstDelazification>(); 191 break; 192 case JS::DelazificationOption::ParseEverythingEagerly: 193 // ParseEverythingEagerly parse all functions eagerly, thus leaving no 194 // functions to be parsed on demand. 195 MOZ_CRASH( 196 "ParseEverythingEagerly should not create a DelazificationContext"); 197 break; 198 } 199 200 if (!strategy_) { 201 return false; 202 } 203 204 // Queue functions from the top-level to be delazify. 205 ScriptStencilRef topLevel{*stencils_, ScriptIndex{0}}; 206 return strategy_->add(&fc_, topLevel); 207 } 208 209 bool DelazificationContext::delazify() { 210 fc_.setStackQuota(stackQuota_); 211 auto purgePool = 212 mozilla::MakeScopeExit([&] { fc_.nameCollectionPool().purge(); }); 213 214 using namespace js::frontend; 215 216 // Create a scope-binding cache dedicated to this delazification. The memory 217 // would be reclaimed when interrupted or if all delazification are completed. 218 // 219 // We do not use the one from the JSContext/Runtime, as it is not thread safe 220 // to use it, as it could be purged by a GC in the mean time. 221 StencilScopeBindingCache scopeCache(*stencils_); 222 223 LifoAlloc tempLifoAlloc(JSContext::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE, 224 js::BackgroundMallocArena); 225 226 while (!strategy_->done()) { 227 if (isInterrupted_) { 228 isInterrupted_ = false; 229 break; 230 } 231 const CompilationStencil* innerStencil = nullptr; 232 ScriptStencilRef scriptRef = strategy_->next(); 233 { 234 // Parse and generate bytecode for the inner function and save it on the 235 // InitialStencilAndDelazifications object. If the function had already 236 // been parsed, then just get the result back from the stencil. 237 DelazifyFailureReason failureReason; 238 innerStencil = DelazifyCanonicalScriptedFunction( 239 &fc_, tempLifoAlloc, initialPrefableOptions_, &scopeCache, 240 scriptRef.scriptIndex_, stencils_.get(), &failureReason); 241 if (!innerStencil) { 242 if (failureReason == DelazifyFailureReason::Compressed) { 243 // The script source is already compressed, and delazification cannot 244 // be performed without decompressing. 245 // There is no reason to keep our eager delazification going. 246 strategy_->clear(); 247 return true; 248 } 249 250 strategy_->clear(); 251 return false; 252 } 253 } 254 255 if (!strategy_->add(&fc_, scriptRef)) { 256 strategy_->clear(); 257 return false; 258 } 259 } 260 261 return true; 262 } 263 264 bool DelazificationContext::done() const { 265 if (!strategy_) { 266 return true; 267 } 268 269 return strategy_->done(); 270 } 271 272 size_t DelazificationContext::sizeOfExcludingThis( 273 mozilla::MallocSizeOf mallocSizeOf) const { 274 size_t size = stencils_->sizeOfIncludingThis(mallocSizeOf); 275 return size; 276 }