From fb0cdfff2ec5554963aaa050253afcbc91ade191 Mon Sep 17 00:00:00 2001 From: Coroiu Cristina Date: Mon, 2 Dec 2019 21:27:55 +0200 Subject: [PATCH] Backed out 7 changesets (bug 1600439, bug 1566466, bug 1591598) for raptor crashes on a CLOSED TREE Backed out changeset 14f33b78f0eb (bug 1566466) Backed out changeset 617ce539db70 (bug 1600439) Backed out changeset 47f84263339e (bug 1600439) Backed out changeset 07471e96b2d8 (bug 1600439) Backed out changeset 5163670bfede (bug 1591598) Backed out changeset 5ea098cc9ef0 (bug 1591598) Backed out changeset 42ca239ee6a6 (bug 1591598) --- js/src/builtin/TestingFunctions.cpp | 2 +- js/src/debugger/Script.cpp | 46 +-- js/src/frontend/FullParseHandler.h | 33 +- js/src/gc/GCEnum.h | 1 + js/src/gc/Marking.cpp | 53 ++- js/src/gc/PublicIterators.cpp | 11 +- js/src/jit/JitScript.cpp | 3 +- js/src/vm/JSFunction.cpp | 4 +- js/src/vm/JSScript-inl.h | 22 - js/src/vm/JSScript.cpp | 363 ++++++++++------- js/src/vm/JSScript.h | 608 ++++++++++++++-------------- 11 files changed, 595 insertions(+), 551 deletions(-) diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index 5da946bcb924..f98b522be8dd 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -1108,7 +1108,7 @@ static bool IsRelazifiableFunction(JSContext* cx, unsigned argc, Value* vp) { JSFunction* fun = &args[0].toObject().as(); args.rval().setBoolean(fun->hasScript() && - fun->nonLazyScript()->isRelazifiable()); + fun->nonLazyScript()->isRelazifiableIgnoringJitCode()); return true; } diff --git a/js/src/debugger/Script.cpp b/js/src/debugger/Script.cpp index 293acf937bdc..b5cc0b9dbe18 100644 --- a/js/src/debugger/Script.cpp +++ b/js/src/debugger/Script.cpp @@ -532,28 +532,6 @@ static bool PushFunctionScript(JSContext* cx, Debugger* dbg, HandleFunction fun, return wrapped && NewbornArrayPush(cx, array, ObjectValue(*wrapped)); } -static bool PushInnerFunctions(JSContext* cx, Debugger* dbg, HandleObject array, - mozilla::Span gcThings) { - RootedFunction fun(cx); - - for (JS::GCCellPtr gcThing : gcThings) { - if (!gcThing.is()) { - continue; - } - - JSObject* obj = &gcThing.as(); - if (obj->is()) { - fun = &obj->as(); - - if (!PushFunctionScript(cx, dbg, fun, array)) { - return false; - } - } - } - - return true; -} - bool DebuggerScript::CallData::getChildScripts() { if (!ensureScriptMaybeLazy()) { return false; @@ -565,15 +543,31 @@ bool DebuggerScript::CallData::getChildScripts() { return false; } + RootedFunction fun(cx); if (obj->getReferent().is()) { RootedScript script(cx, obj->getReferent().as()); - if (!PushInnerFunctions(cx, dbg, result, script->gcthings())) { - return false; + for (JS::GCCellPtr gcThing : script->gcthings()) { + if (!gcThing.is()) { + continue; + } + + JSObject* obj = &gcThing.as(); + if (obj->is()) { + fun = &obj->as(); + + if (!PushFunctionScript(cx, dbg, fun, result)) { + return false; + } + } } } else { Rooted lazy(cx, obj->getReferent().as()); - if (!PushInnerFunctions(cx, dbg, result, lazy->gcthings())) { - return false; + + for (const GCPtrFunction& innerFun : lazy->innerFunctions()) { + fun = innerFun; + if (!PushFunctionScript(cx, dbg, fun, result)) { + return false; + } } } diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index a01ef667b929..db12a8fa84c4 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -102,22 +102,7 @@ class FullParseHandler { lazyOuterFunction_(cx, lazyOuterFunction), lazyInnerFunctionIndex(0), lazyClosedOverBindingIndex(0), - sourceKind_(kind) { - // The LazyScript::gcthings() array contains the inner function list - // followed by the closed-over bindings data. Advance the index for - // closed-over bindings to the end of the inner functions. The - // nextLazyInnerFunction / nextLazyClosedOverBinding accessors confirm we - // have the expected types. See also: LazyScript::Create. - if (lazyOuterFunction) { - for (JS::GCCellPtr gcThing : lazyOuterFunction->gcthings()) { - if (gcThing.is()) { - lazyClosedOverBindingIndex++; - } else { - break; - } - } - } - } + sourceKind_(kind) {} static NullNode null() { return NullNode(); } @@ -1051,17 +1036,15 @@ class FullParseHandler { bool canSkipLazyInnerFunctions() { return !!lazyOuterFunction_; } bool canSkipLazyClosedOverBindings() { return !!lazyOuterFunction_; } JSFunction* nextLazyInnerFunction() { - return &lazyOuterFunction_->gcthings()[lazyInnerFunctionIndex++] - .as() - .as(); + MOZ_ASSERT(lazyInnerFunctionIndex < + lazyOuterFunction_->numInnerFunctions()); + return lazyOuterFunction_->innerFunctions()[lazyInnerFunctionIndex++]; } JSAtom* nextLazyClosedOverBinding() { - // These entries are either JSAtom* or nullptr, so use the 'asCell()' - // accessor which is faster. - gc::Cell* cell = - lazyOuterFunction_->gcthings()[lazyClosedOverBindingIndex++].asCell(); - MOZ_ASSERT_IF(cell, cell->is()); - return static_cast(cell); + MOZ_ASSERT(lazyClosedOverBindingIndex < + lazyOuterFunction_->numClosedOverBindings()); + return lazyOuterFunction_ + ->closedOverBindings()[lazyClosedOverBindingIndex++]; } }; diff --git a/js/src/gc/GCEnum.h b/js/src/gc/GCEnum.h index c335aa6d4ba9..8652af75a5cb 100644 --- a/js/src/gc/GCEnum.h +++ b/js/src/gc/GCEnum.h @@ -98,6 +98,7 @@ enum class ZealMode { _(ObjectElements) \ _(ObjectSlots) \ _(ScriptPrivateData) \ + _(LazyScriptData) \ _(MapObjectTable) \ _(BigIntDigits) \ _(ScopeData) \ diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index da122c0f1a05..5c51e198eeb9 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -965,8 +965,6 @@ bool js::GCMarker::mark(T* thing) { void BaseScript::traceChildren(JSTracer* trc) { TraceEdge(trc, &functionOrGlobal_, "function"); TraceNullableEdge(trc, &sourceObject_, "sourceObject"); - - warmUpData_.trace(trc); } void LazyScript::traceChildren(JSTracer* trc) { @@ -977,8 +975,25 @@ void LazyScript::traceChildren(JSTracer* trc) { TraceNullableEdge(trc, &script_, "script"); } - if (data_) { - data_->trace(trc); + if (enclosingLazyScriptOrScope_) { + TraceGenericPointerRoot( + trc, + reinterpret_cast( + enclosingLazyScriptOrScope_.unsafeUnbarrieredForTracing()), + "enclosingScope or enclosingLazyScript"); + } + + // We rely on the fact that atoms are always tenured. + for (GCPtrAtom& closedOverBinding : closedOverBindings()) { + if (closedOverBinding) { + TraceEdge(trc, &closedOverBinding, "closedOverBinding"); + } + } + + for (GCPtrFunction& innerFunction : innerFunctions()) { + if (innerFunction) { + TraceEdge(trc, &innerFunction, "lazyScriptInnerFunction"); + } } if (trc->isMarkingTracer()) { @@ -992,20 +1007,26 @@ inline void js::GCMarker::eagerlyMarkChildren(LazyScript* thing) { traverseEdge(thing, static_cast(thing->sourceObject_)); } - thing->warmUpData_.trace(this); - // script_ is weak so is not traced here. - if (thing->data_) { - // Traverse the PrivateScriptData::gcthings() array. - for (JS::GCCellPtr& elem : thing->data_->gcthings()) { - if (elem.is()) { - traverseEdge(thing, &elem.as()); - } else if (elem.is()) { - traverseEdge(thing, &elem.as()); - } else { - MOZ_ASSERT(!elem); - } + if (thing->enclosingLazyScriptOrScope_) { + TraceManuallyBarrieredGenericPointerEdge( + this, + reinterpret_cast( + thing->enclosingLazyScriptOrScope_.unsafeUnbarrieredForTracing()), + "enclosingScope or enclosingLazyScript"); + } + + // We rely on the fact that atoms are always tenured. + for (GCPtrAtom& closedOverBinding : thing->closedOverBindings()) { + if (closedOverBinding) { + traverseEdge(thing, static_cast(closedOverBinding)); + } + } + + for (GCPtrFunction& innerFunction : thing->innerFunctions()) { + if (innerFunction) { + traverseEdge(thing, static_cast(innerFunction)); } } diff --git a/js/src/gc/PublicIterators.cpp b/js/src/gc/PublicIterators.cpp index aeb257fb95d7..c9193e426f23 100644 --- a/js/src/gc/PublicIterators.cpp +++ b/js/src/gc/PublicIterators.cpp @@ -86,12 +86,11 @@ static void TraverseInnerLazyScriptsForLazyScript( JSContext* cx, void* data, LazyScript* enclosingLazyScript, IterateLazyScriptCallback lazyScriptCallback, const JS::AutoRequireNoGC& nogc) { - for (JS::GCCellPtr gcThing : enclosingLazyScript->gcthings()) { - if (!gcThing.is()) { - continue; - } - - JSFunction* fun = &gcThing.as().as(); + for (JSFunction* fun : enclosingLazyScript->innerFunctions()) { + // LazyScript::CreateForXDR temporarily initializes innerFunctions with + // its own function, but it should be overwritten with correct + // inner functions before getting inserted into parent's innerFunctions. + MOZ_ASSERT(fun != enclosingLazyScript->function()); if (!fun->isInterpretedLazy()) { return; diff --git a/js/src/jit/JitScript.cpp b/js/src/jit/JitScript.cpp index cd09a6895a71..010e28b06b60 100644 --- a/js/src/jit/JitScript.cpp +++ b/js/src/jit/JitScript.cpp @@ -149,8 +149,9 @@ bool JSScript::createJitScript(JSContext* cx) { return false; } + MOZ_ASSERT(!hasJitScript()); prepareForDestruction.release(); - warmUpData_.initJitScript(jitScript.release()); + warmUpData_.setJitScript(jitScript.release()); AddCellMemory(this, allocSize.value(), MemoryUse::JitScript); // We have a JitScript so we can set the script's jitCodeRaw_ pointer to the diff --git a/js/src/vm/JSFunction.cpp b/js/src/vm/JSFunction.cpp index a2658b7917ff..72498179a112 100644 --- a/js/src/vm/JSFunction.cpp +++ b/js/src/vm/JSFunction.cpp @@ -1607,7 +1607,7 @@ static bool DelazifyCanonicalScriptedFunction(JSContext* cx, RootedScript script(cx, fun->nonLazyScript()); MOZ_ASSERT(lazy->maybeScript() == script); - if (script->isRelazifiable()) { + if (lazy->canRelazify()) { // Remember the lazy script on the compiled script, so it can be // stored on the function again in case of re-lazification. // Only functions without inner functions are re-lazified. @@ -1726,7 +1726,7 @@ void JSFunction::maybeRelazify(JSRuntime* rt) { // Don't relazify functions with JIT code. JSScript* script = nonLazyScript(); - if (!script->canRelazify()) { + if (!script->isRelazifiable()) { return; } diff --git a/js/src/vm/JSScript-inl.h b/js/src/vm/JSScript-inl.h index 7645b356aa8d..51d24f9d5ac1 100644 --- a/js/src/vm/JSScript-inl.h +++ b/js/src/vm/JSScript-inl.h @@ -58,28 +58,6 @@ ScriptAndCounts::ScriptAndCounts(ScriptAndCounts&& sac) void SetFrameArgumentsObject(JSContext* cx, AbstractFramePtr frame, HandleScript script, JSObject* argsobj); -inline void ScriptWarmUpData::initEnclosingScript(LazyScript* enclosingScript) { - MOZ_ASSERT(data_ == ResetState()); - setTaggedPtr(enclosingScript); - static_assert(std::is_base_of::value, - "LazyScript must be TenuredCell to avoid post-barriers"); -} -inline void ScriptWarmUpData::clearEnclosingScript() { - LazyScript::writeBarrierPre(toEnclosingScript()); - data_ = ResetState(); -} - -inline void ScriptWarmUpData::initEnclosingScope(Scope* enclosingScope) { - MOZ_ASSERT(data_ == ResetState()); - setTaggedPtr(enclosingScope); - static_assert(std::is_base_of::value, - "Scope must be TenuredCell to avoid post-barriers"); -} -inline void ScriptWarmUpData::clearEnclosingScope() { - Scope::writeBarrierPre(toEnclosingScope()); - data_ = ResetState(); -} - } // namespace js inline JSFunction* JSScript::getFunction(size_t index) { diff --git a/js/src/vm/JSScript.cpp b/js/src/vm/JSScript.cpp index b16cf0684c68..7635403ac06b 100644 --- a/js/src/vm/JSScript.cpp +++ b/js/src/vm/JSScript.cpp @@ -241,59 +241,27 @@ template XDRResult js::XDRScriptConst(XDRState*, // Code LazyScript's closed over bindings. template -/* static */ -XDRResult LazyScript::XDRScriptData(XDRState* xdr, - HandleScriptSourceObject sourceObject, - Handle lazy) { +static XDRResult XDRLazyClosedOverBindings(XDRState* xdr, + MutableHandle lazy) { JSContext* cx = xdr->cx(); - RootedAtom atom(cx); - RootedFunction func(cx); + for (GCPtrAtom& elem : lazy->closedOverBindings()) { + uint8_t endOfScopeSentinel; + if (mode == XDR_ENCODE) { + atom = elem.get(); + endOfScopeSentinel = !atom; + } - for (JS::GCCellPtr& elem : lazy->data_->gcthings()) { - JS::TraceKind kind = elem.kind(); + MOZ_TRY(xdr->codeUint8(&endOfScopeSentinel)); - MOZ_TRY(xdr->codeEnum32(&kind)); + if (endOfScopeSentinel) { + atom = nullptr; + } else { + MOZ_TRY(XDRAtom(xdr, &atom)); + } - switch (kind) { - case JS::TraceKind::Object: { - if (mode == XDR_ENCODE) { - func = &elem.as().as(); - } - MOZ_TRY(XDRInterpretedFunction(xdr, nullptr, sourceObject, &func)); - if (mode == XDR_DECODE) { - MOZ_ASSERT(func->isInterpretedLazy()); - func->setEnclosingLazyScript(lazy); - - elem = JS::GCCellPtr(func); - } - break; - } - - case JS::TraceKind::String: { - if (mode == XDR_ENCODE) { - gc::Cell* cell = elem.asCell(); - MOZ_ASSERT_IF(cell, cell->is()); - atom = static_cast(cell); - } - MOZ_TRY(XDRAtom(xdr, &atom)); - if (mode == XDR_DECODE) { - elem = JS::GCCellPtr(static_cast(atom)); - } - break; - } - - case JS::TraceKind::Null: { - // This is default so nothing to do. - MOZ_ASSERT(!elem); - break; - } - - default: { - // Fail in debug, but only soft-fail in release - MOZ_ASSERT(false, "Bad XDR class kind"); - return xdr->fail(JS::TranscodeResult_Failure_BadDecode); - } + if (mode == XDR_DECODE) { + elem.init(atom); } } @@ -311,10 +279,8 @@ static XDRResult XDRRelazificationInfo(XDRState* xdr, HandleFunction fun, JSContext* cx = xdr->cx(); - RootedScriptSourceObject sourceObject(cx, script->sourceObject()); - uint32_t immutableFlags; - uint32_t ngcthings; + uint32_t numClosedOverBindings; { uint32_t sourceStart = script->sourceStart(); uint32_t sourceEnd = script->sourceEnd(); @@ -326,13 +292,17 @@ static XDRResult XDRRelazificationInfo(XDRState* xdr, HandleFunction fun, if (mode == XDR_ENCODE) { immutableFlags = lazy->immutableFlags(); - ngcthings = lazy->gcthings().size(); + numClosedOverBindings = lazy->numClosedOverBindings(); MOZ_ASSERT(sourceStart == lazy->sourceStart()); MOZ_ASSERT(sourceEnd == lazy->sourceEnd()); MOZ_ASSERT(toStringStart == lazy->toStringStart()); MOZ_ASSERT(toStringEnd == lazy->toStringEnd()); MOZ_ASSERT(lineno == lazy->lineno()); MOZ_ASSERT(column == lazy->column()); + // We can assert we have no inner functions because we don't + // relazify scripts with inner functions. See + // JSFunction::delazifyLazilyInterpretedFunction. + MOZ_ASSERT(!lazy->hasInnerFunctions()); if (fun->kind() == FunctionFlags::FunctionKind::ClassConstructor) { numFieldInitializers = (uint32_t)lazy->getFieldInitializers().numFieldInitializers; @@ -343,13 +313,14 @@ static XDRResult XDRRelazificationInfo(XDRState* xdr, HandleFunction fun, MOZ_TRY(xdr->codeUint32(&immutableFlags)); MOZ_TRY(xdr->codeUint32(&numFieldInitializers)); - MOZ_TRY(xdr->codeUint32(&ngcthings)); + MOZ_TRY(xdr->codeUint32(&numClosedOverBindings)); if (mode == XDR_DECODE) { + RootedScriptSourceObject sourceObject(cx, script->sourceObject()); lazy.set(LazyScript::CreateForXDR( - cx, ngcthings, fun, script, enclosingScope, sourceObject, - immutableFlags, sourceStart, sourceEnd, toStringStart, toStringEnd, - lineno, column)); + cx, numClosedOverBindings, /* numInnerFunctions = */ 0, fun, script, + enclosingScope, sourceObject, immutableFlags, sourceStart, sourceEnd, + toStringStart, toStringEnd, lineno, column)); if (!lazy) { return xdr->fail(JS::TranscodeResult_Throw); } @@ -361,11 +332,11 @@ static XDRResult XDRRelazificationInfo(XDRState* xdr, HandleFunction fun, } } - // We can assert we have no inner functions because we don't relazify scripts - // with inner functions. See JSFunction::delazifyLazilyInterpretedFunction. - MOZ_ASSERT(!lazy->hasInnerFunctions()); + // Code binding names. + MOZ_TRY(XDRLazyClosedOverBindings(xdr, lazy)); - MOZ_TRY(LazyScript::XDRScriptData(xdr, sourceObject, lazy)); + // No need to do anything with inner functions, since we asserted we don't + // have any. return Ok(); } @@ -662,15 +633,6 @@ js::ScriptSource* js::BaseScript::maybeForwardedScriptSource() const { .source(); } -void js::BaseScript::finalize(JSFreeOp* fop) { - if (data_) { - size_t size = data_->allocationSize(); - AlwaysPoison(data_, JS_POISONED_JSSCRIPT_DATA_PATTERN, size, - MemCheckKind::MakeNoAccess); - fop->free_(this, data_, size, MemoryUse::ScriptPrivateData); - } -} - template /* static */ XDRResult js::PrivateScriptData::XDR(XDRState* xdr, HandleScript script, @@ -1287,7 +1249,8 @@ XDRResult js::XDRLazyScript(XDRState* xdr, HandleScope enclosingScope, uint32_t column; uint32_t immutableFlags; uint32_t numFieldInitializers; - uint32_t ngcthings; + uint32_t numClosedOverBindings; + uint32_t numInnerFunctions; if (mode == XDR_ENCODE) { // Note: it's possible the LazyScript has a non-null script_ pointer @@ -1309,7 +1272,8 @@ XDRResult js::XDRLazyScript(XDRState* xdr, HandleScope enclosingScope, } else { numFieldInitializers = UINT32_MAX; } - ngcthings = lazy->gcthings().size(); + numClosedOverBindings = lazy->numClosedOverBindings(); + numInnerFunctions = lazy->numInnerFunctions(); } MOZ_TRY(xdr->codeUint32(&sourceStart)); @@ -1320,13 +1284,14 @@ XDRResult js::XDRLazyScript(XDRState* xdr, HandleScope enclosingScope, MOZ_TRY(xdr->codeUint32(&column)); MOZ_TRY(xdr->codeUint32(&immutableFlags)); MOZ_TRY(xdr->codeUint32(&numFieldInitializers)); - MOZ_TRY(xdr->codeUint32(&ngcthings)); + MOZ_TRY(xdr->codeUint32(&numClosedOverBindings)); + MOZ_TRY(xdr->codeUint32(&numInnerFunctions)); if (mode == XDR_DECODE) { lazy.set(LazyScript::CreateForXDR( - cx, ngcthings, fun, nullptr, enclosingScope, sourceObject, - immutableFlags, sourceStart, sourceEnd, toStringStart, toStringEnd, - lineno, column)); + cx, numClosedOverBindings, numInnerFunctions, fun, nullptr, + enclosingScope, sourceObject, immutableFlags, sourceStart, sourceEnd, + toStringStart, toStringEnd, lineno, column)); if (!lazy) { return xdr->fail(JS::TranscodeResult_Throw); } @@ -1340,7 +1305,27 @@ XDRResult js::XDRLazyScript(XDRState* xdr, HandleScope enclosingScope, } } - MOZ_TRY(LazyScript::XDRScriptData(xdr, sourceObject, lazy)); + // Code closed-over bindings. + MOZ_TRY(XDRLazyClosedOverBindings(xdr, lazy)); + + // Code inner functions. + { + RootedFunction func(cx); + for (GCPtrFunction& elem : lazy->innerFunctions()) { + if (mode == XDR_ENCODE) { + func = elem.get(); + } + + MOZ_TRY(XDRInterpretedFunction(xdr, nullptr, sourceObject, &func)); + + if (mode == XDR_DECODE) { + elem.init(func); + if (elem->isInterpretedLazy()) { + elem->setEnclosingLazyScript(lazy); + } + } + } + } return Ok(); } @@ -4599,6 +4584,8 @@ void JSScript::assertValidJumpTargets() const { } #endif +size_t JSScript::computedSizeOfData() const { return data_->allocationSize(); } + size_t JSScript::sizeOfData(mozilla::MallocSizeOf mallocSizeOf) const { return mallocSizeOf(data_); } @@ -4628,9 +4615,6 @@ void JSScript::finalize(JSFreeOp* fop) { fop->runtime()->geckoProfiler().onScriptFinalized(this); - // Finalize the base-script fields. - BaseScript::finalize(fop); - if (hasJitScript()) { releaseJitScriptOnFinalize(fop); } @@ -4645,6 +4629,13 @@ void JSScript::finalize(JSFreeOp* fop) { } #endif + if (data_) { + size_t size = computedSizeOfData(); + AlwaysPoison(data_, JS_POISONED_JSSCRIPT_DATA_PATTERN, size, + MemCheckKind::MakeNoAccess); + fop->free_(this, data_, size, MemoryUse::ScriptPrivateData); + } + freeScriptData(); // In most cases, our LazyScript's script pointer will reference this @@ -5282,32 +5273,12 @@ void RuntimeScriptData::markForCrossZone(JSContext* cx) { } void ScriptWarmUpData::trace(JSTracer* trc) { - uintptr_t tag = data_ & TagMask; - switch (tag) { - case EnclosingScriptTag: { - LazyScript* enclosingScript = toEnclosingScript(); - TraceManuallyBarrieredEdge(trc, &enclosingScript, "enclosingScript"); - setTaggedPtr(enclosingScript); - break; - } - - case EnclosingScopeTag: { - Scope* enclosingScope = toEnclosingScope(); - TraceManuallyBarrieredEdge(trc, &enclosingScope, "enclosingScope"); - setTaggedPtr(enclosingScope); - break; - } - - case JitScriptTag: { - toJitScript()->trace(trc); - break; - } - - default: { - MOZ_ASSERT(isWarmUpCount()); - break; - } + if (isJitScript()) { + toJitScript()->trace(trc); + return; } + + MOZ_ASSERT(isWarmUpCount()); } void JSScript::traceChildren(JSTracer* trc) { @@ -5327,6 +5298,8 @@ void JSScript::traceChildren(JSTracer* trc) { scriptData()->traceChildren(trc); } + warmUpData_.trace(trc); + if (maybeLazyScript()) { TraceManuallyBarrieredEdge(trc, &lazyScript, "lazyScript"); } @@ -5340,6 +5313,13 @@ void JSScript::traceChildren(JSTracer* trc) { } } +void LazyScript::finalize(JSFreeOp* fop) { + if (lazyData_) { + fop->free_(this, lazyData_, lazyData_->allocationSize(), + MemoryUse::LazyScriptData); + } +} + size_t JSScript::calculateLiveFixed(jsbytecode* pc) { size_t nlivefixed = numAlwaysLiveFixedSlots(); @@ -5558,23 +5538,110 @@ bool JSScript::formalLivesInArgumentsObject(unsigned argSlot) { return argsObjAliasesFormals() && !formalIsAliased(argSlot); } +/* static */ size_t LazyScriptData::AllocationSize( + uint32_t numClosedOverBindings, uint32_t numInnerFunctions) { + size_t size = sizeof(LazyScriptData); + + size += numClosedOverBindings * sizeof(GCPtrAtom); + size += numInnerFunctions * sizeof(GCPtrFunction); + + return size; +} + +inline size_t LazyScriptData::allocationSize() const { + return AllocationSize(numClosedOverBindings_, numInnerFunctions_); +} + +// Placement-new elements of an array. This should optimize away for types with +// trivial default initiation. +template +void LazyScriptData::initElements(size_t offset, size_t length) { + void* raw = offsetToPointer(offset); + DefaultInitializeElements(raw, length); +} + +LazyScriptData::LazyScriptData(uint32_t numClosedOverBindings, + uint32_t numInnerFunctions) + : numClosedOverBindings_(numClosedOverBindings), + numInnerFunctions_(numInnerFunctions) { + // Variable-length data begins immediately after LazyScriptData itself. + size_t cursor = sizeof(*this); + + // Default-initialize trailing arrays. + + static_assert(alignof(LazyScriptData) >= alignof(GCPtrAtom), + "Incompatible alignment"); + initElements(cursor, numClosedOverBindings); + cursor += numClosedOverBindings * sizeof(GCPtrAtom); + + static_assert(alignof(GCPtrAtom) >= alignof(GCPtrFunction), + "Incompatible alignment"); + initElements(cursor, numInnerFunctions); + cursor += numInnerFunctions * sizeof(GCPtrFunction); + + // Sanity check + MOZ_ASSERT(AllocationSize(numClosedOverBindings, numInnerFunctions) == + cursor); +} + +/* static */ LazyScriptData* LazyScriptData::new_( + JSContext* cx, uint32_t numClosedOverBindings, uint32_t numInnerFunctions) { + // Compute size including trailing arrays + size_t size = AllocationSize(numClosedOverBindings, numInnerFunctions); + + // Allocate contiguous raw buffer + void* raw = cx->pod_malloc(size); + MOZ_ASSERT(uintptr_t(raw) % alignof(LazyScriptData) == 0); + if (!raw) { + return nullptr; + } + + // Constuct the LazyScriptData. Trailing arrays are uninitialized but + // GCPtrs are put into a safe state. + return new (raw) LazyScriptData(numClosedOverBindings, numInnerFunctions); +} + +mozilla::Span LazyScriptData::closedOverBindings() { + size_t offset = sizeof(LazyScriptData); + return mozilla::MakeSpan(offsetToPointer(offset), + numClosedOverBindings_); +} + +mozilla::Span LazyScriptData::innerFunctions() { + size_t offset = + sizeof(LazyScriptData) + sizeof(GCPtrAtom) * numClosedOverBindings_; + return mozilla::MakeSpan(offsetToPointer(offset), + numInnerFunctions_); +} + +void LazyScriptData::trace(JSTracer* trc) { + if (numClosedOverBindings_) { + auto array = closedOverBindings(); + TraceRange(trc, array.size(), array.data(), "closedOverBindings"); + } + + if (numInnerFunctions_) { + auto array = innerFunctions(); + TraceRange(trc, array.size(), array.data(), "innerFunctions"); + } +} + LazyScript::LazyScript(JSFunction* fun, uint8_t* stubEntry, - ScriptSourceObject& sourceObject, - PrivateScriptData* data, uint32_t immutableFlags, - uint32_t sourceStart, uint32_t sourceEnd, - uint32_t toStringStart, uint32_t toStringEnd, - uint32_t lineno, uint32_t column) + ScriptSourceObject& sourceObject, LazyScriptData* data, + uint32_t immutableFlags, uint32_t sourceStart, + uint32_t sourceEnd, uint32_t toStringStart, + uint32_t toStringEnd, uint32_t lineno, uint32_t column) : BaseScript(stubEntry, fun, &sourceObject, sourceStart, sourceEnd, toStringStart, toStringEnd), - script_(nullptr) { + script_(nullptr), + lazyData_(data) { lineno_ = lineno; column_ = column; immutableFlags_ = immutableFlags; if (data) { - data_ = data; - AddCellMemory(this, data->allocationSize(), MemoryUse::ScriptPrivateData); + AddCellMemory(this, data->allocationSize(), MemoryUse::LazyScriptData); } } @@ -5593,23 +5660,19 @@ void LazyScript::setEnclosingLazyScript(LazyScript* enclosingLazyScript) { // Enclosing scopes never transition back to enclosing lazy scripts. MOZ_ASSERT(!hasEnclosingScope()); - warmUpData_.initEnclosingScript(enclosingLazyScript); + enclosingLazyScriptOrScope_ = enclosingLazyScript; } void LazyScript::setEnclosingScope(Scope* enclosingScope) { MOZ_ASSERT(enclosingScope); MOZ_ASSERT(!hasEnclosingScope()); - if (warmUpData_.isEnclosingScript()) { - warmUpData_.clearEnclosingScript(); - } - - MOZ_ASSERT(warmUpData_.isWarmUpCount()); - warmUpData_.initEnclosingScope(enclosingScope); + enclosingLazyScriptOrScope_ = enclosingScope; } /* static */ -LazyScript* LazyScript::CreateRaw(JSContext* cx, uint32_t ngcthings, +LazyScript* LazyScript::CreateRaw(JSContext* cx, uint32_t numClosedOverBindings, + uint32_t numInnerFunctions, HandleFunction fun, HandleScriptSourceObject sourceObject, uint32_t immutableFlags, uint32_t sourceStart, @@ -5620,11 +5683,12 @@ LazyScript* LazyScript::CreateRaw(JSContext* cx, uint32_t ngcthings, MOZ_ASSERT(sourceObject); - // Allocate a PrivateScriptData if it will not be empty. Lazy class - // constructors also need PrivateScriptData for field lists. - Rooted> data(cx); - if (ngcthings || fun->isClassConstructor()) { - data.reset(PrivateScriptData::new_(cx, ngcthings)); + // Allocate a LazyScriptData if it will not be empty. Lazy class constructors + // also need LazyScriptData for field lists. + Rooted> data(cx); + if (numClosedOverBindings || numInnerFunctions || fun->isClassConstructor()) { + data.reset( + LazyScriptData::new_(cx, numClosedOverBindings, numInnerFunctions)); if (!data) { return nullptr; } @@ -5663,52 +5727,41 @@ LazyScript* LazyScript::Create( immutableFlags |= uint32_t(ImmutableFlags::HasInnerFunctions); } - uint32_t ngcthings = - innerFunctionBoxes.length() + closedOverBindings.length(); - LazyScript* res = LazyScript::CreateRaw( - cx, ngcthings, fun, sourceObject, immutableFlags, sourceStart, sourceEnd, - toStringStart, toStringEnd, lineno, column); + cx, closedOverBindings.length(), innerFunctionBoxes.length(), fun, + sourceObject, immutableFlags, sourceStart, sourceEnd, toStringStart, + toStringEnd, lineno, column); if (!res) { return nullptr; } - // Fill in gcthing data with inner functions followed by binding data. - mozilla::Span gcThings = - res->data_ ? res->data_->gcthings() : mozilla::Span(); - auto iter = gcThings.begin(); - - for (const frontend::FunctionBox* funbox : innerFunctionBoxes) { - JSFunction* fun = funbox->function(); - *iter++ = JS::GCCellPtr(fun); - - MOZ_ASSERT(fun->isInterpretedLazy()); - fun->setEnclosingLazyScript(res); + mozilla::Span resClosedOverBindings = res->closedOverBindings(); + for (size_t i = 0; i < res->numClosedOverBindings(); i++) { + resClosedOverBindings[i].init(closedOverBindings[i]); } - for (JSAtom* binding : closedOverBindings) { - if (binding) { - *iter++ = JS::GCCellPtr(binding); - } else { - iter++; + mozilla::Span resInnerFunctions = res->innerFunctions(); + for (size_t i = 0; i < res->numInnerFunctions(); i++) { + resInnerFunctions[i].init(innerFunctionBoxes[i]->function()); + if (resInnerFunctions[i]->isInterpretedLazy()) { + resInnerFunctions[i]->setEnclosingLazyScript(res); } } - MOZ_ASSERT(iter == gcThings.end()); - return res; } /* static */ LazyScript* LazyScript::CreateForXDR( - JSContext* cx, uint32_t ngcthings, HandleFunction fun, HandleScript script, - HandleScope enclosingScope, HandleScriptSourceObject sourceObject, - uint32_t immutableFlags, uint32_t sourceStart, uint32_t sourceEnd, - uint32_t toStringStart, uint32_t toStringEnd, uint32_t lineno, - uint32_t column) { + JSContext* cx, uint32_t numClosedOverBindings, uint32_t numInnerFunctions, + HandleFunction fun, HandleScript script, HandleScope enclosingScope, + HandleScriptSourceObject sourceObject, uint32_t immutableFlags, + uint32_t sourceStart, uint32_t sourceEnd, uint32_t toStringStart, + uint32_t toStringEnd, uint32_t lineno, uint32_t column) { LazyScript* res = LazyScript::CreateRaw( - cx, ngcthings, fun, sourceObject, immutableFlags, sourceStart, sourceEnd, - toStringStart, toStringEnd, lineno, column); + cx, numClosedOverBindings, numInnerFunctions, fun, sourceObject, + immutableFlags, sourceStart, sourceEnd, toStringStart, toStringEnd, + lineno, column); if (!res) { return nullptr; } diff --git a/js/src/vm/JSScript.h b/js/src/vm/JSScript.h index e0bcbe15803a..18e4859858de 100644 --- a/js/src/vm/JSScript.h +++ b/js/src/vm/JSScript.h @@ -1397,222 +1397,6 @@ class ScriptSourceObject : public NativeObject { enum class GeneratorKind : bool { NotGenerator, Generator }; enum class FunctionAsyncKind : bool { SyncFunction, AsyncFunction }; -// ScriptWarmUpData represents a pointer-sized field in BaseScript that stores -// one of the following using low-bit tags: -// -// * The enclosing LazyScript. This is only used while this script is lazy and -// its containing script is also lazy. This outer script must be compiled -// before the current script can in order to correctly build the scope chain. -// -// * The enclosing Scope. This is only used while this script is lazy and its -// containing script is compiled. This is the outer scope chain that will be -// used to compile this scipt. -// -// * The script's warm-up count. This is only used until the script has a -// JitScript. The Baseline Interpreter and JITs use the warm-up count stored -// in JitScript. -// -// * A pointer to the JitScript, when the script is warm enough for the Baseline -// Interpreter. -// -class ScriptWarmUpData { - uintptr_t data_ = ResetState(); - - private: - static constexpr uintptr_t NumTagBits = 2; - static constexpr uint32_t MaxWarmUpCount = UINT32_MAX >> NumTagBits; - - public: - // Public only for the JITs. - static constexpr uintptr_t TagMask = (1 << NumTagBits) - 1; - static constexpr uintptr_t JitScriptTag = 0; - static constexpr uintptr_t EnclosingScriptTag = 1; - static constexpr uintptr_t EnclosingScopeTag = 2; - static constexpr uintptr_t WarmUpCountTag = 3; - - private: - // A gc-safe value to clear to. - constexpr uintptr_t ResetState() { return 0 | WarmUpCountTag; } - - template - inline void setTaggedPtr(void* ptr) { - static_assert(Tag <= TagMask, "Tag must fit in TagMask"); - MOZ_ASSERT((uintptr_t(ptr) & TagMask) == 0); - data_ = uintptr_t(ptr) | Tag; - } - - template - inline T getTaggedPtr() const { - static_assert(Tag <= TagMask, "Tag must fit in TagMask"); - MOZ_ASSERT((data_ & TagMask) == Tag); - return reinterpret_cast(data_ & ~TagMask); - } - - void setWarmUpCount(uint32_t count) { - if (count > MaxWarmUpCount) { - count = MaxWarmUpCount; - } - data_ = (uintptr_t(count) << NumTagBits) | WarmUpCountTag; - } - - public: - void trace(JSTracer* trc); - - bool isEnclosingScript() const { - return (data_ & TagMask) == EnclosingScriptTag; - } - bool isEnclosingScope() const { - return (data_ & TagMask) == EnclosingScopeTag; - } - bool isWarmUpCount() const { return (data_ & TagMask) == WarmUpCountTag; } - bool isJitScript() const { return (data_ & TagMask) == JitScriptTag; } - - // NOTE: To change type safely, 'clear' the old tagged value and then 'init' - // the new one. This will notify the GC appropriately. - - LazyScript* toEnclosingScript() const { - return getTaggedPtr(); - } - inline void initEnclosingScript(LazyScript* enclosingScript); - inline void clearEnclosingScript(); - - Scope* toEnclosingScope() const { - return getTaggedPtr(); - } - inline void initEnclosingScope(Scope* enclosingScope); - inline void clearEnclosingScope(); - - uint32_t toWarmUpCount() const { - MOZ_ASSERT(isWarmUpCount()); - return data_ >> NumTagBits; - } - void resetWarmUpCount(uint32_t count) { - MOZ_ASSERT(isWarmUpCount()); - setWarmUpCount(count); - } - void incWarmUpCount(uint32_t amount) { - MOZ_ASSERT(isWarmUpCount()); - data_ += uintptr_t(amount) << NumTagBits; - } - - jit::JitScript* toJitScript() const { - return getTaggedPtr(); - } - void initJitScript(jit::JitScript* jitScript) { - MOZ_ASSERT(isWarmUpCount()); - setTaggedPtr(jitScript); - } - void clearJitScript() { - MOZ_ASSERT(isJitScript()); - data_ = ResetState(); - } -} JS_HAZ_GC_POINTER; - -static_assert(sizeof(ScriptWarmUpData) == sizeof(uintptr_t), - "JIT code depends on ScriptWarmUpData being pointer-sized"); - -struct FieldInitializers { -#ifdef DEBUG - bool valid; -#endif - // This struct will eventually have a vector of constant values for optimizing - // field initializers. - size_t numFieldInitializers; - - explicit FieldInitializers(size_t numFieldInitializers) - : -#ifdef DEBUG - valid(true), -#endif - numFieldInitializers(numFieldInitializers) { - } - - static FieldInitializers Invalid() { return FieldInitializers(); } - - private: - FieldInitializers() - : -#ifdef DEBUG - valid(false), -#endif - numFieldInitializers(0) { - } -}; - -// [SMDOC] - JSScript data layout (unshared) -// -// PrivateScriptData stores variable-length data associated with a script. -// Abstractly a PrivateScriptData consists of all these arrays: -// -// * A non-empty array of GCCellPtr in gcthings() -// -// Accessing this array just requires calling the appropriate public -// Span-computing function. -class alignas(uintptr_t) PrivateScriptData final { - uint32_t ngcthings = 0; - - js::FieldInitializers fieldInitializers_ = js::FieldInitializers::Invalid(); - - // Translate an offset into a concrete pointer. - template - T* offsetToPointer(size_t offset) { - uintptr_t base = reinterpret_cast(this); - uintptr_t elem = base + offset; - return reinterpret_cast(elem); - } - - // Helpers for creating initializing trailing data - template - void initElements(size_t offset, size_t length); - - // Size to allocate - static size_t AllocationSize(uint32_t ngcthings); - - // Initialize header and PackedSpans - explicit PrivateScriptData(uint32_t ngcthings); - - public: - static constexpr size_t offsetOfGCThings() { - return sizeof(PrivateScriptData); - } - - // Accessors for typed array spans. - mozilla::Span gcthings() { - size_t offset = offsetOfGCThings(); - return mozilla::MakeSpan(offsetToPointer(offset), ngcthings); - } - - void setFieldInitializers(FieldInitializers fieldInitializers) { - fieldInitializers_ = fieldInitializers; - } - const FieldInitializers& getFieldInitializers() { return fieldInitializers_; } - - // Allocate a new PrivateScriptData. Headers and GCPtrs are initialized. - static PrivateScriptData* new_(JSContext* cx, uint32_t ngcthings); - - template - static MOZ_MUST_USE XDRResult XDR(js::XDRState* xdr, - js::HandleScript script, - js::HandleScriptSourceObject sourceObject, - js::HandleScope scriptEnclosingScope, - js::HandleFunction fun); - - // Clone src script data into dst script. - static bool Clone(JSContext* cx, js::HandleScript src, js::HandleScript dst, - js::MutableHandle> scopes); - - static bool InitFromEmitter(JSContext* cx, js::HandleScript script, - js::frontend::BytecodeEmitter* bce); - - void trace(JSTracer* trc); - - size_t allocationSize() const; - - // PrivateScriptData has trailing data so isn't copyable or movable. - PrivateScriptData(const PrivateScriptData&) = delete; - PrivateScriptData& operator=(const PrivateScriptData&) = delete; -}; - // This class contains fields and accessors that are common to both lazy and // non-lazy interpreted scripts. This must be located at offset +0 of any // derived classes in order for the 'jitCodeRaw' mechanism to work with the @@ -1632,12 +1416,6 @@ class BaseScript : public gc::TenuredCell { // The ScriptSourceObject for this script. GCPtr sourceObject_ = {}; - // Unshared variable-length data. This may be nullptr for lazy scripts of leaf - // functions. Note that meaning of this data is different if the script is - // lazy vs non-lazy. In both cases, the JSFunction pointers will represent the - // inner-functions, but other kinds of entries have different interpretations. - PrivateScriptData* data_ = nullptr; - // Range of characters in scriptSource which contains this script's source, // that is, the range used by the Parser to produce this script. // @@ -1676,8 +1454,6 @@ class BaseScript : public gc::TenuredCell { uint32_t immutableFlags_ = 0; uint32_t mutableFlags_ = 0; - ScriptWarmUpData warmUpData_ = {}; - BaseScript(uint8_t* stubEntry, JSObject* functionOrGlobal, ScriptSourceObject* sourceObject, uint32_t sourceStart, uint32_t sourceEnd, uint32_t toStringStart, uint32_t toStringEnd) @@ -2091,28 +1867,6 @@ setterLevel: \ } } - mozilla::Span gcthings() const { - return data_ ? data_->gcthings() : mozilla::Span(); - } - - void setFieldInitializers(FieldInitializers fieldInitializers) { - MOZ_ASSERT(data_); - data_->setFieldInitializers(fieldInitializers); - } - - const FieldInitializers& getFieldInitializers() const { - MOZ_ASSERT(data_); - return data_->getFieldInitializers(); - } - - protected: - void finalize(JSFreeOp* fop); - - public: - size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) { - return mallocSizeOf(data_); - } - // JIT accessors static constexpr size_t offsetOfJitCodeRaw() { return offsetof(BaseScript, jitCodeRaw_); @@ -2123,8 +1877,33 @@ setterLevel: \ static constexpr size_t offsetOfMutableFlags() { return offsetof(BaseScript, mutableFlags_); } - static constexpr size_t offsetOfWarmUpData() { - return offsetof(BaseScript, warmUpData_); +}; + +struct FieldInitializers { +#ifdef DEBUG + bool valid; +#endif + // This struct will eventually have a vector of constant values for optimizing + // field initializers. + size_t numFieldInitializers; + + explicit FieldInitializers(size_t numFieldInitializers) + : +#ifdef DEBUG + valid(true), +#endif + numFieldInitializers(numFieldInitializers) { + } + + static FieldInitializers Invalid() { return FieldInitializers(); } + + private: + FieldInitializers() + : +#ifdef DEBUG + valid(false), +#endif + numFieldInitializers(0) { } }; @@ -2149,6 +1928,80 @@ XDRResult XDRLazyScript(XDRState* xdr, HandleScope enclosingScope, template XDRResult XDRScriptConst(XDRState* xdr, MutableHandleValue vp); +// [SMDOC] - JSScript data layout (unshared) +// +// PrivateScriptData stores variable-length data associated with a script. +// Abstractly a PrivateScriptData consists of all these arrays: +// +// * A non-empty array of GCCellPtr in gcthings() +// +// Accessing this array just requires calling the appropriate public +// Span-computing function. +class alignas(uintptr_t) PrivateScriptData final { + uint32_t ngcthings = 0; + + js::FieldInitializers fieldInitializers_ = js::FieldInitializers::Invalid(); + + // Translate an offset into a concrete pointer. + template + T* offsetToPointer(size_t offset) { + uintptr_t base = reinterpret_cast(this); + uintptr_t elem = base + offset; + return reinterpret_cast(elem); + } + + // Helpers for creating initializing trailing data + template + void initElements(size_t offset, size_t length); + + // Size to allocate + static size_t AllocationSize(uint32_t ngcthings); + + // Initialize header and PackedSpans + explicit PrivateScriptData(uint32_t ngcthings); + + public: + static constexpr size_t offsetOfGCThings() { + return sizeof(PrivateScriptData); + } + + // Accessors for typed array spans. + mozilla::Span gcthings() { + size_t offset = offsetOfGCThings(); + return mozilla::MakeSpan(offsetToPointer(offset), ngcthings); + } + + void setFieldInitializers(FieldInitializers fieldInitializers) { + fieldInitializers_ = fieldInitializers; + } + const FieldInitializers& getFieldInitializers() { return fieldInitializers_; } + + // Allocate a new PrivateScriptData. Headers and GCPtrs are initialized. + static PrivateScriptData* new_(JSContext* cx, uint32_t ngcthings); + + template + static MOZ_MUST_USE XDRResult XDR(js::XDRState* xdr, + js::HandleScript script, + js::HandleScriptSourceObject sourceObject, + js::HandleScope scriptEnclosingScope, + js::HandleFunction fun); + + // Clone src script data into dst script. + static bool Clone(JSContext* cx, js::HandleScript src, js::HandleScript dst, + js::MutableHandle> scopes); + + static bool InitFromEmitter(JSContext* cx, js::HandleScript script, + js::frontend::BytecodeEmitter* bce); + + void trace(JSTracer* trc); + + size_t allocationSize() const; + + // PrivateScriptData has trailing data so isn't copyable or movable. + PrivateScriptData(const PrivateScriptData&) = delete; + PrivateScriptData& operator=(const PrivateScriptData&) = delete; +}; + // [SMDOC] JSScript data layout (immutable) // // ImmutableScriptData stores variable-length script data that may be shared @@ -2523,6 +2376,75 @@ using RuntimeScriptDataTable = extern void SweepScriptData(JSRuntime* rt); +// ScriptWarmUpData represents a pointer-sized field in JSScript that stores +// one of the following: +// +// * The script's warm-up count. This is only used until the script has a +// JitScript. The Baseline Interpreter and JITs use the warm-up count stored +// in JitScript. +// +// * A pointer to the JitScript, when the script is warm enough for the Baseline +// Interpreter. +// +// Pointer tagging is used to distinguish those states. +class ScriptWarmUpData { + static constexpr uintptr_t NumTagBits = 2; + static constexpr uint32_t MaxWarmUpCount = UINT32_MAX >> NumTagBits; + + public: + // Public only for the JITs. + static constexpr uintptr_t TagMask = (1 << NumTagBits) - 1; + static constexpr uintptr_t JitScriptTag = 0; + static constexpr uintptr_t WarmUpCountTag = 1; + + private: + uintptr_t data_ = 0 | WarmUpCountTag; + + void setWarmUpCount(uint32_t count) { + if (count > MaxWarmUpCount) { + count = MaxWarmUpCount; + } + data_ = (uintptr_t(count) << NumTagBits) | WarmUpCountTag; + } + + public: + void trace(JSTracer* trc); + + bool isWarmUpCount() const { return (data_ & TagMask) == WarmUpCountTag; } + bool isJitScript() const { return (data_ & TagMask) == JitScriptTag; } + + uint32_t toWarmUpCount() const { + MOZ_ASSERT(isWarmUpCount()); + return data_ >> NumTagBits; + } + void resetWarmUpCount(uint32_t count) { + MOZ_ASSERT(isWarmUpCount()); + setWarmUpCount(count); + } + void incWarmUpCount(uint32_t amount) { + MOZ_ASSERT(isWarmUpCount()); + data_ += uintptr_t(amount) << NumTagBits; + } + + jit::JitScript* toJitScript() const { + MOZ_ASSERT(isJitScript()); + static_assert(JitScriptTag == 0, "Code depends on JitScriptTag being zero"); + return reinterpret_cast(data_); + } + void setJitScript(jit::JitScript* jitScript) { + MOZ_ASSERT(isWarmUpCount()); + MOZ_ASSERT((uintptr_t(jitScript) & TagMask) == 0); + data_ = uintptr_t(jitScript) | JitScriptTag; + } + void clearJitScript() { + MOZ_ASSERT(isJitScript()); + setWarmUpCount(0); + } +}; + +static_assert(sizeof(ScriptWarmUpData) == sizeof(uintptr_t), + "JIT code depends on ScriptWarmUpData being pointer-sized"); + } /* namespace js */ namespace JS { @@ -2540,7 +2462,12 @@ class JSScript : public js::BaseScript { // Shareable script data RefPtr scriptData_ = {}; + // Unshared variable-length data + js::PrivateScriptData* data_ = nullptr; + private: + js::ScriptWarmUpData warmUpData_ = {}; + /* Information used to re-lazify a lazily-parsed interpreted function. */ js::LazyScript* lazyScript = nullptr; @@ -2801,6 +2728,16 @@ class JSScript : public js::BaseScript { static void argumentsOptimizationFailed(JSContext* cx, js::HandleScript script); + void setFieldInitializers(js::FieldInitializers fieldInitializers) { + MOZ_ASSERT(data_); + data_->setFieldInitializers(fieldInitializers); + } + + const js::FieldInitializers& getFieldInitializers() const { + MOZ_ASSERT(data_); + return data_->getFieldInitializers(); + } + /* * Arguments access (via JSOP_*ARG* opcodes) must access the canonical * location for the argument. If an arguments object exists AND it's mapped @@ -2819,37 +2756,23 @@ class JSScript : public js::BaseScript { static constexpr size_t offsetOfPrivateScriptData() { return offsetof(JSScript, data_); } + static constexpr size_t offsetOfWarmUpData() { + return offsetof(JSScript, warmUpData_); + } void updateJitCodeRaw(JSRuntime* rt); + // We don't relazify functions with a JitScript or JIT code, but some + // callers (XDR, testing functions) want to know whether this script is + // relazifiable ignoring (or after) discarding JIT code. + bool isRelazifiableIgnoringJitCode() const { + return (selfHosted() || lazyScript) && !hasInnerFunctions() && + !isGenerator() && !isAsync() && !isDefaultClassConstructor() && + !doNotRelazify() && !hasCallSiteObj(); + } bool isRelazifiable() const { - // A script may not be relazifiable if parts of it can be entrained in - // interesting ways: - // - Scripts with inner-functions or direct-eval (which can add - // inner-functions) should not be relazified as their Scopes may be part - // of another scope-chain. - // - Generators and async functions may be re-entered in complex ways so - // don't discard bytecode. - // - Functions with template literals must always return the same object - // instance so must not discard it by relazifying. - return !hasInnerFunctions() && !hasDirectEval() && !isGenerator() && - !isAsync() && !hasCallSiteObj(); + return isRelazifiableIgnoringJitCode() && !hasJitScript(); } - bool canRelazify() const { - // In order to actually relazify we must satisfy additional runtime - // conditions: - // - The lazy form must still exist. This is either the original LazyScript - // or the self-hosted script that we cloned from. - // - There must not be any JIT code attached since the relazification - // process does not know how to discard it. In general, the GC should - // discard most JIT code before attempting relazification. - // - Specific subsystems (such as the Debugger) may disable scripts for - // their own reasons. - bool lazyAvailable = selfHosted() || lazyScript; - return isRelazifiable() && lazyAvailable && !hasJitScript() && - !doNotRelazify(); - } - void setLazyScript(js::LazyScript* lazy) { lazyScript = lazy; } js::LazyScript* maybeLazyScript() { return lazyScript; } @@ -3062,15 +2985,21 @@ class JSScript : public js::BaseScript { } /* + * computedSizeOfData() is the in-use size of all the data sections. * sizeOfData() is the size of the block allocated to hold all the data * sections (which can be larger than the in-use size). */ + size_t computedSizeOfData() const; size_t sizeOfData(mozilla::MallocSizeOf mallocSizeOf) const; void addSizeOfJitScript(mozilla::MallocSizeOf mallocSizeOf, size_t* sizeOfJitScript, size_t* sizeOfBaselineFallbackStubs) const; + mozilla::Span gcthings() const { + return data_->gcthings(); + } + mozilla::Span trynotes() const { return immutableScriptData()->tryNotes(); } @@ -3249,6 +3178,48 @@ static_assert( namespace js { +// Variable-length data for LazyScripts. Contains vector of inner functions and +// vector of captured property ids. +class alignas(uintptr_t) LazyScriptData final { + private: + uint32_t numClosedOverBindings_ = 0; + uint32_t numInnerFunctions_ = 0; + + FieldInitializers fieldInitializers_ = FieldInitializers::Invalid(); + + // Size to allocate + static size_t AllocationSize(uint32_t numClosedOverBindings, + uint32_t numInnerFunctions); + size_t allocationSize() const; + + // Translate an offset into a concrete pointer. + template + T* offsetToPointer(size_t offset) { + uintptr_t base = reinterpret_cast(this); + return reinterpret_cast(base + offset); + } + + template + void initElements(size_t offset, size_t length); + + LazyScriptData(uint32_t numClosedOverBindings, uint32_t numInnerFunctions); + + public: + static LazyScriptData* new_(JSContext* cx, uint32_t numClosedOverBindings, + uint32_t numInnerFunctions); + + friend class LazyScript; + + mozilla::Span closedOverBindings(); + mozilla::Span innerFunctions(); + + void trace(JSTracer* trc); + + // LazyScriptData has trailing data so isn't copyable or movable. + LazyScriptData(const LazyScriptData&) = delete; + LazyScriptData& operator=(const LazyScriptData&) = delete; +}; + // Information about a script which may be (or has been) lazily compiled to // bytecode from its source. class LazyScript : public BaseScript { @@ -3258,7 +3229,7 @@ class LazyScript : public BaseScript { WeakHeapPtrScript script_; friend void js::gc::SweepLazyScripts(GCParallelTask* task); - // The BaseScript::warmUpData_ field is used as follows: + // This field holds one of: // * LazyScript in which the script is nested. This case happens if the // enclosing script is lazily parsed and have never been compiled. // @@ -3285,8 +3256,6 @@ class LazyScript : public BaseScript { // enclosing script has ever been compiled. // // * nullptr for incomplete (initial or failure) state - // NOTE: We currently represent this as WarmUpCount(0) inside the - // ScriptWarmUpData tagged pointer. // // This field should be accessed via accessors: // * enclosingScope @@ -3335,12 +3304,17 @@ class LazyScript : public BaseScript { // +-----------------+ | // | enclosing Scope |<-------------+ // +-----------------+ + GCPtr enclosingLazyScriptOrScope_; + + // Heap allocated table with any free variables, inner functions, or class + // fields. This will be nullptr if none exist. + LazyScriptData* lazyData_; static const uint32_t NumClosedOverBindingsBits = 20; static const uint32_t NumInnerFunctionsBits = 20; LazyScript(JSFunction* fun, uint8_t* stubEntry, - ScriptSourceObject& sourceObject, PrivateScriptData* data, + ScriptSourceObject& sourceObject, LazyScriptData* data, uint32_t immutableFlags, uint32_t sourceStart, uint32_t sourceEnd, uint32_t toStringStart, uint32_t toStringEnd, uint32_t lineno, uint32_t column); @@ -3348,8 +3322,8 @@ class LazyScript : public BaseScript { // Create a LazyScript without initializing the closedOverBindings and the // innerFunctions. To be GC-safe, the caller must initialize both vectors // with valid atoms and functions. - static LazyScript* CreateRaw(JSContext* cx, uint32_t ngcthings, - HandleFunction fun, + static LazyScript* CreateRaw(JSContext* cx, uint32_t numClosedOverBindings, + uint32_t numInnerFunctions, HandleFunction fun, HandleScriptSourceObject sourceObject, uint32_t immutableFlags, uint32_t sourceStart, uint32_t sourceEnd, uint32_t toStringStart, @@ -3380,19 +3354,21 @@ class LazyScript : public BaseScript { // // The sourceObject and enclosingScope arguments may be null if the // enclosing function is also lazy. - static LazyScript* CreateForXDR(JSContext* cx, uint32_t ngcthings, - HandleFunction fun, HandleScript script, - HandleScope enclosingScope, - HandleScriptSourceObject sourceObject, - uint32_t immutableFlags, uint32_t sourceStart, - uint32_t sourceEnd, uint32_t toStringStart, - uint32_t toStringEnd, uint32_t lineno, - uint32_t column); + static LazyScript* CreateForXDR( + JSContext* cx, uint32_t numClosedOverBindings, uint32_t numInnerFunctions, + HandleFunction fun, HandleScript script, HandleScope enclosingScope, + HandleScriptSourceObject sourceObject, uint32_t immutableFlags, + uint32_t sourceStart, uint32_t sourceEnd, uint32_t toStringStart, + uint32_t toStringEnd, uint32_t lineno, uint32_t column); - template - static XDRResult XDRScriptData(XDRState* xdr, - HandleScriptSourceObject sourceObject, - Handle lazy); + bool canRelazify() const { + // Only functions without inner functions or direct eval are re-lazified. + // Functions with either of those are on the static scope chain of their + // inner functions, or in the case of eval, possibly eval'd inner + // functions. Note that if this ever changes, XDRRelazificationInfo will + // have to be fixed. + return !hasInnerFunctions() && !hasDirectEval(); + } void initScript(JSScript* script); @@ -3402,23 +3378,47 @@ class LazyScript : public BaseScript { } bool hasScript() const { return bool(script_); } - bool hasEnclosingScope() const { return warmUpData_.isEnclosingScope(); } + bool hasEnclosingScope() const { + return enclosingLazyScriptOrScope_ && + enclosingLazyScriptOrScope_->is(); + } bool hasEnclosingLazyScript() const { - return warmUpData_.isEnclosingScript(); + return enclosingLazyScriptOrScope_ && + enclosingLazyScriptOrScope_->is(); } LazyScript* enclosingLazyScript() const { - return warmUpData_.toEnclosingScript(); + MOZ_ASSERT(hasEnclosingLazyScript()); + return enclosingLazyScriptOrScope_->as(); } void setEnclosingLazyScript(LazyScript* enclosingLazyScript); - Scope* enclosingScope() const { return warmUpData_.toEnclosingScope(); } + Scope* enclosingScope() const { + MOZ_ASSERT(hasEnclosingScope()); + return enclosingLazyScriptOrScope_->as(); + } void setEnclosingScope(Scope* enclosingScope); bool hasNonSyntacticScope() const { return enclosingScope()->hasOnChain(ScopeKind::NonSyntactic); } + mozilla::Span closedOverBindings() { + return lazyData_ ? lazyData_->closedOverBindings() + : mozilla::Span(); + } + uint32_t numClosedOverBindings() const { + return lazyData_ ? lazyData_->closedOverBindings().size() : 0; + }; + + mozilla::Span innerFunctions() { + return lazyData_ ? lazyData_->innerFunctions() + : mozilla::Span(); + } + uint32_t numInnerFunctions() const { + return lazyData_ ? lazyData_->innerFunctions().size() : 0; + } + frontend::ParseGoal parseGoal() const { if (hasFlag(ImmutableFlags::IsModule)) { return frontend::ParseGoal::Module; @@ -3433,6 +3433,16 @@ class LazyScript : public BaseScript { } void setWrappedByDebugger() { setFlag(MutableFlags::WrappedByDebugger); } + void setFieldInitializers(FieldInitializers fieldInitializers) { + MOZ_ASSERT(lazyData_); + lazyData_->fieldInitializers_ = fieldInitializers; + } + + const FieldInitializers& getFieldInitializers() const { + MOZ_ASSERT(lazyData_); + return lazyData_->fieldInitializers_; + } + // Returns true if the enclosing script has ever been compiled. // Once the enclosing script is compiled, the scope chain is created. // This LazyScript is delazify-able as long as it has the enclosing scope, @@ -3445,9 +3455,13 @@ class LazyScript : public BaseScript { friend class GCMarker; void traceChildren(JSTracer* trc); - void finalize(JSFreeOp* fop) { BaseScript::finalize(fop); } + void finalize(JSFreeOp* fop); static const JS::TraceKind TraceKind = JS::TraceKind::LazyScript; + + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) { + return mallocSizeOf(lazyData_); + } }; /* If this fails, add/remove padding within LazyScript. */