From 38ebe42aaabef2820e6ba1b71bc42c8d9ada1e68 Mon Sep 17 00:00:00 2001 From: Jon Coppeard Date: Mon, 8 Aug 2016 10:13:47 +0100 Subject: [PATCH] Bug 1288715 - Refactor SharedScriptData to prepare for making it refcounted r=till --- js/src/jsscript.cpp | 178 +++++++++++++++++++++--------------------- js/src/jsscript.h | 183 +++++++++++++++++++++++++++----------------- 2 files changed, 198 insertions(+), 163 deletions(-) diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index b23a8b1b8bc9..0dce45cd7402 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -586,9 +586,6 @@ FindScopeObjectIndex(JSScript* script, NestedStaticScope& scope) MOZ_CRASH("Scope not found"); } -static bool -SaveSharedScriptData(ExclusiveContext*, Handle, SharedScriptData*, uint32_t); - enum XDRClassKind { CK_BlockObject = 0, CK_WithObject = 1, @@ -880,7 +877,6 @@ js::XDRScript(XDRState* xdr, HandleObject enclosingScopeArg, HandleScript MOZ_ASSERT(!script->mainOffset()); script->mainOffset_ = prologueLength; - script->setLength(length); script->funLength_ = funLength; scriptp.set(script); @@ -954,22 +950,18 @@ js::XDRScript(XDRState* xdr, HandleObject enclosingScopeArg, HandleScript script->nslots_ = nslots; } - jsbytecode* code = script->code(); - SharedScriptData* ssd; + auto scriptDataGuard = mozilla::MakeScopeExit([&] { + if (mode == XDR_DECODE) + script->freeScriptData(); + }); + if (mode == XDR_DECODE) { - ssd = SharedScriptData::new_(cx, length, nsrcnotes, natoms); - if (!ssd) + if (!script->createScriptData(cx, length, nsrcnotes, natoms)) return false; - code = ssd->data; - if (natoms != 0) { - script->natoms_ = natoms; - script->atoms = ssd->atoms(); - } } + jsbytecode* code = script->code(); if (!xdr->codeBytes(code, length) || !xdr->codeBytes(code + length, nsrcnotes)) { - if (mode == XDR_DECODE) - js_free(ssd); return false; } @@ -978,16 +970,17 @@ js::XDRScript(XDRState* xdr, HandleObject enclosingScopeArg, HandleScript RootedAtom tmp(cx); if (!XDRAtom(xdr, &tmp)) return false; - script->atoms[i].init(tmp); + script->atoms()[i].init(tmp); } else { - RootedAtom tmp(cx, script->atoms[i]); + RootedAtom tmp(cx, script->atoms()[i]); if (!XDRAtom(xdr, &tmp)) return false; } } + scriptDataGuard.release(); if (mode == XDR_DECODE) { - if (!SaveSharedScriptData(cx, script, ssd, nsrcnotes)) + if (!script->shareScriptData(cx)) return false; } @@ -2416,53 +2409,69 @@ SharedScriptData* js::SharedScriptData::new_(ExclusiveContext* cx, uint32_t codeLength, uint32_t srcnotesLength, uint32_t natoms) { - /* - * Ensure the atoms are aligned, as some architectures don't allow unaligned - * access. - */ - const uint32_t pointerSize = sizeof(JSAtom*); - const uint32_t pointerMask = pointerSize - 1; - const uint32_t dataOffset = offsetof(SharedScriptData, data); - uint32_t baseLength = codeLength + srcnotesLength; - uint32_t padding = (pointerSize - ((baseLength + dataOffset) & pointerMask)) & pointerMask; - uint32_t length = baseLength + padding + pointerSize * natoms; - - SharedScriptData* entry = reinterpret_cast( - cx->zone()->pod_malloc(length + dataOffset)); + uint32_t dataLength = natoms * sizeof(GCPtrAtom) + codeLength + srcnotesLength; + uint32_t allocLength = offsetof(SharedScriptData, data_) + dataLength; + auto entry = reinterpret_cast(cx->zone()->pod_malloc(allocLength)); if (!entry) { ReportOutOfMemory(cx); return nullptr; } - entry->length = length; - entry->natoms = natoms; - entry->marked = false; - memset(entry->data + baseLength, 0, padding); + entry->dataLength_ = dataLength; + entry->natoms_ = natoms; + entry->codeLength_ = codeLength; + entry->marked_ = false; /* * Call constructors to initialize the storage that will be accessed as a * GCPtrAtom array via atoms(). */ GCPtrAtom* atoms = entry->atoms(); - MOZ_ASSERT(reinterpret_cast(atoms) % sizeof(JSAtom*) == 0); + MOZ_ASSERT(reinterpret_cast(atoms) % sizeof(GCPtrAtom*) == 0); for (unsigned i = 0; i < natoms; ++i) new (&atoms[i]) GCPtrAtom(); return entry; } +bool +JSScript::createScriptData(ExclusiveContext* cx, uint32_t codeLength, uint32_t srcnotesLength, + uint32_t natoms) +{ + MOZ_ASSERT(!scriptData()); + SharedScriptData* ssd = SharedScriptData::new_(cx, codeLength, srcnotesLength, natoms); + if (!ssd) + return false; + + setScriptData(ssd); + return true; +} + +void +JSScript::freeScriptData() +{ + js_free(scriptData_); + scriptData_ = nullptr; +} + +void +JSScript::setScriptData(js::SharedScriptData* data) +{ + MOZ_ASSERT(!scriptData_); + scriptData_ = data; +} + /* * 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. */ -static bool -SaveSharedScriptData(ExclusiveContext* cx, Handle script, SharedScriptData* ssd, - uint32_t nsrcnotes) +bool +JSScript::shareScriptData(ExclusiveContext* cx) { - MOZ_ASSERT(script != nullptr); - MOZ_ASSERT(ssd != nullptr); + SharedScriptData* ssd = scriptData(); + MOZ_ASSERT(ssd); AutoLockForExclusiveAccess lock(cx); @@ -2470,13 +2479,12 @@ SaveSharedScriptData(ExclusiveContext* cx, Handle script, SharedScrip ScriptDataTable::AddPtr p = cx->scriptDataTable(lock).lookupForAdd(l); if (p) { - js_free(ssd); - ssd = *p; + MOZ_ASSERT(ssd != *p); + freeScriptData(); + setScriptData(*p); } else { if (!cx->scriptDataTable(lock).add(p, ssd)) { - script->setCode(nullptr); - script->atoms = nullptr; - js_free(ssd); + freeScriptData(); ReportOutOfMemory(cx); return false; } @@ -2491,35 +2499,19 @@ SaveSharedScriptData(ExclusiveContext* cx, Handle script, SharedScrip if (cx->isJSContext()) { JSContext* ncx = cx->asJSContext(); if (JS::IsIncrementalGCInProgress(ncx) && ncx->gc.isFullGc()) - ssd->marked = true; + scriptData()->setMarked(true); } - script->setCode(ssd->data); - script->atoms = ssd->atoms(); return true; } -static inline void -MarkScriptData(JSRuntime* rt, const jsbytecode* bytecode) -{ - /* - * As an invariant, a ScriptBytecodeEntry should not be 'marked' outside of - * a GC. Since SweepScriptBytecodes is only called during a full gc, - * to preserve this invariant, only mark during a full gc. - */ - if (rt->gc.isFullGc()) - SharedScriptData::fromBytecode(bytecode)->marked = true; -} - void js::UnmarkScriptData(JSRuntime* rt, AutoLockForExclusiveAccess& lock) { MOZ_ASSERT(rt->gc.isFullGc()); ScriptDataTable& table = rt->scriptDataTable(lock); - for (ScriptDataTable::Enum e(table); !e.empty(); e.popFront()) { - SharedScriptData* entry = e.front(); - entry->marked = false; - } + for (ScriptDataTable::Enum e(table); !e.empty(); e.popFront()) + e.front()->setMarked(false); } void @@ -2533,7 +2525,7 @@ js::SweepScriptData(JSRuntime* rt, AutoLockForExclusiveAccess& lock) for (ScriptDataTable::Enum e(table); !e.empty(); e.popFront()) { SharedScriptData* entry = e.front(); - if (!entry->marked) { + if (!entry->marked()) { js_free(entry); e.removeFront(); } @@ -2833,14 +2825,13 @@ JSScript::fullyInitTrivial(ExclusiveContext* cx, Handle script) if (!partiallyInit(cx, script, 0, 0, 0, 0, 0, 0)) return false; - SharedScriptData* ssd = SharedScriptData::new_(cx, 1, 1, 0); - if (!ssd) + if (!script->createScriptData(cx, 1, 1, 0)) return false; - ssd->data[0] = JSOP_RETRVAL; - ssd->data[1] = SRC_NULL; - script->setLength(1); - return SaveSharedScriptData(cx, script, ssd, 1); + jsbytecode* code = script->code(); + code[0] = JSOP_RETRVAL; + code[1] = SRC_NULL; + return script->shareScriptData(cx); } /* static */ void @@ -2926,19 +2917,16 @@ JSScript::fullyInitFromEmitter(ExclusiveContext* cx, HandleScript script, Byteco script->lineno_ = bce->firstLine; - script->setLength(prologueLength + mainLength); - script->natoms_ = natoms; - SharedScriptData* ssd = SharedScriptData::new_(cx, script->length(), nsrcnotes, natoms); - if (!ssd) + if (!script->createScriptData(cx, prologueLength + mainLength, nsrcnotes, natoms)) return false; - jsbytecode* code = ssd->data; + jsbytecode* code = script->code(); PodCopy(code, bce->prologue.code.begin(), prologueLength); PodCopy(code + prologueLength, bce->main.code.begin(), mainLength); bce->copySrcNotes((jssrcnote*)(code + script->length()), nsrcnotes); - InitAtomMap(bce->atomIndices.getMap(), ssd->atoms()); + InitAtomMap(bce->atomIndices.getMap(), script->atoms()); - if (!SaveSharedScriptData(cx, script, ssd, nsrcnotes)) + if (!script->shareScriptData(cx)) return false; if (bce->constList.length() != 0) @@ -3562,13 +3550,10 @@ js::detail::CopyScript(JSContext* cx, HandleObject scriptStaticScope, HandleScri memcpy(dst->data, src->data, size); /* Script filenames, bytecodes and atoms are runtime-wide. */ - dst->setCode(src->code()); - dst->atoms = src->atoms; + dst->setScriptData(src->scriptData()); - dst->setLength(src->length()); dst->lineno_ = src->lineno(); dst->mainOffset_ = src->mainOffset(); - dst->natoms_ = src->natoms(); dst->funLength_ = src->funLength(); dst->nTypeSets_ = src->nTypeSets(); dst->nslots_ = src->nslots(); @@ -3914,6 +3899,21 @@ JSScript::hasBreakpointsAt(jsbytecode* pc) return site->enabledCount > 0; } +void +SharedScriptData::traceChildren(JSTracer* trc) +{ + for (uint32_t i = 0; i < natoms(); ++i) + TraceNullableEdge(trc, &atoms()[i], "atom"); + + /* + * As an invariant, a ScriptBytecodeEntry should not be 'marked' outside of + * a GC. Since SweepScriptBytecodes is only called during a full gc, + * to preserve this invariant, only mark during a full gc. + */ + if (trc->isMarkingTracer() && trc->runtime()->gc.isFullGc()) + setMarked(true); +} + void JSScript::traceChildren(JSTracer* trc) { @@ -3926,10 +3926,8 @@ JSScript::traceChildren(JSTracer* trc) static_cast(trc)->shouldCheckCompartments(), zone()->isCollecting()); - if (atoms) { - for (uint32_t i = 0; i < natoms(); ++i) - TraceNullableEdge(trc, &atoms[i], "atom"); - } + if (scriptData()) + scriptData()->traceChildren(trc); if (hasObjects()) { ObjectArray* objarray = objects(); @@ -3952,13 +3950,9 @@ JSScript::traceChildren(JSTracer* trc) if (maybeLazyScript()) TraceManuallyBarrieredEdge(trc, &lazyScript, "lazyScript"); - if (trc->isMarkingTracer()) { + if (trc->isMarkingTracer()) compartment()->mark(); - if (code()) - MarkScriptData(trc->runtime(), code()); - } - bindings.trace(trc); jit::TraceJitScripts(trc, this); diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 69a6568938bd..3559cd96b735 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -934,6 +934,89 @@ template bool XDRScriptConst(XDRState* xdr, MutableHandleValue vp); +/* + * Common data that can be shared between many scripts in a single runtime. + */ +class SharedScriptData +{ + uint32_t dataLength_; + uint32_t natoms_; + uint32_t codeLength_; + mozilla::Atomic marked_; + uintptr_t data_[1]; + + public: + static SharedScriptData* new_(ExclusiveContext* cx, uint32_t codeLength, + uint32_t srcnotesLength, uint32_t natoms); + + uint32_t dataLength() const { + return dataLength_; + } + uint8_t* data() { + return reinterpret_cast(data_); + } + + uint32_t natoms() const { + return natoms_; + } + GCPtrAtom* atoms() { + if (!natoms_) + return nullptr; + return reinterpret_cast(data()); + } + + uint32_t codeLength() const { + return codeLength_; + } + jsbytecode* code() { + return reinterpret_cast(data() + natoms_ * sizeof(GCPtrAtom)); + } + + bool marked() const { + return marked_; + } + void setMarked(bool marked) { + marked_ = marked; + } + + void traceChildren(JSTracer* trc); + + private: + SharedScriptData() = delete; + SharedScriptData(const SharedScriptData&) = delete; + SharedScriptData& operator=(const SharedScriptData&) = delete; +}; + +struct ScriptBytecodeHasher +{ + struct Lookup + { + const uint8_t* data; + uint32_t length; + + explicit Lookup(SharedScriptData* ssd) : data(ssd->data()), length(ssd->dataLength()) {} + }; + static HashNumber hash(const Lookup& l) { return mozilla::HashBytes(l.data, l.length); } + static bool match(SharedScriptData* entry, const Lookup& lookup) { + if (entry->dataLength() != lookup.length) + return false; + return mozilla::PodEqual(entry->data(), lookup.data, lookup.length); + } +}; + +typedef HashSet ScriptDataTable; + +extern void +UnmarkScriptData(JSRuntime* rt, AutoLockForExclusiveAccess& lock); + +extern void +SweepScriptData(JSRuntime* rt, AutoLockForExclusiveAccess& lock); + +extern void +FreeScriptData(JSRuntime* rt, AutoLockForExclusiveAccess& lock); + } /* namespace js */ class JSScript : public js::gc::TenuredCell @@ -980,13 +1063,11 @@ class JSScript : public js::gc::TenuredCell // Word-sized fields. private: - jsbytecode* code_; /* bytecodes and their immediate operands */ + js::SharedScriptData* scriptData_; public: uint8_t* data; /* pointer to variable-length data array (see comment above Create() for details) */ - js::GCPtrAtom* atoms; /* maps immediate index to literal struct */ - JSCompartment* compartment_; private: @@ -1026,7 +1107,6 @@ class JSScript : public js::gc::TenuredCell // 32-bit fields. - uint32_t length_; /* length of code vector */ uint32_t dataSize_; /* size of the used part of the data array */ uint32_t lineno_; /* base line number of script */ @@ -1035,7 +1115,6 @@ class JSScript : public js::gc::TenuredCell uint32_t mainOffset_;/* offset of main entry point from code, after predef'ing prologue */ - uint32_t natoms_; /* length of atoms array */ uint32_t nslots_; /* vars plus maximum stack depth */ /* Range of characters in scriptSource which contains this script's source. */ @@ -1200,7 +1279,7 @@ class JSScript : public js::gc::TenuredCell // instead of private to suppress -Wunused-private-field compiler warnings. protected: #if JS_BITS_PER_WORD == 32 - // No padding currently required. + uint32_t padding_; #endif // @@ -1248,16 +1327,20 @@ class JSScript : public js::gc::TenuredCell void setVersion(JSVersion v) { version = v; } - // Script bytecode is immutable after creation. - jsbytecode* code() const { - return code_; - } - size_t length() const { - return length_; + js::SharedScriptData* scriptData() { + return scriptData_; } - void setCode(jsbytecode* code) { code_ = code; } - void setLength(size_t length) { length_ = length; } + // Script bytecode is immutable after creation. + jsbytecode* code() const { + if (!scriptData_) + return nullptr; + return scriptData_->code(); + } + size_t length() const { + MOZ_ASSERT(scriptData_); + return scriptData_->codeLength(); + } jsbytecode* codeEnd() const { return code() + length(); } @@ -1714,6 +1797,12 @@ class JSScript : public js::gc::TenuredCell private: bool makeTypes(JSContext* cx); + bool createScriptData(js::ExclusiveContext* cx, uint32_t codeLength, uint32_t srcnotesLength, + uint32_t natoms); + bool shareScriptData(js::ExclusiveContext* cx); + void freeScriptData(); + void setScriptData(js::SharedScriptData* data); + public: uint32_t getWarmUpCount() const { return warmUpCount; } uint32_t incWarmUpCounter(uint32_t amount = 1) { return warmUpCount += amount; } @@ -1807,11 +1896,18 @@ class JSScript : public js::gc::TenuredCell bool hasLoops(); - size_t natoms() const { return natoms_; } + size_t natoms() const { + MOZ_ASSERT(scriptData_); + return scriptData_->natoms(); + } + js::GCPtrAtom* atoms() const { + MOZ_ASSERT(scriptData_); + return scriptData_->atoms(); + } js::GCPtrAtom& getAtom(size_t index) const { MOZ_ASSERT(index < natoms()); - return atoms[index]; + return atoms()[index]; } js::GCPtrAtom& getAtom(jsbytecode* pc) const { @@ -2411,61 +2507,6 @@ class LazyScript : public gc::TenuredCell /* If this fails, add/remove padding within LazyScript. */ JS_STATIC_ASSERT(sizeof(LazyScript) % js::gc::CellSize == 0); -struct SharedScriptData -{ - uint32_t length; - uint32_t natoms; - mozilla::Atomic marked; - jsbytecode data[1]; - - static SharedScriptData* new_(ExclusiveContext* cx, uint32_t codeLength, - uint32_t srcnotesLength, uint32_t natoms); - - GCPtrAtom* atoms() { - if (!natoms) - return nullptr; - return reinterpret_cast(data + length - sizeof(JSAtom*) * natoms); - } - - static SharedScriptData* fromBytecode(const jsbytecode* bytecode) { - return (SharedScriptData*)(bytecode - offsetof(SharedScriptData, data)); - } - - private: - SharedScriptData() = delete; - SharedScriptData(const SharedScriptData&) = delete; -}; - -struct ScriptBytecodeHasher -{ - struct Lookup - { - jsbytecode* code; - uint32_t length; - - explicit Lookup(SharedScriptData* ssd) : code(ssd->data), length(ssd->length) {} - }; - static HashNumber hash(const Lookup& l) { return mozilla::HashBytes(l.code, l.length); } - static bool match(SharedScriptData* entry, const Lookup& lookup) { - if (entry->length != lookup.length) - return false; - return mozilla::PodEqual(entry->data, lookup.code, lookup.length); - } -}; - -typedef HashSet ScriptDataTable; - -extern void -UnmarkScriptData(JSRuntime* rt, AutoLockForExclusiveAccess& lock); - -extern void -SweepScriptData(JSRuntime* rt, AutoLockForExclusiveAccess& lock); - -extern void -FreeScriptData(JSRuntime* rt, AutoLockForExclusiveAccess& lock); - struct ScriptAndCounts { /* This structure is stored and marked from the JSRuntime. */