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
30 bool DelazifyStrategy::add(FrontendContext
* fc
,
31 const frontend::CompilationStencil
& stencil
,
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()) {
51 ScriptIndex innerScriptIndex
= index
.toFunction();
52 ScriptStencilRef innerScriptRef
{stencil
, innerScriptIndex
};
53 if (innerScriptRef
.scriptData().isGhost() ||
54 !innerScriptRef
.scriptData().functionFlags
.isInterpreted()) {
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
)) {
66 // Maybe insert the new script index in the queue of functions to delazify.
67 if (!insert(innerScriptIndex
, innerScriptRef
)) {
68 ReportOutOfMemory(fc
);
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();
86 // NOTE: We write (n + 1) - 1, instead of n, to explicit that the
87 // manipualted indexes are all offseted by 1.
90 if (n
+ 1 <= len
&& heap
[(n
+ 1) - 1].first
> heap
[n
- 1].first
) {
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].
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.
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]);
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
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
))) {
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();
134 if (heap
[i
- 1].first
<= heap
[(i
/ 2) - 1].first
) {
138 std::swap(heap
[i
- 1], heap
[(i
/ 2) - 1]);
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
))) {
155 auto initial
= fc_
.getAllocator()->make_unique
<ExtensibleCompilationStencil
>(
156 options
, stencil
.source
);
157 if (!initial
|| !initial
->cloneFrom(&fc_
, stencil
)) {
161 if (!fc_
.allocateOwnedPool()) {
165 if (!merger_
.setInitial(&fc_
, std::move(initial
))) {
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.");
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
>();
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
>();
186 case JS::DelazificationOption::ParseEverythingEagerly
:
187 // ParseEverythingEagerly parse all functions eagerly, thus leaving no
188 // functions to be parsed on demand.
190 "ParseEverythingEagerly should not create a DelazificationContext");
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_
);
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;
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
);
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.
253 // Add the generated stencil to the cache, to be consumed by the main
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_
);
264 // Stencils for this source are no longer accepted in the cache, thus
265 // there is no reason to keep our eager delazification going.
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
)) {
280 BorrowingCompilationStencil
borrow(merger_
.getResult());
281 if (!strategy_
->add(&fc_
, borrow
, scriptIndex
)) {
291 bool DelazificationContext::done() const {
296 return strategy_
->done();
299 size_t DelazificationContext::sizeOfExcludingThis(
300 mozilla::MallocSizeOf mallocSizeOf
) const {
301 size_t mergerSize
= merger_
.getResult().sizeOfIncludingThis(mallocSizeOf
);