Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / js / src / vm / ConcurrentDelazification.cpp
blob3e146708569a5c76bd93ca2ae90ab8cde28eb5d1
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/. */
7 #include "vm/ConcurrentDelazification.h"
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
14 #include <stddef.h> // size_t
15 #include <utility> // std::swap, std::move, std::pair
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 #include "vm/StencilCache.h" // DelazificationCache
28 using namespace js;
30 bool DelazifyStrategy::add(FrontendContext* fc,
31 const frontend::CompilationStencil& stencil,
32 ScriptIndex index) {
33 using namespace js::frontend;
34 ScriptStencilRef scriptRef{stencil, index};
36 // Only functions with bytecode are allowed to be added.
37 MOZ_ASSERT(!scriptRef.scriptData().isGhost());
38 MOZ_ASSERT(scriptRef.scriptData().hasSharedData());
40 // Lookup the gc-things range which are referenced by this script.
41 size_t offset = scriptRef.scriptData().gcThingsOffset.index;
42 size_t length = scriptRef.scriptData().gcThingsLength;
43 auto gcThingData = stencil.gcThingData.Subspan(offset, length);
45 // Iterate over gc-things of the script and queue inner functions.
46 for (TaggedScriptThingIndex index : mozilla::Reversed(gcThingData)) {
47 if (!index.isFunction()) {
48 continue;
51 ScriptIndex innerScriptIndex = index.toFunction();
52 ScriptStencilRef innerScriptRef{stencil, innerScriptIndex};
53 if (innerScriptRef.scriptData().isGhost() ||
54 !innerScriptRef.scriptData().functionFlags.isInterpreted()) {
55 continue;
57 if (innerScriptRef.scriptData().hasSharedData()) {
58 // The top-level parse decided to eagerly parse this function, thus we
59 // should visit its inner function the same way.
60 if (!add(fc, stencil, innerScriptIndex)) {
61 return false;
63 continue;
66 // Maybe insert the new script index in the queue of functions to delazify.
67 if (!insert(innerScriptIndex, innerScriptRef)) {
68 ReportOutOfMemory(fc);
69 return false;
73 return true;
76 DelazifyStrategy::ScriptIndex LargeFirstDelazification::next() {
77 std::swap(heap.back(), heap[0]);
78 ScriptIndex result = heap.popCopy().second;
80 // NOTE: These are a heap indexes offseted by 1, such that we can manipulate
81 // the tree of heap-sorted values which bubble up the largest values towards
82 // the root of the tree.
83 size_t len = heap.length();
84 size_t i = 1;
85 while (true) {
86 // NOTE: We write (n + 1) - 1, instead of n, to explicit that the
87 // manipualted indexes are all offseted by 1.
88 size_t n = 2 * i;
89 size_t largest;
90 if (n + 1 <= len && heap[(n + 1) - 1].first > heap[n - 1].first) {
91 largest = n + 1;
92 } else if (n <= len) {
93 // The condition is n <= len in case n + 1 is out of the heap vector, but
94 // not n, in which case we still want to check if the last element of the
95 // heap vector should be swapped. Otherwise heap[n - 1] represents a
96 // larger function than heap[(n + 1) - 1].
97 largest = n;
98 } else {
99 // n is out-side the heap vector, thus our element is already in a leaf
100 // position and would not be moved any more.
101 break;
104 if (heap[i - 1].first < heap[largest - 1].first) {
105 // We found a function which has a larger body as a child of the current
106 // element. we swap it with the current element, such that the largest
107 // element is closer to the root of the tree.
108 std::swap(heap[i - 1], heap[largest - 1]);
109 i = largest;
110 } else {
111 // The largest function found as a child of the current node is smaller
112 // than the current node's function size. The heap tree is now organized
113 // as expected.
114 break;
118 return result;
121 bool LargeFirstDelazification::insert(ScriptIndex index,
122 frontend::ScriptStencilRef& ref) {
123 const frontend::ScriptStencilExtra& extra = ref.scriptExtra();
124 SourceSize size = extra.extent.sourceEnd - extra.extent.sourceStart;
125 if (!heap.append(std::pair(size, index))) {
126 return false;
129 // NOTE: These are a heap indexes offseted by 1, such that we can manipulate
130 // the tree of heap-sorted values which bubble up the largest values towards
131 // the root of the tree.
132 size_t i = heap.length();
133 while (i > 1) {
134 if (heap[i - 1].first <= heap[(i / 2) - 1].first) {
135 return true;
138 std::swap(heap[i - 1], heap[(i / 2) - 1]);
139 i /= 2;
142 return true;
145 bool DelazificationContext::init(const JS::ReadOnlyCompileOptions& options,
146 const frontend::CompilationStencil& stencil) {
147 using namespace js::frontend;
149 RefPtr<ScriptSource> source(stencil.source);
150 DelazificationCache& cache = DelazificationCache::getSingleton();
151 if (!cache.startCaching(std::move(source))) {
152 return false;
155 auto initial = fc_.getAllocator()->make_unique<ExtensibleCompilationStencil>(
156 options, stencil.source);
157 if (!initial || !initial->cloneFrom(&fc_, stencil)) {
158 return false;
161 if (!fc_.allocateOwnedPool()) {
162 return false;
165 if (!merger_.setInitial(&fc_, std::move(initial))) {
166 return false;
169 switch (options.eagerDelazificationStrategy()) {
170 case JS::DelazificationOption::OnDemandOnly:
171 // OnDemandOnly will parse function as they are require to continue the
172 // execution on the main thread.
173 MOZ_CRASH("OnDemandOnly should not create a DelazificationContext.");
174 break;
175 case JS::DelazificationOption::CheckConcurrentWithOnDemand:
176 case JS::DelazificationOption::ConcurrentDepthFirst:
177 // ConcurrentDepthFirst visit all functions to be delazified, visiting the
178 // inner functions before the siblings functions.
179 strategy_ = fc_.getAllocator()->make_unique<DepthFirstDelazification>();
180 break;
181 case JS::DelazificationOption::ConcurrentLargeFirst:
182 // ConcurrentLargeFirst visit all functions to be delazified, visiting the
183 // largest function first.
184 strategy_ = fc_.getAllocator()->make_unique<LargeFirstDelazification>();
185 break;
186 case JS::DelazificationOption::ParseEverythingEagerly:
187 // ParseEverythingEagerly parse all functions eagerly, thus leaving no
188 // functions to be parsed on demand.
189 MOZ_CRASH(
190 "ParseEverythingEagerly should not create a DelazificationContext");
191 break;
194 if (!strategy_) {
195 return false;
198 // Queue functions from the top-level to be delazify.
199 BorrowingCompilationStencil borrow(merger_.getResult());
200 ScriptIndex topLevel{0};
201 return strategy_->add(&fc_, borrow, topLevel);
204 bool DelazificationContext::delazify() {
205 fc_.setStackQuota(stackQuota_);
206 auto purgePool =
207 mozilla::MakeScopeExit([&] { fc_.nameCollectionPool().purge(); });
209 using namespace js::frontend;
211 // Create a scope-binding cache dedicated to this delazification. The memory
212 // would be reclaimed when interrupted or if all delazification are completed.
214 // We do not use the one from the JSContext/Runtime, as it is not thread safe
215 // to use it, as it could be purged by a GC in the mean time.
216 StencilScopeBindingCache scopeCache(merger_);
218 LifoAlloc tempLifoAlloc(JSContext::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE);
220 while (!strategy_->done()) {
221 if (isInterrupted_) {
222 isInterrupted_ = false;
223 break;
225 RefPtr<CompilationStencil> innerStencil;
226 ScriptIndex scriptIndex = strategy_->next();
228 BorrowingCompilationStencil borrow(merger_.getResult());
230 // Take the next inner function to be delazified.
231 ScriptStencilRef scriptRef{borrow, scriptIndex};
232 MOZ_ASSERT(!scriptRef.scriptData().isGhost());
233 MOZ_ASSERT(!scriptRef.scriptData().hasSharedData());
235 // Parse and generate bytecode for the inner function.
236 DelazifyFailureReason failureReason;
237 innerStencil = DelazifyCanonicalScriptedFunction(
238 &fc_, tempLifoAlloc, initialPrefableOptions_, &scopeCache, borrow,
239 scriptIndex, &failureReason);
240 if (!innerStencil) {
241 if (failureReason == DelazifyFailureReason::Compressed) {
242 // The script source is already compressed, and delazification cannot
243 // be performed without decompressing.
244 // There is no reason to keep our eager delazification going.
245 strategy_->clear();
246 return true;
249 strategy_->clear();
250 return false;
253 // Add the generated stencil to the cache, to be consumed by the main
254 // thread.
255 DelazificationCache& cache = DelazificationCache::getSingleton();
256 StencilContext key(borrow.source, scriptRef.scriptExtra().extent);
257 if (auto guard = cache.isSourceCached(borrow.source)) {
258 if (!cache.putNew(guard, key, innerStencil.get())) {
259 ReportOutOfMemory(&fc_);
260 strategy_->clear();
261 return false;
263 } else {
264 // Stencils for this source are no longer accepted in the cache, thus
265 // there is no reason to keep our eager delazification going.
266 strategy_->clear();
267 return true;
271 // We are merging the delazification now, while this could be post-poned
272 // until we have to look at inner functions, this is simpler to do it now
273 // than querying the cache for every enclosing script.
274 if (!merger_.addDelazification(&fc_, *innerStencil)) {
275 strategy_->clear();
276 return false;
280 BorrowingCompilationStencil borrow(merger_.getResult());
281 if (!strategy_->add(&fc_, borrow, scriptIndex)) {
282 strategy_->clear();
283 return false;
288 return true;
291 bool DelazificationContext::done() const {
292 if (!strategy_) {
293 return true;
296 return strategy_->done();
299 size_t DelazificationContext::sizeOfExcludingThis(
300 mozilla::MallocSizeOf mallocSizeOf) const {
301 size_t mergerSize = merger_.getResult().sizeOfIncludingThis(mallocSizeOf);
302 return mergerSize;