зеркало из https://github.com/mozilla/gecko-dev.git
464 строки
16 KiB
C++
464 строки
16 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; -*- */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#ifndef ScriptPreloader_h
|
|
#define ScriptPreloader_h
|
|
|
|
#include "mozilla/CheckedInt.h"
|
|
#include "mozilla/EnumSet.h"
|
|
#include "mozilla/LinkedList.h"
|
|
#include "mozilla/MemoryReporting.h"
|
|
#include "mozilla/Maybe.h"
|
|
#include "mozilla/MaybeOneOf.h"
|
|
#include "mozilla/Monitor.h"
|
|
#include "mozilla/Range.h"
|
|
#include "mozilla/Vector.h"
|
|
#include "mozilla/Result.h"
|
|
#include "mozilla/loader/AutoMemMap.h"
|
|
#include "nsClassHashtable.h"
|
|
#include "nsIFile.h"
|
|
#include "nsIMemoryReporter.h"
|
|
#include "nsIObserver.h"
|
|
#include "nsIThread.h"
|
|
|
|
#include "jsapi.h"
|
|
#include "js/GCAnnotations.h"
|
|
|
|
#include <prio.h>
|
|
|
|
namespace mozilla {
|
|
namespace dom {
|
|
class ContentParent;
|
|
}
|
|
namespace ipc {
|
|
class FileDescriptor;
|
|
}
|
|
namespace loader {
|
|
class InputBuffer;
|
|
class ScriptCacheChild;
|
|
|
|
enum class ProcessType : uint8_t {
|
|
Parent,
|
|
Web,
|
|
Extension,
|
|
};
|
|
|
|
template <typename T>
|
|
struct Matcher
|
|
{
|
|
virtual bool Matches(T) = 0;
|
|
};
|
|
}
|
|
|
|
using namespace mozilla::loader;
|
|
|
|
class ScriptPreloader : public nsIObserver
|
|
, public nsIMemoryReporter
|
|
, public nsIRunnable
|
|
{
|
|
MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
|
|
|
|
friend class mozilla::loader::ScriptCacheChild;
|
|
|
|
public:
|
|
NS_DECL_THREADSAFE_ISUPPORTS
|
|
NS_DECL_NSIOBSERVER
|
|
NS_DECL_NSIMEMORYREPORTER
|
|
NS_DECL_NSIRUNNABLE
|
|
|
|
static ScriptPreloader& GetSingleton();
|
|
static ScriptPreloader& GetChildSingleton();
|
|
|
|
static ProcessType GetChildProcessType(const nsAString& remoteType);
|
|
|
|
// Retrieves the script with the given cache key from the script cache.
|
|
// Returns null if the script is not cached.
|
|
JSScript* GetCachedScript(JSContext* cx, const nsCString& name);
|
|
|
|
// Notes the execution of a script with the given URL and cache key.
|
|
// Depending on the stage of startup, the script may be serialized and
|
|
// stored to the startup script cache.
|
|
void NoteScript(const nsCString& url, const nsCString& cachePath, JS::HandleScript script);
|
|
|
|
void NoteScript(const nsCString& url, const nsCString& cachePath,
|
|
ProcessType processType, nsTArray<uint8_t>&& xdrData,
|
|
TimeStamp loadTime);
|
|
|
|
// Initializes the script cache from the startup script cache file.
|
|
Result<Ok, nsresult> InitCache(const nsAString& = NS_LITERAL_STRING("scriptCache"));
|
|
|
|
Result<Ok, nsresult> InitCache(const Maybe<ipc::FileDescriptor>& cacheFile, ScriptCacheChild* cacheChild);
|
|
|
|
bool Active()
|
|
{
|
|
return mCacheInitialized && !mStartupFinished;
|
|
}
|
|
|
|
private:
|
|
Result<Ok, nsresult> InitCacheInternal();
|
|
|
|
public:
|
|
void Trace(JSTracer* trc);
|
|
|
|
static ProcessType CurrentProcessType()
|
|
{
|
|
return sProcessType;
|
|
}
|
|
|
|
static void InitContentChild(dom::ContentParent& parent);
|
|
|
|
protected:
|
|
virtual ~ScriptPreloader() = default;
|
|
|
|
private:
|
|
enum class ScriptStatus {
|
|
Restored,
|
|
Saved,
|
|
};
|
|
|
|
// Represents a cached JS script, either initially read from the script
|
|
// cache file, to be added to the next session's script cache file, or
|
|
// both.
|
|
//
|
|
// A script which was read from the cache file may be in any of the
|
|
// following states:
|
|
//
|
|
// - Read from the cache, and being compiled off thread. In this case,
|
|
// mReadyToExecute is false, and mToken is null.
|
|
// - Off-thread compilation has finished, but the script has not yet been
|
|
// executed. In this case, mReadyToExecute is true, and mToken has a non-null
|
|
// value.
|
|
// - Read from the cache, but too small or needed to immediately to be
|
|
// compiled off-thread. In this case, mReadyToExecute is true, and both mToken
|
|
// and mScript are null.
|
|
// - Fully decoded, and ready to be added to the next session's cache
|
|
// file. In this case, mReadyToExecute is true, and mScript is non-null.
|
|
//
|
|
// A script to be added to the next session's cache file always has a
|
|
// non-null mScript value. If it was read from the last session's cache
|
|
// file, it also has a non-empty mXDRRange range, which will be stored in
|
|
// the next session's cache file. If it was compiled in this session, its
|
|
// mXDRRange will initially be empty, and its mXDRData buffer will be
|
|
// populated just before it is written to the cache file.
|
|
class CachedScript : public LinkedListElement<CachedScript>
|
|
{
|
|
public:
|
|
CachedScript(CachedScript&&) = delete;
|
|
|
|
CachedScript(ScriptPreloader& cache, const nsCString& url, const nsCString& cachePath, JSScript* script)
|
|
: mCache(cache)
|
|
, mURL(url)
|
|
, mCachePath(cachePath)
|
|
, mScript(script)
|
|
, mReadyToExecute(true)
|
|
{}
|
|
|
|
inline CachedScript(ScriptPreloader& cache, InputBuffer& buf);
|
|
|
|
~CachedScript() = default;
|
|
|
|
ScriptStatus Status() const
|
|
{
|
|
return mProcessTypes.isEmpty() ? ScriptStatus::Restored : ScriptStatus::Saved;
|
|
}
|
|
|
|
// For use with nsTArray::Sort.
|
|
//
|
|
// Orders scripts by script load time, so that scripts which are needed
|
|
// earlier are stored earlier, and scripts needed at approximately the
|
|
// same time are stored approximately contiguously.
|
|
struct Comparator
|
|
{
|
|
bool Equals(const CachedScript* a, const CachedScript* b) const
|
|
{
|
|
return a->mLoadTime == b->mLoadTime;
|
|
}
|
|
|
|
bool LessThan(const CachedScript* a, const CachedScript* b) const
|
|
{
|
|
return a->mLoadTime < b->mLoadTime;
|
|
}
|
|
};
|
|
|
|
struct StatusMatcher final : public Matcher<CachedScript*>
|
|
{
|
|
explicit StatusMatcher(ScriptStatus status) : mStatus(status) {}
|
|
|
|
virtual bool Matches(CachedScript* script)
|
|
{
|
|
return script->Status() == mStatus;
|
|
}
|
|
|
|
const ScriptStatus mStatus;
|
|
};
|
|
|
|
void FreeData()
|
|
{
|
|
// If the script data isn't mmapped, we need to release both it
|
|
// and the Range that points to it at the same time.
|
|
if (!mXDRData.empty()) {
|
|
mXDRRange.reset();
|
|
mXDRData.destroy();
|
|
}
|
|
}
|
|
|
|
void UpdateLoadTime(const TimeStamp& loadTime)
|
|
{
|
|
if (mLoadTime.IsNull() || loadTime < mLoadTime) {
|
|
mLoadTime = loadTime;
|
|
}
|
|
}
|
|
|
|
// Encodes this script into XDR data, and stores the result in mXDRData.
|
|
// Returns true on success, false on failure.
|
|
bool XDREncode(JSContext* cx);
|
|
|
|
// Encodes or decodes this script, in the storage format required by the
|
|
// script cache file.
|
|
template<typename Buffer>
|
|
void Code(Buffer& buffer)
|
|
{
|
|
buffer.codeString(mURL);
|
|
buffer.codeString(mCachePath);
|
|
buffer.codeUint32(mOffset);
|
|
buffer.codeUint32(mSize);
|
|
buffer.codeUint8(mProcessTypes);
|
|
}
|
|
|
|
// Returns the XDR data generated for this script during this session. See
|
|
// mXDRData.
|
|
JS::TranscodeBuffer& Buffer()
|
|
{
|
|
MOZ_ASSERT(HasBuffer());
|
|
return mXDRData.ref<JS::TranscodeBuffer>();
|
|
}
|
|
|
|
bool HasBuffer() { return mXDRData.constructed<JS::TranscodeBuffer>(); }
|
|
|
|
// Returns the read-only XDR data for this script. See mXDRRange.
|
|
const JS::TranscodeRange& Range()
|
|
{
|
|
MOZ_ASSERT(HasRange());
|
|
return mXDRRange.ref();
|
|
}
|
|
|
|
bool HasRange() { return mXDRRange.isSome(); }
|
|
|
|
nsTArray<uint8_t>& Array()
|
|
{
|
|
MOZ_ASSERT(HasArray());
|
|
return mXDRData.ref<nsTArray<uint8_t>>();
|
|
}
|
|
|
|
bool HasArray() { return mXDRData.constructed<nsTArray<uint8_t>>(); }
|
|
|
|
|
|
JSScript* GetJSScript(JSContext* cx);
|
|
|
|
size_t HeapSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
|
|
{
|
|
auto size = mallocSizeOf(this);
|
|
|
|
if (HasArray()) {
|
|
size += Array().ShallowSizeOfExcludingThis(mallocSizeOf);
|
|
} else if (HasBuffer()) {
|
|
size += Buffer().sizeOfExcludingThis(mallocSizeOf);
|
|
} else {
|
|
return size;
|
|
}
|
|
|
|
size += (mURL.SizeOfExcludingThisEvenIfShared(mallocSizeOf) +
|
|
mCachePath.SizeOfExcludingThisEvenIfShared(mallocSizeOf));
|
|
return size;
|
|
}
|
|
|
|
ScriptPreloader& mCache;
|
|
|
|
// The URL from which this script was initially read and compiled.
|
|
nsCString mURL;
|
|
// A unique identifier for this script's filesystem location, used as a
|
|
// primary cache lookup value.
|
|
nsCString mCachePath;
|
|
|
|
// The offset of this script in the cache file, from the start of the XDR
|
|
// data block.
|
|
uint32_t mOffset = 0;
|
|
// The size of this script's encoded XDR data.
|
|
uint32_t mSize = 0;
|
|
|
|
TimeStamp mLoadTime{};
|
|
|
|
JS::Heap<JSScript*> mScript;
|
|
|
|
// True if this script is ready to be executed. This means that either the
|
|
// off-thread portion of an off-thread decode has finished, or the script
|
|
// is too small to be decoded off-thread, and may be immediately decoded
|
|
// whenever it is first executed.
|
|
bool mReadyToExecute = false;
|
|
|
|
// The set of processes in which this script has been used.
|
|
EnumSet<ProcessType> mProcessTypes{};
|
|
|
|
// The set of processes which the script was loaded into during the
|
|
// last session, as read from the cache file.
|
|
EnumSet<ProcessType> mOriginalProcessTypes{};
|
|
|
|
// The read-only XDR data for this script, which was either read from an
|
|
// existing cache file, or generated by encoding a script which was
|
|
// compiled during this session.
|
|
Maybe<JS::TranscodeRange> mXDRRange;
|
|
|
|
// XDR data which was generated from a script compiled during this
|
|
// session, and will be written to the cache file.
|
|
MaybeOneOf<JS::TranscodeBuffer, nsTArray<uint8_t>> mXDRData;
|
|
} JS_HAZ_NON_GC_POINTER;
|
|
|
|
template <ScriptStatus status>
|
|
static Matcher<CachedScript*>* Match()
|
|
{
|
|
static CachedScript::StatusMatcher matcher{status};
|
|
return &matcher;
|
|
}
|
|
|
|
// There's a significant setup cost for each off-thread decode operation,
|
|
// so scripts are decoded in chunks to minimize the overhead. There's a
|
|
// careful balancing act in choosing the size of chunks, to minimize the
|
|
// number of decode operations, while also minimizing the number of buffer
|
|
// underruns that require the main thread to wait for a script to finish
|
|
// decoding.
|
|
//
|
|
// For the first chunk, we don't have much time between the start of the
|
|
// decode operation and the time the first script is needed, so that chunk
|
|
// needs to be fairly small. After the first chunk is finished, we have
|
|
// some buffered scripts to fall back on, and a lot more breathing room,
|
|
// so the chunks can be a bit bigger, but still not too big.
|
|
static constexpr int OFF_THREAD_FIRST_CHUNK_SIZE = 128 * 1024;
|
|
static constexpr int OFF_THREAD_CHUNK_SIZE = 512 * 1024;
|
|
|
|
// Ideally, we want every chunk to be smaller than the chunk sizes
|
|
// specified above. However, if we have some number of small scripts
|
|
// followed by a huge script that would put us over the normal chunk size,
|
|
// we're better off processing them as a single chunk.
|
|
//
|
|
// In order to guarantee that the JS engine will process a chunk
|
|
// off-thread, it needs to be at least 100K (which is an implementation
|
|
// detail that can change at any time), so make sure that we always hit at
|
|
// least that size, with a bit of breathing room to be safe.
|
|
static constexpr int SMALL_SCRIPT_CHUNK_THRESHOLD = 128 * 1024;
|
|
|
|
// The maximum size of scripts to re-decode on the main thread if off-thread
|
|
// decoding hasn't finished yet. In practice, we don't hit this very often,
|
|
// but when we do, re-decoding some smaller scripts on the main thread gives
|
|
// the background decoding a chance to catch up without blocking the main
|
|
// thread for quite as long.
|
|
static constexpr int MAX_MAINTHREAD_DECODE_SIZE = 50 * 1024;
|
|
|
|
ScriptPreloader();
|
|
|
|
void ForceWriteCacheFile();
|
|
void Cleanup();
|
|
|
|
void FinishPendingParses(MonitorAutoLock& aMal);
|
|
void InvalidateCache();
|
|
|
|
// Opens the cache file for reading.
|
|
Result<Ok, nsresult> OpenCache();
|
|
|
|
// Writes a new cache file to disk. Must not be called on the main thread.
|
|
Result<Ok, nsresult> WriteCache();
|
|
|
|
// Prepares scripts for writing to the cache, serializing new scripts to
|
|
// XDR, and calculating their size-based offsets.
|
|
void PrepareCacheWrite();
|
|
|
|
void PrepareCacheWriteInternal();
|
|
|
|
// Returns a file pointer for the cache file with the given name in the
|
|
// current profile.
|
|
Result<nsCOMPtr<nsIFile>, nsresult>
|
|
GetCacheFile(const nsAString& suffix);
|
|
|
|
// Waits for the given cached script to finish compiling off-thread, or
|
|
// decodes it synchronously on the main thread, as appropriate.
|
|
JSScript* WaitForCachedScript(JSContext* cx, CachedScript* script);
|
|
|
|
void DecodeNextBatch(size_t chunkSize);
|
|
|
|
static void OffThreadDecodeCallback(void* token, void* context);
|
|
void MaybeFinishOffThreadDecode();
|
|
void DoFinishOffThreadDecode();
|
|
|
|
size_t ShallowHeapSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
|
|
{
|
|
return (mallocSizeOf(this) + mScripts.ShallowSizeOfExcludingThis(mallocSizeOf) +
|
|
mallocSizeOf(mSaveThread.get()) + mallocSizeOf(mProfD.get()));
|
|
}
|
|
|
|
using ScriptHash = nsClassHashtable<nsCStringHashKey, CachedScript>;
|
|
|
|
template<ScriptStatus status>
|
|
static size_t SizeOfHashEntries(ScriptHash& scripts, mozilla::MallocSizeOf mallocSizeOf)
|
|
{
|
|
size_t size = 0;
|
|
for (auto elem : IterHash(scripts, Match<status>())) {
|
|
size += elem->HeapSizeOfIncludingThis(mallocSizeOf);
|
|
}
|
|
return size;
|
|
}
|
|
|
|
ScriptHash mScripts;
|
|
|
|
// True after we've shown the first window, and are no longer adding new
|
|
// scripts to the cache.
|
|
bool mStartupFinished = false;
|
|
|
|
bool mCacheInitialized = false;
|
|
bool mSaveComplete = false;
|
|
bool mDataPrepared = false;
|
|
bool mCacheInvalidated = false;
|
|
bool mBlockedOnSyncDispatch = false;
|
|
|
|
// The list of scripts that we read from the initial startup cache file,
|
|
// but have yet to initiate a decode task for.
|
|
LinkedList<CachedScript> mPendingScripts;
|
|
|
|
// The lists of scripts and their sources that make up the chunk currently
|
|
// being decoded in a background thread.
|
|
JS::TranscodeSources mParsingSources;
|
|
Vector<CachedScript*> mParsingScripts;
|
|
|
|
// The token for the completed off-thread decode task.
|
|
void* mToken = nullptr;
|
|
|
|
// True if a runnable has been dispatched to the main thread to finish an
|
|
// off-thread decode operation.
|
|
bool mFinishDecodeRunnablePending = false;
|
|
|
|
// The process type of the current process.
|
|
static ProcessType sProcessType;
|
|
|
|
// The process types for which remote processes have been initialized, and
|
|
// are expected to send back script data.
|
|
EnumSet<ProcessType> mInitializedProcesses{};
|
|
|
|
RefPtr<ScriptPreloader> mChildCache;
|
|
ScriptCacheChild* mChildActor = nullptr;
|
|
|
|
nsString mBaseName;
|
|
|
|
nsCOMPtr<nsIFile> mProfD;
|
|
nsCOMPtr<nsIThread> mSaveThread;
|
|
|
|
// The mmapped cache data from this session's cache file.
|
|
AutoMemMap mCacheData;
|
|
|
|
Monitor mMonitor;
|
|
Monitor mSaveMonitor;
|
|
};
|
|
|
|
} // namespace mozilla
|
|
|
|
#endif // ScriptPreloader_h
|