Bug 1562272 - Add js::RuntimeScriptData type. r=jandem

This splits the ref-count off from js::SharedScriptData. The JSScript
points to the RuntimeScriptData which points to the SharedScriptData.

This is precursor work to allowing script data to be shared between
processes.

Differential Revision: https://phabricator.services.mozilla.com/D36352

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Ted Campbell 2019-07-09 16:16:44 +00:00
Родитель f0628ffad3
Коммит 91f73ba53a
6 изменённых файлов: 183 добавлений и 88 удалений

Просмотреть файл

@ -478,6 +478,7 @@ void BaselineInterpreterCodeGen::loadScriptAtom(Register index, Register dest) {
MOZ_ASSERT(index != dest);
loadScript(dest);
masm.loadPtr(Address(dest, JSScript::offsetOfScriptData()), dest);
masm.loadPtr(Address(dest, RuntimeScriptData::offsetOfSSD()), dest);
masm.loadPtr(
BaseIndex(dest, index, ScalePointer, SharedScriptData::offsetOfAtoms()),
dest);
@ -530,6 +531,7 @@ void BaselineInterpreterCodeGen::emitInitializeLocals() {
Register scratch = R0.scratchReg();
loadScript(scratch);
masm.loadPtr(Address(scratch, JSScript::offsetOfScriptData()), scratch);
masm.loadPtr(Address(scratch, RuntimeScriptData::offsetOfSSD()), scratch);
masm.load32(Address(scratch, SharedScriptData::offsetOfNfixed()), scratch);
Label top, done;
@ -897,6 +899,7 @@ void BaselineInterpreterCodeGen::subtractScriptSlotsSize(Register reg,
MOZ_ASSERT(reg != scratch);
loadScript(scratch);
masm.loadPtr(Address(scratch, JSScript::offsetOfScriptData()), scratch);
masm.loadPtr(Address(scratch, RuntimeScriptData::offsetOfSSD()), scratch);
masm.load32(Address(scratch, SharedScriptData::offsetOfNslots()), scratch);
static_assert(sizeof(Value) == 8,
"shift by 3 below assumes Value is 8 bytes");
@ -1220,6 +1223,7 @@ void BaselineInterpreterCodeGen::emitInitFrameFields(Register nonFunctionEnv) {
// Initialize interpreter pc.
masm.loadPtr(Address(scratch1, JSScript::offsetOfScriptData()), scratch1);
masm.loadPtr(Address(scratch1, RuntimeScriptData::offsetOfSSD()), scratch1);
masm.load32(Address(scratch1, SharedScriptData::offsetOfCodeOffset()),
scratch2);
masm.addPtr(scratch2, scratch1);
@ -4860,8 +4864,9 @@ template <typename Handler>
void BaselineCodeGen<Handler>::emitInterpJumpToResumeEntry(Register script,
Register resumeIndex,
Register scratch) {
// Load JSScript::scriptData() into |script|.
// Load JSScript::sharedScriptData() into |script|.
masm.loadPtr(Address(script, JSScript::offsetOfScriptData()), script);
masm.loadPtr(Address(script, RuntimeScriptData::offsetOfSSD()), script);
// Load the resume pcOffset in |resumeIndex|.
masm.load32(Address(script, SharedScriptData::offsetOfResumeOffsetsOffset()),

Просмотреть файл

@ -2705,6 +2705,7 @@ bool CacheIRCompiler::emitLoadFunctionLengthResult() {
// Load the length from the function's script.
masm.loadPtr(Address(obj, JSFunction::offsetOfScript()), scratch);
masm.loadPtr(Address(scratch, JSScript::offsetOfScriptData()), scratch);
masm.loadPtr(Address(scratch, RuntimeScriptData::offsetOfSSD()), scratch);
masm.load16ZeroExtend(Address(scratch, SharedScriptData::offsetOfFunLength()),
scratch);

Просмотреть файл

@ -13777,6 +13777,7 @@ void CodeGenerator::visitFinishBoundFunctionInit(
// Load the length property of an interpreted function.
masm.loadPtr(Address(target, JSFunction::offsetOfScript()), temp1);
masm.loadPtr(Address(temp1, JSScript::offsetOfScriptData()), temp1);
masm.loadPtr(Address(temp1, RuntimeScriptData::offsetOfSSD()), temp1);
masm.load16ZeroExtend(Address(temp1, SharedScriptData::offsetOfFunLength()),
temp1);
}

Просмотреть файл

@ -825,7 +825,7 @@ XDRResult SharedScriptData::XDR(XDRState<mode>* xdr, HandleScript script) {
SharedScriptData* ssd = nullptr;
if (mode == XDR_ENCODE) {
ssd = script->scriptData();
ssd = script->sharedScriptData();
natoms = ssd->natoms();
codeLength = ssd->codeLength();
@ -849,7 +849,7 @@ XDRResult SharedScriptData::XDR(XDRState<mode>* xdr, HandleScript script) {
numTryNotes)) {
return xdr->fail(JS::TranscodeResult_Throw);
}
ssd = script->scriptData();
ssd = script->sharedScriptData();
}
MOZ_TRY(xdr->codeUint32(&ssd->mainOffset));
@ -908,6 +908,28 @@ template
XDRResult
SharedScriptData::XDR(XDRState<XDR_DECODE>* xdr, HandleScript script);
/* static */ size_t RuntimeScriptData::AllocationSize() {
size_t size = sizeof(RuntimeScriptData);
return size;
}
// Placement-new elements of an array. This should optimize away for types with
// trivial default initiation.
template <typename T>
void RuntimeScriptData::initElements(size_t offset, size_t length) {
uintptr_t base = reinterpret_cast<uintptr_t>(this);
DefaultInitializeElements<T>(reinterpret_cast<void*>(base + offset), length);
}
RuntimeScriptData::RuntimeScriptData() {
// Variable-length data begins immediately after RuntimeScriptData itself.
size_t cursor = sizeof(*this);
// Sanity check
MOZ_ASSERT(AllocationSize() == cursor);
}
template <XDRMode mode>
XDRResult js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope,
HandleScriptSourceObject sourceObjectArg,
@ -3494,6 +3516,22 @@ SharedScriptData* js::SharedScriptData::new_(
numScopeNotes, numTryNotes);
}
RuntimeScriptData* js::RuntimeScriptData::new_(JSContext* cx) {
// Compute size including trailing arrays
size_t size = AllocationSize();
// Allocate contiguous raw buffer
void* raw = cx->pod_malloc<uint8_t>(size);
MOZ_ASSERT(uintptr_t(raw) % alignof(RuntimeScriptData) == 0);
if (!raw) {
return nullptr;
}
// Constuct the RuntimeScriptData. Trailing arrays are uninitialized but
// GCPtrs are put into a safe state.
return new (raw) RuntimeScriptData();
}
bool JSScript::createSharedScriptData(JSContext* cx, uint32_t codeLength,
uint32_t noteLength, uint32_t natoms,
uint32_t numResumeOffsets,
@ -3509,43 +3547,51 @@ bool JSScript::createSharedScriptData(JSContext* cx, uint32_t codeLength,
#endif
MOZ_ASSERT(!scriptData_);
scriptData_ =
RefPtr<RuntimeScriptData> rsd(RuntimeScriptData::new_(cx));
if (!rsd) {
return false;
}
js::UniquePtr<SharedScriptData> ssd(
SharedScriptData::new_(cx, codeLength, noteLength, natoms,
numResumeOffsets, numScopeNotes, numTryNotes);
return !!scriptData_;
numResumeOffsets, numScopeNotes, numTryNotes));
if (!ssd) {
return false;
}
rsd->ssd_ = std::move(ssd);
scriptData_ = std::move(rsd);
return true;
}
void JSScript::freeScriptData() { scriptData_ = nullptr; }
/*
* Takes ownership of its *ssd parameter and either adds it into the runtime's
* ScriptDataTable or frees it if a matching entry already exists.
*
* Sets the |code| and |atoms| fields on the given JSScript.
*/
// Takes owndership of the script's scriptData_ and either adds it into the
// runtime's ScriptDataTable or frees it if a matching entry already exists.
bool JSScript::shareScriptData(JSContext* cx) {
SharedScriptData* ssd = scriptData();
MOZ_ASSERT(ssd);
MOZ_ASSERT(ssd->refCount() == 1);
RuntimeScriptData* rsd = scriptData();
MOZ_ASSERT(rsd);
MOZ_ASSERT(rsd->refCount() == 1);
// Calculate the hash before taking the lock. Because the data is reference
// counted, it also will be freed after releasing the lock if necessary.
SharedScriptDataHasher::Lookup lookup(ssd);
SharedScriptDataHasher::Lookup lookup(rsd);
AutoLockScriptData lock(cx->runtime());
ScriptDataTable::AddPtr p = cx->scriptDataTable(lock).lookupForAdd(lookup);
if (p) {
MOZ_ASSERT(ssd != *p);
MOZ_ASSERT(rsd != *p);
scriptData_ = *p;
} else {
if (!cx->scriptDataTable(lock).add(p, ssd)) {
if (!cx->scriptDataTable(lock).add(p, rsd)) {
ReportOutOfMemory(cx);
return false;
}
// Being in the table counts as a reference on the script data.
ssd->AddRef();
rsd->AddRef();
}
// Refs: JSScript, ScriptDataTable
@ -3562,7 +3608,7 @@ void js::SweepScriptData(JSRuntime* rt) {
ScriptDataTable& table = rt->scriptDataTable(lock);
for (ScriptDataTable::Enum e(table); !e.empty(); e.popFront()) {
SharedScriptData* scriptData = e.front();
RuntimeScriptData* scriptData = e.front();
if (scriptData->refCount() == 1) {
scriptData->Release();
e.removeFront();
@ -3590,9 +3636,9 @@ void js::FreeScriptData(JSRuntime* rt) {
for (ScriptDataTable::Enum e(table); !e.empty(); e.popFront()) {
#ifdef DEBUG
if (++numLive <= maxCells) {
SharedScriptData* scriptData = e.front();
RuntimeScriptData* scriptData = e.front();
fprintf(stderr,
"ERROR: GC found live SharedScriptData %p with ref count %d at "
"ERROR: GC found live RuntimeScriptData %p with ref count %d at "
"shutdown\n",
scriptData, scriptData->refCount());
}
@ -3888,10 +3934,10 @@ bool JSScript::initFunctionPrototype(JSContext* cx, HandleScript script,
return false;
}
jsbytecode* code = script->scriptData_->code();
jsbytecode* code = script->sharedScriptData()->code();
code[0] = JSOP_RETRVAL;
jssrcnote* notes = script->scriptData_->notes();
jssrcnote* notes = script->sharedScriptData()->notes();
notes[0] = SRC_NULL;
notes[1] = SRC_NULL;
notes[2] = SRC_NULL;
@ -4945,7 +4991,7 @@ bool JSScript::hasBreakpointsAt(jsbytecode* pc) {
return false;
}
js::SharedScriptData* data = script->scriptData_;
js::SharedScriptData* data = script->sharedScriptData();
// Initialize POD fields
data->mainOffset = bce->mainOffset();
@ -4976,7 +5022,6 @@ bool JSScript::hasBreakpointsAt(jsbytecode* pc) {
}
void SharedScriptData::traceChildren(JSTracer* trc) {
MOZ_ASSERT(refCount() != 0);
for (uint32_t i = 0; i < natoms(); ++i) {
TraceNullableEdge(trc, &atoms()[i], "atom");
}
@ -4988,6 +5033,16 @@ void SharedScriptData::markForCrossZone(JSContext* cx) {
}
}
void RuntimeScriptData::traceChildren(JSTracer* trc) {
MOZ_ASSERT(refCount() != 0);
ssd_->traceChildren(trc);
}
void RuntimeScriptData::markForCrossZone(JSContext* cx) {
ssd_->markForCrossZone(cx);
}
void JSScript::traceChildren(JSTracer* trc) {
// NOTE: this JSScript may be partially initialized at this point. E.g. we
// may have created it and partially initialized it with

Просмотреть файл

@ -1574,13 +1574,6 @@ class alignas(uintptr_t) PrivateScriptData final {
// offset of the array from the start offset of the subsequent array. The
// notable exception is that bytecode length is stored explicitly.
class alignas(uintptr_t) SharedScriptData final {
// This class is reference counted as follows: each pointer from a JSScript
// counts as one reference plus there may be one reference from the shared
// script data table.
mozilla::Atomic<uint32_t, mozilla::SequentiallyConsistent,
mozilla::recordreplay::Behavior::DontPreserve>
refCount_ = {};
uint32_t codeOffset_ = 0; // Byte-offset from 'this'
uint32_t codeLength_ = 0;
uint32_t optArrayOffset_ = 0;
@ -1717,39 +1710,12 @@ class alignas(uintptr_t) SharedScriptData final {
return nullLength;
}
uint32_t refCount() const { return refCount_; }
void AddRef() { refCount_++; }
void Release() {
MOZ_ASSERT(refCount_ != 0);
uint32_t remain = --refCount_;
if (remain == 0) {
js_free(this);
}
}
// Span over all raw bytes in this struct and its trailing arrays.
mozilla::Span<const uint8_t> allocSpan() const {
mozilla::Span<const uint8_t> immutableData() const {
size_t allocSize = endOffset();
return mozilla::MakeSpan(reinterpret_cast<const uint8_t*>(this), allocSize);
}
// Span over all immutable bytes in allocation. This excludes part of
// structure used for reference counting and is the basis of how we
// de-duplicate data.
mozilla::Span<const uint8_t> immutableData() const {
// The refCount_ must be first field of structure.
static_assert(offsetof(SharedScriptData, refCount_) == 0,
"refCount_ must be at start of SharedScriptData");
constexpr size_t dataOffset = sizeof(refCount_);
static_assert(offsetof(SharedScriptData, numBytecodeTypeSets) +
sizeof(numBytecodeTypeSets) ==
sizeof(SharedScriptData),
"SharedScriptData should not have padding after last field");
return allocSpan().From(dataOffset);
}
uint32_t natoms() const {
return (flagOffset() - atomOffset()) / sizeof(GCPtrAtom);
}
@ -1816,25 +1782,87 @@ class alignas(uintptr_t) SharedScriptData final {
SharedScriptData& operator=(const SharedScriptData&) = delete;
};
struct SharedScriptDataHasher;
// Script data that is shareable across a JSRuntime.
class RuntimeScriptData final {
// This class is reference counted as follows: each pointer from a JSScript
// counts as one reference plus there may be one reference from the shared
// script data table.
mozilla::Atomic<uint32_t, mozilla::SequentiallyConsistent,
mozilla::recordreplay::Behavior::DontPreserve>
refCount_ = {};
js::UniquePtr<SharedScriptData> ssd_ = nullptr;
// NOTE: The raw bytes of this structure are used for hashing so use explicit
// padding values as needed for predicatable results across compilers.
friend class ::JSScript;
friend class js::SharedScriptData;
friend struct js::SharedScriptDataHasher;
private:
// Size to allocate.
static size_t AllocationSize();
template <typename T>
void initElements(size_t offset, size_t length);
// Initialize to GC-safe state.
RuntimeScriptData();
public:
static RuntimeScriptData* new_(JSContext* cx);
uint32_t refCount() const { return refCount_; }
void AddRef() { refCount_++; }
void Release() {
MOZ_ASSERT(refCount_ != 0);
uint32_t remain = --refCount_;
if (remain == 0) {
ssd_ = nullptr;
js_free(this);
}
}
static constexpr size_t offsetOfSSD() {
return offsetof(RuntimeScriptData, ssd_);
}
void traceChildren(JSTracer* trc);
// Mark this RuntimeScriptData for use in a new zone.
void markForCrossZone(JSContext* cx);
size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) {
return mallocSizeOf(this) + mallocSizeOf(ssd_.get());
}
// RuntimeScriptData has trailing data so isn't copyable or movable.
RuntimeScriptData(const RuntimeScriptData&) = delete;
RuntimeScriptData& operator=(const RuntimeScriptData&) = delete;
};
// Two SharedScriptData instances may be de-duplicated if they have the same
// data in their immutableData() span. This Hasher enables that comparison.
struct SharedScriptDataHasher {
using Lookup = RefPtr<SharedScriptData>;
using Lookup = RefPtr<RuntimeScriptData>;
static HashNumber hash(const Lookup& l) {
mozilla::Span<const uint8_t> immutableData = l->immutableData();
mozilla::Span<const uint8_t> immutableData = l->ssd_->immutableData();
return mozilla::HashBytes(immutableData.data(), immutableData.size());
}
static bool match(SharedScriptData* entry, const Lookup& lookup) {
return entry->immutableData() == lookup->immutableData();
static bool match(RuntimeScriptData* entry, const Lookup& lookup) {
return entry->ssd_->immutableData() == lookup->ssd_->immutableData();
}
};
class AutoLockScriptData;
using ScriptDataTable =
HashSet<SharedScriptData*, SharedScriptDataHasher, SystemAllocPolicy>;
HashSet<RuntimeScriptData*, SharedScriptDataHasher, SystemAllocPolicy>;
extern void SweepScriptData(JSRuntime* rt);
@ -1860,7 +1888,7 @@ class JSScript : public js::gc::TenuredCell {
uint8_t* jitCodeRaw_ = nullptr;
// Shareable script data
RefPtr<js::SharedScriptData> scriptData_ = {};
RefPtr<js::RuntimeScriptData> scriptData_ = {};
// Unshared variable-length data
js::PrivateScriptData* data_ = nullptr;
@ -2223,14 +2251,17 @@ class JSScript : public js::gc::TenuredCell {
JS::Compartment* maybeCompartment() const { return compartment(); }
JS::Realm* realm() const { return realm_; }
js::SharedScriptData* scriptData() { return scriptData_; }
js::RuntimeScriptData* scriptData() { return scriptData_; }
js::SharedScriptData* sharedScriptData() const {
return scriptData_->ssd_.get();
}
// Script bytecode is immutable after creation.
jsbytecode* code() const {
if (!scriptData_) {
return nullptr;
}
return scriptData_->code();
return sharedScriptData()->code();
}
bool hasForceInterpreterOp() const {
@ -2253,7 +2284,7 @@ class JSScript : public js::gc::TenuredCell {
size_t length() const {
MOZ_ASSERT(scriptData_);
return scriptData_->codeLength();
return sharedScriptData()->codeLength();
}
jsbytecode* codeEnd() const { return code() + length(); }
@ -2282,7 +2313,7 @@ class JSScript : public js::gc::TenuredCell {
return code() + offset;
}
size_t mainOffset() const { return scriptData_->mainOffset; }
size_t mainOffset() const { return sharedScriptData()->mainOffset; }
uint32_t lineno() const { return lineno_; }
@ -2292,7 +2323,7 @@ class JSScript : public js::gc::TenuredCell {
// The fixed part of a stack frame is comprised of vars (in function and
// module code) and block-scoped locals (in all kinds of code).
size_t nfixed() const { return scriptData_->nfixed; }
size_t nfixed() const { return sharedScriptData()->nfixed; }
// Number of fixed slots reserved for slots that are always live. Only
// nonzero for function or module code.
@ -2309,7 +2340,7 @@ class JSScript : public js::gc::TenuredCell {
// Calculate the number of fixed slots that are live at a particular bytecode.
size_t calculateLiveFixed(jsbytecode* pc);
size_t nslots() const { return scriptData_->nslots; }
size_t nslots() const { return sharedScriptData()->nslots; }
unsigned numArgs() const {
if (bodyScope()->is<js::FunctionScope>()) {
@ -2339,12 +2370,12 @@ class JSScript : public js::gc::TenuredCell {
"MaxBytecodeTypeSets must match sizeof(numBytecodeTypeSets)");
size_t numBytecodeTypeSets() const {
return scriptData_->numBytecodeTypeSets;
return sharedScriptData()->numBytecodeTypeSets;
}
size_t numICEntries() const { return scriptData_->numICEntries; }
size_t numICEntries() const { return sharedScriptData()->numICEntries; }
size_t funLength() const { return scriptData_->funLength; }
size_t funLength() const { return sharedScriptData()->funLength; }
uint32_t sourceStart() const { return sourceStart_; }
@ -2740,7 +2771,7 @@ class JSScript : public js::gc::TenuredCell {
inline bool hasGlobal(const js::GlobalObject* global) const;
js::GlobalObject& uninlinedGlobal() const;
uint32_t bodyScopeIndex() const { return scriptData_->bodyScopeIndex; }
uint32_t bodyScopeIndex() const { return sharedScriptData()->bodyScopeIndex; }
js::Scope* bodyScope() const { return getScope(bodyScopeIndex()); }
@ -2879,10 +2910,12 @@ class JSScript : public js::gc::TenuredCell {
size_t dataSize() const { return dataSize_; }
bool hasTrynotes() const { return !scriptData_->tryNotes().empty(); }
bool hasScopeNotes() const { return !scriptData_->scopeNotes().empty(); }
bool hasTrynotes() const { return !sharedScriptData()->tryNotes().empty(); }
bool hasScopeNotes() const {
return !sharedScriptData()->scopeNotes().empty();
}
bool hasResumeOffsets() const {
return !scriptData_->resumeOffsets().empty();
return !sharedScriptData()->resumeOffsets().empty();
}
mozilla::Span<const JS::GCCellPtr> gcthings() const {
@ -2890,15 +2923,15 @@ class JSScript : public js::gc::TenuredCell {
}
mozilla::Span<const JSTryNote> trynotes() const {
return scriptData_->tryNotes();
return sharedScriptData()->tryNotes();
}
mozilla::Span<const js::ScopeNote> scopeNotes() const {
return scriptData_->scopeNotes();
return sharedScriptData()->scopeNotes();
}
mozilla::Span<const uint32_t> resumeOffsets() const {
return scriptData_->resumeOffsets();
return sharedScriptData()->resumeOffsets();
}
uint32_t tableSwitchCaseOffset(jsbytecode* pc, uint32_t caseIndex) const {
@ -2915,20 +2948,20 @@ class JSScript : public js::gc::TenuredCell {
uint32_t numNotes() const {
MOZ_ASSERT(scriptData_);
return scriptData_->noteLength();
return sharedScriptData()->noteLength();
}
jssrcnote* notes() const {
MOZ_ASSERT(scriptData_);
return scriptData_->notes();
return sharedScriptData()->notes();
}
size_t natoms() const {
MOZ_ASSERT(scriptData_);
return scriptData_->natoms();
return sharedScriptData()->natoms();
}
js::GCPtrAtom* atoms() const {
MOZ_ASSERT(scriptData_);
return scriptData_->atoms();
return sharedScriptData()->atoms();
}
js::GCPtrAtom& getAtom(size_t index) const {

Просмотреть файл

@ -375,7 +375,7 @@ void JSRuntime::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
scriptDataTable(lock).shallowSizeOfExcludingThis(mallocSizeOf);
for (ScriptDataTable::Range r = scriptDataTable(lock).all(); !r.empty();
r.popFront()) {
rtSizes->scriptData += mallocSizeOf(r.front());
rtSizes->scriptData += r.front()->sizeOfIncludingThis(mallocSizeOf);
}
}