Bug 1943761 - Add class alignment to the mozsearch analysis file. r=asuth
[gecko.git] / js / xpconnect / loader / ScriptPreloader.h
blobbccaf150b9071bbac235b7b75f19099bbf188b2c
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
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/. */
6 #ifndef ScriptPreloader_h
7 #define ScriptPreloader_h
9 #include "mozilla/Atomics.h"
10 #include "mozilla/CheckedInt.h"
11 #include "mozilla/EnumSet.h"
12 #include "mozilla/LinkedList.h"
13 #include "mozilla/MemoryReporting.h"
14 #include "mozilla/Maybe.h"
15 #include "mozilla/MaybeOneOf.h"
16 #include "mozilla/Monitor.h"
17 #include "mozilla/Range.h"
18 #include "mozilla/Result.h"
19 #include "mozilla/SPSCQueue.h"
20 #include "mozilla/StaticPtr.h"
21 #include "mozilla/Vector.h"
22 #include "mozilla/loader/AutoMemMap.h"
23 #include "MainThreadUtils.h"
24 #include "nsClassHashtable.h"
25 #include "nsThreadUtils.h"
26 #include "nsIAsyncShutdown.h"
27 #include "nsIFile.h"
28 #include "nsIMemoryReporter.h"
29 #include "nsIObserver.h"
30 #include "nsIThread.h"
31 #include "nsITimer.h"
33 #include "js/CompileOptions.h" // JS::DecodeOptions, JS::ReadOnlyDecodeOptions
34 #include "js/experimental/JSStencil.h" // JS::Stencil
35 #include "js/GCAnnotations.h" // for JS_HAZ_NON_GC_POINTER
36 #include "js/RootingAPI.h" // for Handle, Heap
37 #include "js/Transcoding.h" // for TranscodeBuffer, TranscodeRange, TranscodeSource
38 #include "js/TypeDecls.h" // for HandleObject, HandleScript
40 #include <prio.h>
42 namespace mozilla {
43 namespace dom {
44 class ContentParent;
46 namespace ipc {
47 class FileDescriptor;
49 namespace loader {
50 class InputBuffer;
51 class ScriptCacheChild;
53 enum class ProcessType : uint8_t {
54 Uninitialized,
55 Parent,
56 Web,
57 Extension,
58 PrivilegedAbout,
61 template <typename T>
62 struct Matcher {
63 virtual bool Matches(T) = 0;
65 } // namespace loader
67 using namespace mozilla::loader;
69 class ScriptPreloader : public nsIObserver,
70 public nsIMemoryReporter,
71 public nsIRunnable,
72 public nsINamed,
73 public nsIAsyncShutdownBlocker,
74 public SingleWriterLockOwner {
75 MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
77 friend class mozilla::loader::ScriptCacheChild;
79 public:
80 NS_DECL_THREADSAFE_ISUPPORTS
81 NS_DECL_NSIOBSERVER
82 NS_DECL_NSIMEMORYREPORTER
83 NS_DECL_NSIRUNNABLE
84 NS_DECL_NSINAMED
85 NS_DECL_NSIASYNCSHUTDOWNBLOCKER
87 private:
88 static StaticRefPtr<ScriptPreloader> gScriptPreloader;
89 static StaticRefPtr<ScriptPreloader> gChildScriptPreloader;
90 static StaticAutoPtr<AutoMemMap> gCacheData;
91 static StaticAutoPtr<AutoMemMap> gChildCacheData;
93 public:
94 static ScriptPreloader& GetSingleton();
95 static ScriptPreloader& GetChildSingleton();
97 static void DeleteSingleton();
98 static void DeleteCacheDataSingleton();
100 static ProcessType GetChildProcessType(const nsACString& remoteType);
102 // Fill some options that should be consistent across all scripts stored
103 // into preloader cache.
104 static void FillCompileOptionsForCachedStencil(JS::CompileOptions& options);
105 static void FillDecodeOptionsForCachedStencil(JS::DecodeOptions& options);
107 bool OnWritingThread() const override { return NS_IsMainThread(); }
109 // Retrieves the stencil with the given cache key from the cache.
110 // Returns null if the stencil is not cached.
111 already_AddRefed<JS::Stencil> GetCachedStencil(
112 JSContext* cx, const JS::ReadOnlyDecodeOptions& options,
113 const nsCString& path);
115 // Notes the execution of a script with the given URL and cache key.
116 // Depending on the stage of startup, the script may be serialized and
117 // stored to the startup script cache.
119 // If isRunOnce is true, this script is expected to run only once per
120 // process per browser session. A cached instance will not be kept alive
121 // for repeated execution.
122 void NoteStencil(const nsCString& url, const nsCString& cachePath,
123 JS::Stencil* stencil, bool isRunOnce = false);
125 // Notes the IPC arrival of the XDR data of a stencil compiled by some
126 // child process. See ScriptCacheChild::SendScriptsAndFinalize.
127 void NoteStencil(const nsCString& url, const nsCString& cachePath,
128 ProcessType processType, nsTArray<uint8_t>&& xdrData,
129 TimeStamp loadTime);
131 // Initializes the script cache from the startup script cache file.
132 Result<Ok, nsresult> InitCache(const nsAString& = u"scriptCache"_ns);
134 Result<Ok, nsresult> InitCache(const Maybe<ipc::FileDescriptor>& cacheFile,
135 ScriptCacheChild* cacheChild);
137 bool Active() const { return mCacheInitialized && !mStartupFinished; }
139 private:
140 Result<Ok, nsresult> InitCacheInternal(JS::Handle<JSObject*> scope = nullptr);
141 already_AddRefed<JS::Stencil> GetCachedStencilInternal(
142 JSContext* cx, const JS::ReadOnlyDecodeOptions& options,
143 const nsCString& path);
145 public:
146 static ProcessType CurrentProcessType() {
147 MOZ_ASSERT(sProcessType != ProcessType::Uninitialized);
148 return sProcessType;
151 static void InitContentChild(dom::ContentParent& parent);
153 protected:
154 virtual ~ScriptPreloader();
156 private:
157 enum class ScriptStatus {
158 Restored,
159 Saved,
162 // Represents a cached script stencil, either initially read from the
163 // cache file, to be added to the next session's stencil cache file, or
164 // both.
166 // - Read from the cache, and being decoded off thread. In this case:
167 // - mReadyToExecute is false
168 // - mDecodingScripts contains the CachedStencil
169 // - mDecodedStencils have never contained the stencil
170 // - mStencil is null
172 // - Off-thread decode for the stencil has finished, but the stencil has not
173 // yet been dequeued nor executed. In this case:
174 // - mReadyToExecute is true
175 // - mDecodingScripts contains the CachedStencil
176 // - mDecodedStencils contains the decoded stencil
177 // - mStencil is null
179 // - Off-thread decode for the stencil has finished, and the stencil has
180 // been dequeued, but has not yet been executed. In this case:
181 // - mReadyToExecute is true
182 // - mDecodingScripts no longer contains the CachedStencil
183 // - mDecodedStencils no longer contains the decoded stencil
184 // - mStencil is non-null
186 // - Fully decoded, and ready to be added to the next session's cache
187 // file. In this case:
188 // - mReadyToExecute is true
189 // - mStencil is non-null
191 // A stencil to be added to the next session's cache file always has a
192 // non-null mStencil value. If it was read from the last session's cache
193 // file, it also has a non-empty mXDRRange range, which will be stored in
194 // the next session's cache file. If it was compiled in this session, its
195 // mXDRRange will initially be empty, and its mXDRData buffer will be
196 // populated just before it is written to the cache file.
197 class CachedStencil : public LinkedListElement<CachedStencil> {
198 public:
199 CachedStencil(CachedStencil&&) = delete;
201 CachedStencil(ScriptPreloader& cache, const nsCString& url,
202 const nsCString& cachePath, JS::Stencil* stencil)
203 : mCache(cache),
204 mURL(url),
205 mCachePath(cachePath),
206 mStencil(stencil),
207 mReadyToExecute(true),
208 mIsRunOnce(false) {}
210 inline CachedStencil(ScriptPreloader& cache, InputBuffer& buf);
212 ~CachedStencil() = default;
214 ScriptStatus Status() const {
215 return mProcessTypes.isEmpty() ? ScriptStatus::Restored
216 : ScriptStatus::Saved;
219 // For use with nsTArray::Sort.
221 // Orders scripts by script load time, so that scripts which are needed
222 // earlier are stored earlier, and scripts needed at approximately the
223 // same time are stored approximately contiguously.
224 struct Comparator {
225 bool Equals(const CachedStencil* a, const CachedStencil* b) const {
226 return a->mLoadTime == b->mLoadTime;
229 bool LessThan(const CachedStencil* a, const CachedStencil* b) const {
230 return a->mLoadTime < b->mLoadTime;
234 struct StatusMatcher final : public Matcher<CachedStencil*> {
235 explicit StatusMatcher(ScriptStatus status) : mStatus(status) {}
237 virtual bool Matches(CachedStencil* script) override {
238 return script->Status() == mStatus;
241 const ScriptStatus mStatus;
244 void FreeData() {
245 // If the script data isn't mmapped, we need to release both it
246 // and the Range that points to it at the same time.
247 if (!IsMemMapped()) {
248 mXDRRange.reset();
249 mXDRData.destroy();
253 void UpdateLoadTime(const TimeStamp& loadTime) {
254 if (mLoadTime.IsNull() || loadTime < mLoadTime) {
255 mLoadTime = loadTime;
259 // Checks whether the cached JSScript for this entry will be needed
260 // again and, if not, drops it and returns true. This is the case for
261 // run-once scripts that do not still need to be encoded into the
262 // cache.
264 // If this method returns false, callers may set mScript to a cached
265 // JSScript instance for this entry. If it returns true, they should
266 // not.
267 bool MaybeDropStencil() {
268 if (mIsRunOnce && (HasRange() || !mCache.WillWriteScripts())) {
269 mStencil = nullptr;
270 return true;
272 return false;
275 // Encodes this script into XDR data, and stores the result in mXDRData.
276 // Returns true on success, false on failure.
277 bool XDREncode(JSContext* cx);
279 // Encodes or decodes this script, in the storage format required by the
280 // script cache file.
281 template <typename Buffer>
282 void Code(Buffer& buffer) {
283 buffer.codeString(mURL);
284 buffer.codeString(mCachePath);
285 buffer.codeUint32(mOffset);
286 buffer.codeUint32(mSize);
287 buffer.codeUint8(mProcessTypes);
290 // Returns the XDR data generated for this script during this session. See
291 // mXDRData.
292 JS::TranscodeBuffer& Buffer() {
293 MOZ_ASSERT(HasBuffer());
294 return mXDRData.ref<JS::TranscodeBuffer>();
297 bool HasBuffer() { return mXDRData.constructed<JS::TranscodeBuffer>(); }
299 // Returns the read-only XDR data for this script. See mXDRRange.
300 const JS::TranscodeRange& Range() {
301 MOZ_ASSERT(HasRange());
302 return mXDRRange.ref();
305 bool HasRange() { return mXDRRange.isSome(); }
307 bool IsMemMapped() const { return mXDRData.empty(); }
309 nsTArray<uint8_t>& Array() {
310 MOZ_ASSERT(HasArray());
311 return mXDRData.ref<nsTArray<uint8_t>>();
314 bool HasArray() { return mXDRData.constructed<nsTArray<uint8_t>>(); }
316 already_AddRefed<JS::Stencil> GetStencil(
317 JSContext* cx, const JS::ReadOnlyDecodeOptions& options);
319 size_t HeapSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) {
320 auto size = mallocSizeOf(this);
322 if (HasArray()) {
323 size += Array().ShallowSizeOfExcludingThis(mallocSizeOf);
324 } else if (HasBuffer()) {
325 size += Buffer().sizeOfExcludingThis(mallocSizeOf);
328 if (mStencil) {
329 size += JS::SizeOfStencil(mStencil, mallocSizeOf);
332 // Note: mURL and mCachePath use the same string for scripts loaded
333 // by the message manager. The following statement avoids
334 // double-measuring in that case.
335 size += (mURL.SizeOfExcludingThisIfUnshared(mallocSizeOf) +
336 mCachePath.SizeOfExcludingThisEvenIfShared(mallocSizeOf));
338 return size;
341 ScriptPreloader& mCache;
343 // The URL from which this script was initially read and compiled.
344 nsCString mURL;
345 // A unique identifier for this script's filesystem location, used as a
346 // primary cache lookup value.
347 nsCString mCachePath;
349 // The offset of this script in the cache file, from the start of the XDR
350 // data block.
351 uint32_t mOffset = 0;
352 // The size of this script's encoded XDR data.
353 uint32_t mSize = 0;
355 TimeStamp mLoadTime{};
357 RefPtr<JS::Stencil> mStencil;
359 // True if this script is ready to be executed. This means that either the
360 // off-thread portion of an off-thread decode has finished, or the
361 // off-thread decode failed, and may be immediately decoded
362 // whenever it is first executed.
363 bool mReadyToExecute = false;
365 // True if this script is expected to run once per process. If so, its
366 // JSScript instance will be dropped as soon as the script has
367 // executed and been encoded into the cache.
368 bool mIsRunOnce = false;
370 // The set of processes in which this script has been used.
371 EnumSet<ProcessType> mProcessTypes{};
373 // The set of processes which the script was loaded into during the
374 // last session, as read from the cache file.
375 EnumSet<ProcessType> mOriginalProcessTypes{};
377 // The read-only XDR data for this script, which was either read from an
378 // existing cache file, or generated by encoding a script which was
379 // compiled during this session.
380 Maybe<JS::TranscodeRange> mXDRRange;
382 // XDR data which was generated from a script compiled during this
383 // session, and will be written to the cache file.
385 // The format is JS::TranscodeBuffer if the script was XDR'd as part
386 // of this process, or nsTArray<> if the script was transfered by IPC
387 // from a child process.
388 MaybeOneOf<JS::TranscodeBuffer, nsTArray<uint8_t>> mXDRData;
389 } JS_HAZ_NON_GC_POINTER;
391 template <ScriptStatus status>
392 static Matcher<CachedStencil*>* Match() {
393 static CachedStencil::StatusMatcher matcher{status};
394 return &matcher;
397 // The maximum size of scripts to re-decode on the main thread if off-thread
398 // decoding hasn't finished yet. In practice, we don't hit this very often,
399 // but when we do, re-decoding some smaller scripts on the main thread gives
400 // the background decoding a chance to catch up without blocking the main
401 // thread for quite as long.
402 static constexpr int MAX_MAINTHREAD_DECODE_SIZE = 50 * 1024;
404 explicit ScriptPreloader(AutoMemMap* cacheData);
406 void Cleanup();
408 void FinishPendingParses(MonitorAutoLock& aMal);
409 void InvalidateCache();
411 // Opens the cache file for reading.
412 Result<Ok, nsresult> OpenCache();
414 // Writes a new cache file to disk. Must not be called on the main thread.
415 Result<Ok, nsresult> WriteCache() MOZ_REQUIRES(mSaveMonitor);
417 void StartCacheWrite();
419 // Prepares scripts for writing to the cache, serializing new scripts to
420 // XDR, and calculating their size-based offsets.
421 void PrepareCacheWrite();
423 void PrepareCacheWriteInternal();
425 void CacheWriteComplete();
427 void FinishContentStartup();
429 // Returns true if scripts added to the cache now will be encoded and
430 // written to the cache. If we've already encoded scripts for the cache
431 // write, or this is a content process which hasn't been asked to return
432 // script bytecode, this will return false.
433 bool WillWriteScripts();
435 // Returns a file pointer for the cache file with the given name in the
436 // current profile.
437 Result<nsCOMPtr<nsIFile>, nsresult> GetCacheFile(const nsAString& suffix);
439 // Waits for the given cached script to finish compiling off-thread, or
440 // decodes it synchronously on the main thread, as appropriate.
441 already_AddRefed<JS::Stencil> WaitForCachedStencil(
442 JSContext* cx, const JS::ReadOnlyDecodeOptions& options,
443 CachedStencil* script);
445 void StartDecodeTask(JS::Handle<JSObject*> scope);
447 private:
448 bool StartDecodeTask(const JS::ReadOnlyDecodeOptions& decodeOptions,
449 Vector<JS::TranscodeSource>&& decodingSources);
451 class DecodeTask : public Runnable {
452 ScriptPreloader* mPreloader;
453 JS::OwningDecodeOptions mDecodeOptions;
454 Vector<JS::TranscodeSource> mDecodingSources;
456 public:
457 DecodeTask(ScriptPreloader* preloader,
458 const JS::ReadOnlyDecodeOptions& decodeOptions,
459 Vector<JS::TranscodeSource>&& decodingSources)
460 : Runnable("ScriptPreloaderDecodeTask"),
461 mPreloader(preloader),
462 mDecodingSources(std::move(decodingSources)) {
463 mDecodeOptions.infallibleCopy(decodeOptions);
466 NS_IMETHOD Run() override;
469 friend class DecodeTask;
471 void onDecodedStencilQueued();
472 void OnDecodeTaskFinished();
473 void OnDecodeTaskFailed();
475 public:
476 void FinishOffThreadDecode();
477 void DoFinishOffThreadDecode();
479 already_AddRefed<nsIAsyncShutdownClient> GetShutdownBarrier();
481 size_t ShallowHeapSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) {
482 return (mallocSizeOf(this) +
483 mScripts.ShallowSizeOfExcludingThis(mallocSizeOf) +
484 mallocSizeOf(mSaveThread.get()) + mallocSizeOf(mProfD.get()));
487 using ScriptHash = nsClassHashtable<nsCStringHashKey, CachedStencil>;
489 template <ScriptStatus status>
490 static size_t SizeOfHashEntries(ScriptHash& scripts,
491 mozilla::MallocSizeOf mallocSizeOf) {
492 size_t size = 0;
493 for (auto elem : IterHash(scripts, Match<status>())) {
494 size += elem->HeapSizeOfIncludingThis(mallocSizeOf);
496 return size;
499 ScriptHash mScripts;
501 // True after we've shown the first window, and are no longer adding new
502 // scripts to the cache.
503 bool mStartupFinished = false;
505 bool mCacheInitialized = false;
506 bool mSaveComplete = false;
507 bool mDataPrepared = false;
508 // May only be changed on the main thread, while `mSaveMonitor` is held.
509 bool mCacheInvalidated MOZ_GUARDED_BY(mSaveMonitor) = false;
511 // The list of scripts currently being decoded in a background thread.
512 LinkedList<CachedStencil> mDecodingScripts;
514 // The result of the decode task.
516 // This is emplaced when starting the decode task, with the capacity equal
517 // to the number of sources.
519 // If the decode task failed, nullptr is enqueued.
520 Maybe<SPSCQueue<RefPtr<JS::Stencil>>> mDecodedStencils;
522 // True is main-thread is blocked and we should notify with Monitor. Access
523 // only while `mMonitor` is held.
524 bool mWaitingForDecode MOZ_GUARDED_BY(mMonitor) = false;
526 // The process type of the current process.
527 static ProcessType sProcessType;
529 // The process types for which remote processes have been initialized, and
530 // are expected to send back script data.
531 EnumSet<ProcessType> mInitializedProcesses{};
533 RefPtr<ScriptPreloader> mChildCache;
534 ScriptCacheChild* mChildActor = nullptr;
536 nsString mBaseName;
537 nsCString mContentStartupFinishedTopic;
539 nsCOMPtr<nsIFile> mProfD;
540 nsCOMPtr<nsIThread> mSaveThread;
541 nsCOMPtr<nsITimer> mSaveTimer;
543 // The mmapped cache data from this session's cache file.
544 // The instance is held by either `gCacheData` or `gChildCacheData` static
545 // fields, and its lifetime is guaranteed to be longer than ScriptPreloader
546 // instance.
547 AutoMemMap* mCacheData;
549 Monitor mMonitor;
550 MonitorSingleWriter mSaveMonitor MOZ_ACQUIRED_BEFORE(mMonitor);
553 } // namespace mozilla
555 #endif // ScriptPreloader_h