LCOV - code coverage report
Current view: top level - js/xpconnect/loader - ScriptPreloader.h (source / functions) Hit Total Coverage
Test: output.info Lines: 43 68 63.2 %
Date: 2017-07-14 16:53:18 Functions: 16 28 57.1 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.13