Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #ifndef ScriptPreloader_h
7 : #define ScriptPreloader_h
8 :
9 : #include "mozilla/CheckedInt.h"
10 : #include "mozilla/EnumSet.h"
11 : #include "mozilla/LinkedList.h"
12 : #include "mozilla/MemoryReporting.h"
13 : #include "mozilla/Maybe.h"
14 : #include "mozilla/MaybeOneOf.h"
15 : #include "mozilla/Monitor.h"
16 : #include "mozilla/Range.h"
17 : #include "mozilla/Vector.h"
18 : #include "mozilla/Result.h"
19 : #include "mozilla/loader/AutoMemMap.h"
20 : #include "nsClassHashtable.h"
21 : #include "nsIFile.h"
22 : #include "nsIMemoryReporter.h"
23 : #include "nsIObserver.h"
24 : #include "nsIThread.h"
25 :
26 : #include "jsapi.h"
27 : #include "js/GCAnnotations.h"
28 :
29 : #include <prio.h>
30 :
31 : namespace mozilla {
32 : namespace dom {
33 : class ContentParent;
34 : }
35 : namespace ipc {
36 : class FileDescriptor;
37 : }
38 : namespace loader {
39 : class InputBuffer;
40 : class ScriptCacheChild;
41 :
42 : enum class ProcessType : uint8_t {
43 : Parent,
44 : Web,
45 : Extension,
46 : };
47 :
48 : template <typename T>
49 1 : struct Matcher
50 : {
51 : virtual bool Matches(T) = 0;
52 : };
53 : }
54 :
55 : using namespace mozilla::loader;
56 :
57 : class ScriptPreloader : public nsIObserver
58 : , public nsIMemoryReporter
59 : , public nsIRunnable
60 : {
61 0 : MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
62 :
63 : friend class mozilla::loader::ScriptCacheChild;
64 :
65 : public:
66 : NS_DECL_THREADSAFE_ISUPPORTS
67 : NS_DECL_NSIOBSERVER
68 : NS_DECL_NSIMEMORYREPORTER
69 : NS_DECL_NSIRUNNABLE
70 :
71 : static ScriptPreloader& GetSingleton();
72 : static ScriptPreloader& GetChildSingleton();
73 :
74 : static ProcessType GetChildProcessType(const nsAString& remoteType);
75 :
76 : // Retrieves the script with the given cache key from the script cache.
77 : // Returns null if the script is not cached.
78 : JSScript* GetCachedScript(JSContext* cx, const nsCString& name);
79 :
80 : // Notes the execution of a script with the given URL and cache key.
81 : // Depending on the stage of startup, the script may be serialized and
82 : // stored to the startup script cache.
83 : void NoteScript(const nsCString& url, const nsCString& cachePath, JS::HandleScript script);
84 :
85 : void NoteScript(const nsCString& url, const nsCString& cachePath,
86 : ProcessType processType, nsTArray<uint8_t>&& xdrData,
87 : TimeStamp loadTime);
88 :
89 : // Initializes the script cache from the startup script cache file.
90 : Result<Ok, nsresult> InitCache(const nsAString& = NS_LITERAL_STRING("scriptCache"));
91 :
92 : Result<Ok, nsresult> InitCache(const Maybe<ipc::FileDescriptor>& cacheFile, ScriptCacheChild* cacheChild);
93 :
94 : private:
95 : Result<Ok, nsresult> InitCacheInternal();
96 :
97 : public:
98 : void Trace(JSTracer* trc);
99 :
100 462 : static ProcessType CurrentProcessType()
101 : {
102 462 : return sProcessType;
103 : }
104 :
105 : static void InitContentChild(dom::ContentParent& parent);
106 :
107 : protected:
108 0 : virtual ~ScriptPreloader() = default;
109 :
110 : private:
111 : enum class ScriptStatus {
112 : Restored,
113 : Saved,
114 : };
115 :
116 : // Represents a cached JS script, either initially read from the script
117 : // cache file, to be added to the next session's script cache file, or
118 : // both.
119 : //
120 : // A script which was read from the cache file may be in any of the
121 : // following states:
122 : //
123 : // - Read from the cache, and being compiled off thread. In this case,
124 : // mReadyToExecute is false, and mToken is null.
125 : // - Off-thread compilation has finished, but the script has not yet been
126 : // executed. In this case, mReadyToExecute is true, and mToken has a non-null
127 : // value.
128 : // - Read from the cache, but too small or needed to immediately to be
129 : // compiled off-thread. In this case, mReadyToExecute is true, and both mToken
130 : // and mScript are null.
131 : // - Fully decoded, and ready to be added to the next session's cache
132 : // file. In this case, mReadyToExecute is true, and mScript is non-null.
133 : //
134 : // A script to be added to the next session's cache file always has a
135 : // non-null mScript value. If it was read from the last session's cache
136 : // file, it also has a non-empty mXDRRange range, which will be stored in
137 : // the next session's cache file. If it was compiled in this session, its
138 : // mXDRRange will initially be empty, and its mXDRData buffer will be
139 : // populated just before it is written to the cache file.
140 : class CachedScript : public LinkedListElement<CachedScript>
141 : {
142 : public:
143 : CachedScript(CachedScript&&) = default;
144 :
145 109 : CachedScript(ScriptPreloader& cache, const nsCString& url, const nsCString& cachePath, JSScript* script)
146 109 : : mCache(cache)
147 : , mURL(url)
148 : , mCachePath(cachePath)
149 : , mScript(script)
150 109 : , mReadyToExecute(true)
151 109 : {}
152 :
153 : inline CachedScript(ScriptPreloader& cache, InputBuffer& buf);
154 :
155 0 : ~CachedScript() = default;
156 :
157 42 : ScriptStatus Status() const
158 : {
159 42 : return mProcessTypes.isEmpty() ? ScriptStatus::Restored : ScriptStatus::Saved;
160 : }
161 :
162 : // For use with nsTArray::Sort.
163 : //
164 : // Orders scripts by script load time, so that scripts which are needed
165 : // earlier are stored earlier, and scripts needed at approximately the
166 : // same time are stored approximately contiguously.
167 : struct Comparator
168 : {
169 0 : bool Equals(const CachedScript* a, const CachedScript* b) const
170 : {
171 0 : return a->mLoadTime == b->mLoadTime;
172 : }
173 :
174 0 : bool LessThan(const CachedScript* a, const CachedScript* b) const
175 : {
176 0 : return a->mLoadTime < b->mLoadTime;
177 : }
178 : };
179 :
180 : struct StatusMatcher final : public Matcher<CachedScript*>
181 : {
182 1 : explicit StatusMatcher(ScriptStatus status) : mStatus(status) {}
183 :
184 42 : virtual bool Matches(CachedScript* script)
185 : {
186 42 : return script->Status() == mStatus;
187 : }
188 :
189 : const ScriptStatus mStatus;
190 : };
191 :
192 18 : void FreeData()
193 : {
194 : // If the script data isn't mmapped, we need to release both it
195 : // and the Range that points to it at the same time.
196 18 : if (!mXDRData.empty()) {
197 18 : mXDRRange.reset();
198 18 : mXDRData.destroy();
199 : }
200 18 : }
201 :
202 300 : void UpdateLoadTime(const TimeStamp& loadTime)
203 : {
204 300 : if (mLoadTime.IsNull() || loadTime < mLoadTime) {
205 289 : mLoadTime = loadTime;
206 : }
207 300 : }
208 :
209 : // Encodes this script into XDR data, and stores the result in mXDRData.
210 : // Returns true on success, false on failure.
211 : bool XDREncode(JSContext* cx);
212 :
213 : // Encodes or decodes this script, in the storage format required by the
214 : // script cache file.
215 : template<typename Buffer>
216 204 : void Code(Buffer& buffer)
217 : {
218 204 : buffer.codeString(mURL);
219 204 : buffer.codeString(mCachePath);
220 204 : buffer.codeUint32(mOffset);
221 204 : buffer.codeUint32(mSize);
222 204 : buffer.codeUint8(mProcessTypes);
223 204 : }
224 :
225 : // Returns the XDR data generated for this script during this session. See
226 : // mXDRData.
227 72 : JS::TranscodeBuffer& Buffer()
228 : {
229 72 : MOZ_ASSERT(HasBuffer());
230 72 : return mXDRData.ref<JS::TranscodeBuffer>();
231 : }
232 :
233 114 : bool HasBuffer() { return mXDRData.constructed<JS::TranscodeBuffer>(); }
234 :
235 : // Returns the read-only XDR data for this script. See mXDRRange.
236 207 : const JS::TranscodeRange& Range()
237 : {
238 207 : MOZ_ASSERT(HasRange());
239 207 : return mXDRRange.ref();
240 : }
241 :
242 269 : bool HasRange() { return mXDRRange.isSome(); }
243 :
244 18 : nsTArray<uint8_t>& Array()
245 : {
246 18 : MOZ_ASSERT(HasArray());
247 18 : return mXDRData.ref<nsTArray<uint8_t>>();
248 : }
249 :
250 36 : bool HasArray() { return mXDRData.constructed<nsTArray<uint8_t>>(); }
251 :
252 :
253 : JSScript* GetJSScript(JSContext* cx);
254 :
255 0 : size_t HeapSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
256 : {
257 0 : auto size = mallocSizeOf(this);
258 :
259 0 : if (HasArray()) {
260 0 : size += Array().ShallowSizeOfExcludingThis(mallocSizeOf);
261 0 : } else if (HasBuffer()) {
262 0 : size += Buffer().sizeOfExcludingThis(mallocSizeOf);
263 : } else {
264 0 : return size;
265 : }
266 :
267 0 : size += (mURL.SizeOfExcludingThisEvenIfShared(mallocSizeOf) +
268 0 : mCachePath.SizeOfExcludingThisEvenIfShared(mallocSizeOf));
269 0 : return size;
270 : }
271 :
272 : ScriptPreloader& mCache;
273 :
274 : // The URL from which this script was initially read and compiled.
275 : nsCString mURL;
276 : // A unique identifier for this script's filesystem location, used as a
277 : // primary cache lookup value.
278 : nsCString mCachePath;
279 :
280 : // The offset of this script in the cache file, from the start of the XDR
281 : // data block.
282 : uint32_t mOffset = 0;
283 : // The size of this script's encoded XDR data.
284 : uint32_t mSize = 0;
285 :
286 : TimeStamp mLoadTime{};
287 :
288 : JS::Heap<JSScript*> mScript;
289 :
290 : // True if this script is ready to be executed. This means that either the
291 : // off-thread portion of an off-thread decode has finished, or the script
292 : // is too small to be decoded off-thread, and may be immediately decoded
293 : // whenever it is first executed.
294 : bool mReadyToExecute = false;
295 :
296 : // The set of processes in which this script has been used.
297 : EnumSet<ProcessType> mProcessTypes{};
298 :
299 : // The set of processes which the script was loaded into during the
300 : // last session, as read from the cache file.
301 : EnumSet<ProcessType> mOriginalProcessTypes{};
302 :
303 : // The read-only XDR data for this script, which was either read from an
304 : // existing cache file, or generated by encoding a script which was
305 : // compiled during this session.
306 : Maybe<JS::TranscodeRange> mXDRRange;
307 :
308 : // XDR data which was generated from a script compiled during this
309 : // session, and will be written to the cache file.
310 : MaybeOneOf<JS::TranscodeBuffer, nsTArray<uint8_t>> mXDRData;
311 : } JS_HAZ_NON_GC_POINTER;
312 :
313 : template <ScriptStatus status>
314 1 : static Matcher<CachedScript*>* Match()
315 : {
316 1 : static CachedScript::StatusMatcher matcher{status};
317 1 : return &matcher;
318 : }
319 :
320 : // There's a significant setup cost for each off-thread decode operation,
321 : // so scripts are decoded in chunks to minimize the overhead. There's a
322 : // careful balancing act in choosing the size of chunks, to minimize the
323 : // number of decode operations, while also minimizing the number of buffer
324 : // underruns that require the main thread to wait for a script to finish
325 : // decoding.
326 : //
327 : // For the first chunk, we don't have much time between the start of the
328 : // decode operation and the time the first script is needed, so that chunk
329 : // needs to be fairly small. After the first chunk is finished, we have
330 : // some buffered scripts to fall back on, and a lot more breathing room,
331 : // so the chunks can be a bit bigger, but still not too big.
332 : static constexpr int OFF_THREAD_FIRST_CHUNK_SIZE = 128 * 1024;
333 : static constexpr int OFF_THREAD_CHUNK_SIZE = 512 * 1024;
334 :
335 : // Ideally, we want every chunk to be smaller than the chunk sizes
336 : // specified above. However, if we have some number of small scripts
337 : // followed by a huge script that would put us over the normal chunk size,
338 : // we're better off processing them as a single chunk.
339 : //
340 : // In order to guarantee that the JS engine will process a chunk
341 : // off-thread, it needs to be at least 100K (which is an implementation
342 : // detail that can change at any time), so make sure that we always hit at
343 : // least that size, with a bit of breathing room to be safe.
344 : static constexpr int SMALL_SCRIPT_CHUNK_THRESHOLD = 128 * 1024;
345 :
346 : // The maximum size of scripts to re-decode on the main thread if off-thread
347 : // decoding hasn't finished yet. In practice, we don't hit this very often,
348 : // but when we do, re-decoding some smaller scripts on the main thread gives
349 : // the background decoding a chance to catch up without blocking the main
350 : // thread for quite as long.
351 : static constexpr int MAX_MAINTHREAD_DECODE_SIZE = 50 * 1024;
352 :
353 : ScriptPreloader();
354 :
355 : void ForceWriteCacheFile();
356 : void Cleanup();
357 :
358 : void InvalidateCache();
359 :
360 : // Opens the cache file for reading.
361 : Result<Ok, nsresult> OpenCache();
362 :
363 : // Writes a new cache file to disk. Must not be called on the main thread.
364 : Result<Ok, nsresult> WriteCache();
365 :
366 : // Prepares scripts for writing to the cache, serializing new scripts to
367 : // XDR, and calculating their size-based offsets.
368 : void PrepareCacheWrite();
369 :
370 : void PrepareCacheWriteInternal();
371 :
372 : // Returns a file pointer for the cache file with the given name in the
373 : // current profile.
374 : Result<nsCOMPtr<nsIFile>, nsresult>
375 : GetCacheFile(const nsAString& suffix);
376 :
377 : // Waits for the given cached script to finish compiling off-thread, or
378 : // decodes it synchronously on the main thread, as appropriate.
379 : JSScript* WaitForCachedScript(JSContext* cx, CachedScript* script);
380 :
381 : void DecodeNextBatch(size_t chunkSize);
382 :
383 : static void OffThreadDecodeCallback(void* token, void* context);
384 : void FinishOffThreadDecode();
385 : void DoFinishOffThreadDecode();
386 :
387 0 : size_t ShallowHeapSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
388 : {
389 0 : return (mallocSizeOf(this) + mScripts.ShallowSizeOfExcludingThis(mallocSizeOf) +
390 0 : mallocSizeOf(mSaveThread.get()) + mallocSizeOf(mProfD.get()));
391 : }
392 :
393 : using ScriptHash = nsClassHashtable<nsCStringHashKey, CachedScript>;
394 :
395 : template<ScriptStatus status>
396 0 : static size_t SizeOfHashEntries(ScriptHash& scripts, mozilla::MallocSizeOf mallocSizeOf)
397 : {
398 0 : size_t size = 0;
399 0 : for (auto elem : IterHash(scripts, Match<status>())) {
400 0 : size += elem->HeapSizeOfIncludingThis(mallocSizeOf);
401 : }
402 0 : return size;
403 : }
404 :
405 : ScriptHash mScripts;
406 :
407 : // True after we've shown the first window, and are no longer adding new
408 : // scripts to the cache.
409 : bool mStartupFinished = false;
410 :
411 : bool mCacheInitialized = false;
412 : bool mSaveComplete = false;
413 : bool mDataPrepared = false;
414 : bool mCacheInvalidated = false;
415 : bool mBlockedOnSyncDispatch = false;
416 :
417 : // The list of scripts that we read from the initial startup cache file,
418 : // but have yet to initiate a decode task for.
419 : LinkedList<CachedScript> mPendingScripts;
420 :
421 : // The lists of scripts and their sources that make up the chunk currently
422 : // being decoded in a background thread.
423 : JS::TranscodeSources mParsingSources;
424 : Vector<CachedScript*> mParsingScripts;
425 :
426 : // The token for the completed off-thread decode task.
427 : void* mToken = nullptr;
428 :
429 : // True if a runnable has been dispatched to the main thread to finish an
430 : // off-thread decode operation.
431 : bool mFinishDecodeRunnablePending = false;
432 :
433 : // The process type of the current process.
434 : static ProcessType sProcessType;
435 :
436 : // The process types for which remote processes have been initialized, and
437 : // are expected to send back script data.
438 : EnumSet<ProcessType> mInitializedProcesses{};
439 :
440 : RefPtr<ScriptPreloader> mChildCache;
441 : ScriptCacheChild* mChildActor = nullptr;
442 :
443 : nsString mBaseName;
444 :
445 : nsCOMPtr<nsIFile> mProfD;
446 : nsCOMPtr<nsIThread> mSaveThread;
447 :
448 : // The mmapped cache data from this session's cache file.
449 : AutoMemMap mCacheData;
450 :
451 : Monitor mMonitor;
452 : Monitor mSaveMonitor;
453 : };
454 :
455 : } // namespace mozilla
456 :
457 : #endif // ScriptPreloader_h
|