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 "js/OffThreadScriptCompilation.h"
8 
9 #include "mozilla/Assertions.h"  // MOZ_ASSERT
10 #include "mozilla/Range.h"       // mozilla::Range
11 #include "mozilla/Utf8.h"        // mozilla::Utf8Unit
12 #include "mozilla/Vector.h"      // mozilla::Vector
13 
14 #include <stddef.h>  // size_t
15 
16 #include "jspubtd.h"  // js::CurrentThreadCanAccessRuntime
17 #include "jstypes.h"  // JS_PUBLIC_API
18 
19 #include "js/CompileOptions.h"  // JS::ReadOnlyCompileOptions
20 #include "js/experimental/JSStencil.h"  // JS::CompileToStencilOffThread, JS::FinishOffThreadCompileToStencil
21 #include "js/SourceText.h"  // JS::SourceText
22 #include "vm/HelperThreadState.h"  // js::OffThreadParsingMustWaitForGC, js::StartOffThreadParseScript
23 #include "vm/JSContext.h"  // JSContext
24 #include "vm/Runtime.h"    // js::CanUseExtraThreads
25 
26 using namespace js;
27 
28 using mozilla::Utf8Unit;
29 
30 using JS::ReadOnlyCompileOptions;
31 
32 enum class OffThread { Compile, Decode };
33 
CanDoOffThread(JSContext * cx,const ReadOnlyCompileOptions & options,size_t length,OffThread what)34 static bool CanDoOffThread(JSContext* cx, const ReadOnlyCompileOptions& options,
35                            size_t length, OffThread what) {
36   static const size_t TINY_LENGTH = 5 * 1000;
37   static const size_t HUGE_SRC_LENGTH = 100 * 1000;
38   static const size_t HUGE_BC_LENGTH = 367 * 1000;
39 
40   // These are heuristics which the caller may choose to ignore (e.g., for
41   // testing purposes).
42   if (!options.forceAsync) {
43     // Compiling off the main thread inolves creating a new Zone and other
44     // significant overheads.  Don't bother if the script is tiny.
45     if (length < TINY_LENGTH) {
46       return false;
47     }
48 
49     // If the parsing task would have to wait for GC to complete, it'll probably
50     // be faster to just start it synchronously on the main thread unless the
51     // script is huge.
52     //
53     // NOTE: JS::DecodeMultiOffThreadScript does not use this API so we don't
54     // have to worry about it still using off-thread parse global.
55     bool mustWait = options.useOffThreadParseGlobal &&
56                     OffThreadParsingMustWaitForGC(cx->runtime());
57     if (mustWait) {
58       if (what == OffThread::Compile && length < HUGE_SRC_LENGTH) {
59         return false;
60       }
61       if (what == OffThread::Decode && length < HUGE_BC_LENGTH) {
62         return false;
63       }
64     }
65   }
66 
67   return cx->runtime()->canUseParallelParsing() && CanUseExtraThreads();
68 }
69 
CanCompileOffThread(JSContext * cx,const ReadOnlyCompileOptions & options,size_t length)70 JS_PUBLIC_API bool JS::CanCompileOffThread(
71     JSContext* cx, const ReadOnlyCompileOptions& options, size_t length) {
72   return CanDoOffThread(cx, options, length, OffThread::Compile);
73 }
74 
CompileOffThread(JSContext * cx,const ReadOnlyCompileOptions & options,JS::SourceText<char16_t> & srcBuf,OffThreadCompileCallback callback,void * callbackData)75 JS_PUBLIC_API JS::OffThreadToken* JS::CompileOffThread(
76     JSContext* cx, const ReadOnlyCompileOptions& options,
77     JS::SourceText<char16_t>& srcBuf, OffThreadCompileCallback callback,
78     void* callbackData) {
79   MOZ_ASSERT(CanCompileOffThread(cx, options, srcBuf.length()));
80   return StartOffThreadParseScript(cx, options, srcBuf, callback, callbackData);
81 }
82 
CompileOffThread(JSContext * cx,const ReadOnlyCompileOptions & options,JS::SourceText<Utf8Unit> & srcBuf,OffThreadCompileCallback callback,void * callbackData)83 JS_PUBLIC_API JS::OffThreadToken* JS::CompileOffThread(
84     JSContext* cx, const ReadOnlyCompileOptions& options,
85     JS::SourceText<Utf8Unit>& srcBuf, OffThreadCompileCallback callback,
86     void* callbackData) {
87   MOZ_ASSERT(CanCompileOffThread(cx, options, srcBuf.length()));
88   return StartOffThreadParseScript(cx, options, srcBuf, callback, callbackData);
89 }
90 
FinishOffThreadScript(JSContext * cx,JS::OffThreadToken * token)91 JS_PUBLIC_API JSScript* JS::FinishOffThreadScript(JSContext* cx,
92                                                   JS::OffThreadToken* token) {
93   MOZ_ASSERT(cx);
94   MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
95   return HelperThreadState().finishScriptParseTask(cx, token);
96 }
97 
CompileToStencilOffThread(JSContext * cx,const ReadOnlyCompileOptions & options,JS::SourceText<char16_t> & srcBuf,OffThreadCompileCallback callback,void * callbackData)98 JS_PUBLIC_API JS::OffThreadToken* JS::CompileToStencilOffThread(
99     JSContext* cx, const ReadOnlyCompileOptions& options,
100     JS::SourceText<char16_t>& srcBuf, OffThreadCompileCallback callback,
101     void* callbackData) {
102   MOZ_ASSERT(CanCompileOffThread(cx, options, srcBuf.length()));
103   return StartOffThreadCompileToStencil(cx, options, srcBuf, callback,
104                                         callbackData);
105 }
106 
CompileToStencilOffThread(JSContext * cx,const ReadOnlyCompileOptions & options,JS::SourceText<Utf8Unit> & srcBuf,OffThreadCompileCallback callback,void * callbackData)107 JS_PUBLIC_API JS::OffThreadToken* JS::CompileToStencilOffThread(
108     JSContext* cx, const ReadOnlyCompileOptions& options,
109     JS::SourceText<Utf8Unit>& srcBuf, OffThreadCompileCallback callback,
110     void* callbackData) {
111   MOZ_ASSERT(CanCompileOffThread(cx, options, srcBuf.length()));
112   return StartOffThreadCompileToStencil(cx, options, srcBuf, callback,
113                                         callbackData);
114 }
115 
FinishOffThreadCompileToStencil(JSContext * cx,JS::OffThreadToken * token)116 JS_PUBLIC_API RefPtr<JS::Stencil> JS::FinishOffThreadCompileToStencil(
117     JSContext* cx, JS::OffThreadToken* token) {
118   MOZ_ASSERT(cx);
119   MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
120   auto stencil = HelperThreadState().finishCompileToStencilTask(cx, token);
121   return do_AddRef(stencil.release());
122 }
123 
FinishOffThreadScriptAndStartIncrementalEncoding(JSContext * cx,JS::OffThreadToken * token)124 JS_PUBLIC_API JSScript* JS::FinishOffThreadScriptAndStartIncrementalEncoding(
125     JSContext* cx, JS::OffThreadToken* token) {
126   MOZ_ASSERT(cx);
127   MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
128   return HelperThreadState().finishScriptParseTask(cx, token,
129                                                    StartEncoding::Yes);
130 }
131 
CancelOffThreadScript(JSContext * cx,JS::OffThreadToken * token)132 JS_PUBLIC_API void JS::CancelOffThreadScript(JSContext* cx,
133                                              JS::OffThreadToken* token) {
134   MOZ_ASSERT(cx);
135   MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
136   HelperThreadState().cancelParseTask(cx->runtime(), ParseTaskKind::Script,
137                                       token);
138 }
139 
CancelOffThreadCompileToStencil(JSContext * cx,JS::OffThreadToken * token)140 JS_PUBLIC_API void JS::CancelOffThreadCompileToStencil(
141     JSContext* cx, JS::OffThreadToken* token) {
142   MOZ_ASSERT(cx);
143   MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
144   HelperThreadState().cancelParseTask(cx->runtime(),
145                                       ParseTaskKind::ScriptStencil, token);
146 }
147 
CompileOffThreadModule(JSContext * cx,const ReadOnlyCompileOptions & options,JS::SourceText<char16_t> & srcBuf,OffThreadCompileCallback callback,void * callbackData)148 JS_PUBLIC_API JS::OffThreadToken* JS::CompileOffThreadModule(
149     JSContext* cx, const ReadOnlyCompileOptions& options,
150     JS::SourceText<char16_t>& srcBuf, OffThreadCompileCallback callback,
151     void* callbackData) {
152   MOZ_ASSERT(CanCompileOffThread(cx, options, srcBuf.length()));
153   return StartOffThreadParseModule(cx, options, srcBuf, callback, callbackData);
154 }
155 
CompileOffThreadModule(JSContext * cx,const ReadOnlyCompileOptions & options,JS::SourceText<Utf8Unit> & srcBuf,OffThreadCompileCallback callback,void * callbackData)156 JS_PUBLIC_API JS::OffThreadToken* JS::CompileOffThreadModule(
157     JSContext* cx, const ReadOnlyCompileOptions& options,
158     JS::SourceText<Utf8Unit>& srcBuf, OffThreadCompileCallback callback,
159     void* callbackData) {
160   MOZ_ASSERT(CanCompileOffThread(cx, options, srcBuf.length()));
161   return StartOffThreadParseModule(cx, options, srcBuf, callback, callbackData);
162 }
163 
FinishOffThreadModule(JSContext * cx,JS::OffThreadToken * token)164 JS_PUBLIC_API JSObject* JS::FinishOffThreadModule(JSContext* cx,
165                                                   JS::OffThreadToken* token) {
166   MOZ_ASSERT(cx);
167   MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
168   return HelperThreadState().finishModuleParseTask(cx, token);
169 }
170 
CancelOffThreadModule(JSContext * cx,JS::OffThreadToken * token)171 JS_PUBLIC_API void JS::CancelOffThreadModule(JSContext* cx,
172                                              JS::OffThreadToken* token) {
173   MOZ_ASSERT(cx);
174   MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
175   HelperThreadState().cancelParseTask(cx->runtime(), ParseTaskKind::Module,
176                                       token);
177 }
178 
CanDecodeOffThread(JSContext * cx,const ReadOnlyCompileOptions & options,size_t length)179 JS_PUBLIC_API bool JS::CanDecodeOffThread(JSContext* cx,
180                                           const ReadOnlyCompileOptions& options,
181                                           size_t length) {
182   return CanDoOffThread(cx, options, length, OffThread::Decode);
183 }
184 
DecodeOffThreadScript(JSContext * cx,const ReadOnlyCompileOptions & options,mozilla::Vector<uint8_t> & buffer,size_t cursor,OffThreadCompileCallback callback,void * callbackData)185 JS_PUBLIC_API JS::OffThreadToken* JS::DecodeOffThreadScript(
186     JSContext* cx, const ReadOnlyCompileOptions& options,
187     mozilla::Vector<uint8_t>& buffer /* TranscodeBuffer& */, size_t cursor,
188     OffThreadCompileCallback callback, void* callbackData) {
189   JS::TranscodeRange range(buffer.begin() + cursor, buffer.length() - cursor);
190   MOZ_ASSERT(CanDecodeOffThread(cx, options, range.length()));
191   return StartOffThreadDecodeScript(cx, options, range, callback, callbackData);
192 }
193 
DecodeOffThreadScript(JSContext * cx,const ReadOnlyCompileOptions & options,const mozilla::Range<uint8_t> & range,OffThreadCompileCallback callback,void * callbackData)194 JS_PUBLIC_API JS::OffThreadToken* JS::DecodeOffThreadScript(
195     JSContext* cx, const ReadOnlyCompileOptions& options,
196     const mozilla::Range<uint8_t>& range /* TranscodeRange& */,
197     OffThreadCompileCallback callback, void* callbackData) {
198   MOZ_ASSERT(CanDecodeOffThread(cx, options, range.length()));
199   return StartOffThreadDecodeScript(cx, options, range, callback, callbackData);
200 }
201 
FinishOffThreadScriptDecoder(JSContext * cx,JS::OffThreadToken * token)202 JS_PUBLIC_API JSScript* JS::FinishOffThreadScriptDecoder(
203     JSContext* cx, JS::OffThreadToken* token) {
204   MOZ_ASSERT(cx);
205   MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
206   return HelperThreadState().finishScriptDecodeTask(cx, token);
207 }
208 
CancelOffThreadScriptDecoder(JSContext * cx,JS::OffThreadToken * token)209 JS_PUBLIC_API void JS::CancelOffThreadScriptDecoder(JSContext* cx,
210                                                     JS::OffThreadToken* token) {
211   MOZ_ASSERT(cx);
212   MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
213   HelperThreadState().cancelParseTask(cx->runtime(),
214                                       ParseTaskKind::ScriptDecode, token);
215 }
216 
DecodeMultiOffThreadScripts(JSContext * cx,const ReadOnlyCompileOptions & options,TranscodeSources & sources,OffThreadCompileCallback callback,void * callbackData)217 JS_PUBLIC_API JS::OffThreadToken* JS::DecodeMultiOffThreadScripts(
218     JSContext* cx, const ReadOnlyCompileOptions& options,
219     TranscodeSources& sources, OffThreadCompileCallback callback,
220     void* callbackData) {
221 #ifdef DEBUG
222   size_t length = 0;
223   for (auto& source : sources) {
224     length += source.range.length();
225   }
226   MOZ_ASSERT(CanCompileOffThread(cx, options, length));
227 #endif
228   return StartOffThreadDecodeMultiScripts(cx, options, sources, callback,
229                                           callbackData);
230 }
231 
FinishMultiOffThreadScriptsDecoder(JSContext * cx,JS::OffThreadToken * token,MutableHandle<ScriptVector> scripts)232 JS_PUBLIC_API bool JS::FinishMultiOffThreadScriptsDecoder(
233     JSContext* cx, JS::OffThreadToken* token,
234     MutableHandle<ScriptVector> scripts) {
235   MOZ_ASSERT(cx);
236   MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
237   return HelperThreadState().finishMultiScriptsDecodeTask(cx, token, scripts);
238 }
239 
CancelMultiOffThreadScriptsDecoder(JSContext * cx,JS::OffThreadToken * token)240 JS_PUBLIC_API void JS::CancelMultiOffThreadScriptsDecoder(
241     JSContext* cx, JS::OffThreadToken* token) {
242   MOZ_ASSERT(cx);
243   MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
244   HelperThreadState().cancelParseTask(cx->runtime(),
245                                       ParseTaskKind::MultiScriptsDecode, token);
246 }
247 
248 namespace js {
249 bool gUseOffThreadParseGlobal = false;
250 }  // namespace js
251 
SetUseOffThreadParseGlobal(bool value)252 JS_PUBLIC_API void JS::SetUseOffThreadParseGlobal(bool value) {
253   gUseOffThreadParseGlobal = value;
254 }
255 
UseOffThreadParseGlobal()256 bool js::UseOffThreadParseGlobal() { return gUseOffThreadParseGlobal; }
257