diff --git a/content/xul/content/src/nsXULElement.cpp b/content/xul/content/src/nsXULElement.cpp index e8f8ca5fa64..75851dc9949 100644 --- a/content/xul/content/src/nsXULElement.cpp +++ b/content/xul/content/src/nsXULElement.cpp @@ -2548,13 +2548,56 @@ nsXULPrototypeScript::DeserializeOutOfLine(nsIObjectInputStream* aInput, return rv; } +class NotifyOffThreadScriptCompletedRunnable : public nsRunnable +{ + nsRefPtr mReceiver; + + // Note: there is no need to root the script, it is protected against GC + // until FinishOffThreadScript is called on it. + JSScript *mScript; + +public: + NotifyOffThreadScriptCompletedRunnable(already_AddRefed aReceiver, + JSScript *aScript) + : mReceiver(aReceiver), mScript(aScript) + {} + + NS_DECL_NSIRUNNABLE +}; + +NS_IMETHODIMP +NotifyOffThreadScriptCompletedRunnable::Run() +{ + MOZ_ASSERT(NS_IsMainThread()); + + // Note: this unroots mScript so that it is available to be collected by the + // JS GC. The receiver needs to root the script before performing a call that + // could GC. + JS::FinishOffThreadScript(nsJSRuntime::sRuntime, mScript); + + return mReceiver->OnScriptCompileComplete(mScript, mScript ? NS_OK : NS_ERROR_FAILURE); +} + +static void +OffThreadScriptReceiverCallback(JSScript *script, void *ptr) +{ + // Be careful not to adjust the refcount on the receiver, as this callback + // may be invoked off the main thread. + nsIOffThreadScriptReceiver* aReceiver = static_cast(ptr); + nsRefPtr notify = + new NotifyOffThreadScriptCompletedRunnable( + already_AddRefed(aReceiver), script); + NS_DispatchToMainThread(notify); +} + nsresult nsXULPrototypeScript::Compile(const PRUnichar* aText, int32_t aTextLength, nsIURI* aURI, uint32_t aLineNo, nsIDocument* aDocument, - nsIScriptGlobalObject* aGlobal) + nsIScriptGlobalObject* aGlobal, + nsIOffThreadScriptReceiver *aOffThreadReceiver /* = nullptr */) { // We'll compile the script using the prototype document's special // script object as the parent. This ensures that we won't end up @@ -2604,11 +2647,23 @@ nsXULPrototypeScript::Compile(const PRUnichar* aText, : JS::CompileOptions::SAVE_SOURCE); JS::RootedObject scope(cx, JS::CurrentGlobalOrNull(cx)); xpc_UnmarkGrayObject(scope); - JSScript* script = JS::Compile(cx, scope, options, + + if (aOffThreadReceiver && JS::CanCompileOffThread(cx, options)) { + if (!JS::CompileOffThread(cx, scope, options, + static_cast(aText), aTextLength, + OffThreadScriptReceiverCallback, + static_cast(aOffThreadReceiver))) { + return NS_ERROR_OUT_OF_MEMORY; + } + // This reference will be consumed by the NotifyOffThreadScriptCompletedRunnable. + NS_ADDREF(aOffThreadReceiver); + } else { + JSScript* script = JS::Compile(cx, scope, options, static_cast(aText), aTextLength); - if (!script) - return NS_ERROR_OUT_OF_MEMORY; - Set(script); + if (!script) + return NS_ERROR_OUT_OF_MEMORY; + Set(script); + } return NS_OK; } diff --git a/content/xul/content/src/nsXULElement.h b/content/xul/content/src/nsXULElement.h index 0231a1a3eef..4d74a8c1537 100644 --- a/content/xul/content/src/nsXULElement.h +++ b/content/xul/content/src/nsXULElement.h @@ -231,7 +231,8 @@ public: nsresult Compile(const PRUnichar* aText, int32_t aTextLength, nsIURI* aURI, uint32_t aLineNo, nsIDocument* aDocument, - nsIScriptGlobalObject* aGlobal); + nsIScriptGlobalObject* aGlobal, + nsIOffThreadScriptReceiver *aOffThreadReceiver = nullptr); void UnlinkJSObjects(); diff --git a/content/xul/document/src/XULDocument.cpp b/content/xul/document/src/XULDocument.cpp index 94f21e2605b..f4dc7009bdf 100644 --- a/content/xul/document/src/XULDocument.cpp +++ b/content/xul/document/src/XULDocument.cpp @@ -354,9 +354,9 @@ NS_IMPL_RELEASE_INHERITED(XULDocument, XMLDocument) // QueryInterface implementation for XULDocument NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(XULDocument) - NS_INTERFACE_TABLE_INHERITED4(XULDocument, nsIXULDocument, + NS_INTERFACE_TABLE_INHERITED5(XULDocument, nsIXULDocument, nsIDOMXULDocument, nsIStreamLoaderObserver, - nsICSSLoaderObserver) + nsICSSLoaderObserver, nsIOffThreadScriptReceiver) NS_INTERFACE_TABLE_TAIL_INHERITING(XMLDocument) @@ -3465,7 +3465,6 @@ XULDocument::LoadScript(nsXULPrototypeScript* aScriptProto, bool* aBlock) return NS_OK; } - NS_IMETHODIMP XULDocument::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* context, @@ -3505,17 +3504,6 @@ XULDocument::OnStreamComplete(nsIStreamLoader* aLoader, return NS_OK; } - // Clear mCurrentScriptProto now, but save it first for use below in - // the compile/execute code, and in the while loop that resumes walks - // of other documents that raced to load this script - nsXULPrototypeScript* scriptProto = mCurrentScriptProto; - mCurrentScriptProto = nullptr; - - // Clear the prototype's loading flag before executing the script or - // resuming document walks, in case any of those control flows starts a - // new script load. - scriptProto->mSrcLoading = false; - if (NS_SUCCEEDED(aStatus)) { // If the including XUL document is a FastLoad document, and we're // compiling an out-of-line script (one with src=...), then we must @@ -3524,78 +3512,123 @@ XULDocument::OnStreamComplete(nsIStreamLoader* aLoader, // nsXULContentSink.cpp) would have already deserialized a non-null // script->mScriptObject, causing control flow at the top of LoadScript // not to reach here. - nsCOMPtr uri = scriptProto->mSrcURI; + nsCOMPtr uri = mCurrentScriptProto->mSrcURI; // XXX should also check nsIHttpChannel::requestSucceeded - nsString stringStr; + MOZ_ASSERT(!mOffThreadCompiling && mOffThreadCompileString.Length() == 0, + "XULDocument can't load multiple scripts at once"); + rv = nsScriptLoader::ConvertToUTF16(channel, string, stringLen, - EmptyString(), this, stringStr); + EmptyString(), this, mOffThreadCompileString); if (NS_SUCCEEDED(rv)) { - rv = scriptProto->Compile(stringStr.get(), stringStr.Length(), - uri, 1, this, - mCurrentPrototype->GetScriptGlobalObject()); + rv = mCurrentScriptProto->Compile(mOffThreadCompileString.get(), + mOffThreadCompileString.Length(), + uri, 1, this, + mCurrentPrototype->GetScriptGlobalObject(), + this); + if (NS_SUCCEEDED(rv) && !mCurrentScriptProto->GetScriptObject()) { + // We will be notified via OnOffThreadCompileComplete when the + // compile finishes. Keep the contents of the compiled script + // alive until the compilation finishes. + mOffThreadCompiling = true; + BlockOnload(); + return NS_OK; + } + mOffThreadCompileString.Truncate(); } + } - aStatus = rv; - if (NS_SUCCEEDED(rv)) { - rv = ExecuteScript(scriptProto); + return OnScriptCompileComplete(mCurrentScriptProto->GetScriptObject(), rv); +} - // If the XUL cache is enabled, save the script object there in - // case different XUL documents source the same script. - // - // But don't save the script in the cache unless the master XUL - // document URL is a chrome: URL. It is valid for a URL such as - // about:config to translate into a master document URL, whose - // prototype document nodes -- including prototype scripts that - // hold GC roots protecting their mJSObject pointers -- are not - // cached in the XUL prototype cache. See StartDocumentLoad, - // the fillXULCache logic. - // - // A document such as about:config is free to load a script via - // a URL such as chrome://global/content/config.js, and we must - // not cache that script object without a prototype cache entry - // containing a companion nsXULPrototypeScript node that owns a - // GC root protecting the script object. Otherwise, the script - // cache entry will dangle once the uncached prototype document - // is released when its owning XULDocument is unloaded. - // - // (See http://bugzilla.mozilla.org/show_bug.cgi?id=98207 for - // the true crime story.) - bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled(); +NS_IMETHODIMP +XULDocument::OnScriptCompileComplete(JSScript* aScript, nsresult aStatus) +{ + // Allow load events to be fired once off thread compilation finishes. + if (mOffThreadCompiling) { + mOffThreadCompiling = false; + UnblockOnload(false); + } + + // After compilation finishes the script's characters are no longer needed. + mOffThreadCompileString.Truncate(); + + // When compiling off thread the script will not have been attached to the + // script proto yet. + if (aScript && !mCurrentScriptProto->GetScriptObject()) + mCurrentScriptProto->Set(aScript); + + // Clear mCurrentScriptProto now, but save it first for use below in + // the execute code, and in the while loop that resumes walks of other + // documents that raced to load this script. + nsXULPrototypeScript* scriptProto = mCurrentScriptProto; + mCurrentScriptProto = nullptr; + + // Clear the prototype's loading flag before executing the script or + // resuming document walks, in case any of those control flows starts a + // new script load. + scriptProto->mSrcLoading = false; + + nsresult rv = aStatus; + if (NS_SUCCEEDED(rv)) { + rv = ExecuteScript(scriptProto); + + // If the XUL cache is enabled, save the script object there in + // case different XUL documents source the same script. + // + // But don't save the script in the cache unless the master XUL + // document URL is a chrome: URL. It is valid for a URL such as + // about:config to translate into a master document URL, whose + // prototype document nodes -- including prototype scripts that + // hold GC roots protecting their mJSObject pointers -- are not + // cached in the XUL prototype cache. See StartDocumentLoad, + // the fillXULCache logic. + // + // A document such as about:config is free to load a script via + // a URL such as chrome://global/content/config.js, and we must + // not cache that script object without a prototype cache entry + // containing a companion nsXULPrototypeScript node that owns a + // GC root protecting the script object. Otherwise, the script + // cache entry will dangle once the uncached prototype document + // is released when its owning XULDocument is unloaded. + // + // (See http://bugzilla.mozilla.org/show_bug.cgi?id=98207 for + // the true crime story.) + bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled(); - if (useXULCache && IsChromeURI(mDocumentURI)) { - nsXULPrototypeCache::GetInstance()->PutScript( - scriptProto->mSrcURI, - scriptProto->GetScriptObject()); - } + if (useXULCache && IsChromeURI(mDocumentURI) && scriptProto->GetScriptObject()) { + nsXULPrototypeCache::GetInstance()->PutScript( + scriptProto->mSrcURI, + scriptProto->GetScriptObject()); + } - if (mIsWritingFastLoad && mCurrentPrototype != mMasterPrototype) { - // If we are loading an overlay script, try to serialize - // it to the FastLoad file here. Master scripts will be - // serialized when the master prototype document gets - // written, at the bottom of ResumeWalk. That way, master - // out-of-line scripts are serialized in the same order that - // they'll be read, in the FastLoad file, which reduces the - // number of seeks that dump the underlying stream's buffer. - // - // Ignore the return value, as we don't need to propagate - // a failure to write to the FastLoad file, because this - // method aborts that whole process on error. - nsIScriptGlobalObject* global = - mCurrentPrototype->GetScriptGlobalObject(); + if (mIsWritingFastLoad && mCurrentPrototype != mMasterPrototype) { + // If we are loading an overlay script, try to serialize + // it to the FastLoad file here. Master scripts will be + // serialized when the master prototype document gets + // written, at the bottom of ResumeWalk. That way, master + // out-of-line scripts are serialized in the same order that + // they'll be read, in the FastLoad file, which reduces the + // number of seeks that dump the underlying stream's buffer. + // + // Ignore the return value, as we don't need to propagate + // a failure to write to the FastLoad file, because this + // method aborts that whole process on error. + nsIScriptGlobalObject* global = + mCurrentPrototype->GetScriptGlobalObject(); - NS_ASSERTION(global != nullptr, "master prototype w/o global?!"); - if (global) { - nsIScriptContext *scriptContext = \ - global->GetScriptContext(); - NS_ASSERTION(scriptContext != nullptr, - "Failed to get script context for language"); - if (scriptContext) - scriptProto->SerializeOutOfLine(nullptr, global); - } + NS_ASSERTION(global != nullptr, "master prototype w/o global?!"); + if (global) { + nsIScriptContext *scriptContext = + global->GetScriptContext(); + NS_ASSERTION(scriptContext != nullptr, + "Failed to get script context for language"); + if (scriptContext) + scriptProto->SerializeOutOfLine(nullptr, global); } } + // ignore any evaluation errors } @@ -3628,7 +3661,6 @@ XULDocument::OnStreamComplete(nsIStreamLoader* aLoader, return rv; } - nsresult XULDocument::ExecuteScript(nsIScriptContext * aContext, JS::Handle aScriptObject) diff --git a/content/xul/document/src/XULDocument.h b/content/xul/document/src/XULDocument.h index e9c06675742..6ba57ca9f67 100644 --- a/content/xul/document/src/XULDocument.h +++ b/content/xul/document/src/XULDocument.h @@ -88,7 +88,8 @@ class XULDocument MOZ_FINAL : public XMLDocument, public nsIXULDocument, public nsIDOMXULDocument, public nsIStreamLoaderObserver, - public nsICSSLoaderObserver + public nsICSSLoaderObserver, + public nsIOffThreadScriptReceiver { public: XULDocument(); @@ -173,6 +174,8 @@ public: virtual void ResetDocumentLWTheme() MOZ_OVERRIDE { mDocLWTheme = Doc_Theme_Uninitialized; } + NS_IMETHOD OnScriptCompileComplete(JSScript* aScript, nsresult aStatus) MOZ_OVERRIDE; + static bool MatchAttribute(nsIContent* aContent, int32_t aNameSpaceID, @@ -439,6 +442,18 @@ protected: */ nsXULPrototypeScript* mCurrentScriptProto; + /** + * Whether the current transcluded script is being compiled off thread. + * The load event is blocked while this is in progress. + */ + bool mOffThreadCompiling; + + /** + * If the current transcluded script is being compiled off thread, the + * source for that script. + */ + nsString mOffThreadCompileString; + /** * Check if a XUL template builder has already been hooked up. */ diff --git a/content/xul/document/src/nsXULPrototypeCache.cpp b/content/xul/document/src/nsXULPrototypeCache.cpp index 015a4fc14ed..de1935a2a38 100644 --- a/content/xul/document/src/nsXULPrototypeCache.cpp +++ b/content/xul/document/src/nsXULPrototypeCache.cpp @@ -204,6 +204,8 @@ nsresult nsXULPrototypeCache::PutScript(nsIURI* aURI, JS::Handle aScriptObject) { + MOZ_ASSERT(aScriptObject, "Need a non-NULL script"); + #ifdef DEBUG if (mScriptTable.Get(aURI)) { nsAutoCString scriptName; diff --git a/dom/base/nsIScriptContext.h b/dom/base/nsIScriptContext.h index ca657786acf..b280d3cd1e2 100644 --- a/dom/base/nsIScriptContext.h +++ b/dom/base/nsIScriptContext.h @@ -35,6 +35,8 @@ class nsIURI; know what language we have is a little silly... */ #define SCRIPTVERSION_DEFAULT JSVERSION_DEFAULT +class nsIOffThreadScriptReceiver; + /** * It is used by the application to initialize a runtime and run scripts. * A script runtime would implement this interface. @@ -176,5 +178,24 @@ public: NS_DEFINE_STATIC_IID_ACCESSOR(nsIScriptContext, NS_ISCRIPTCONTEXT_IID) +#define NS_IOFFTHREADSCRIPTRECEIVER_IID \ +{0x3a980010, 0x878d, 0x46a9, \ + {0x93, 0xad, 0xbc, 0xfd, 0xd3, 0x8e, 0xa0, 0xc2}} + +class nsIOffThreadScriptReceiver : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IOFFTHREADSCRIPTRECEIVER_IID) + + /** + * Notify this object that a previous CompileScript call specifying this as + * aOffThreadReceiver has completed. The script being passed in must be + * rooted before any call which could trigger GC. + */ + NS_IMETHOD OnScriptCompileComplete(JSScript* aScript, nsresult aStatus) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIOffThreadScriptReceiver, NS_IOFFTHREADSCRIPTRECEIVER_IID) + #endif // nsIScriptContext_h__ diff --git a/dom/tests/mochitest/ajax/offline/Makefile.in b/dom/tests/mochitest/ajax/offline/Makefile.in index 946116c01f0..07e467b21f9 100644 --- a/dom/tests/mochitest/ajax/offline/Makefile.in +++ b/dom/tests/mochitest/ajax/offline/Makefile.in @@ -13,90 +13,11 @@ include $(DEPTH)/config/autoconf.mk MOCHITEST_FILES = \ offlineTests.js \ - test_badManifestMagic.html \ - test_bypass.html \ - test_missingFile.html \ - test_noManifest.html \ - test_simpleManifest.html \ - test_identicalManifest.html \ - test_changingManifest.html \ - test_refetchManifest.html \ - test_offlineIFrame.html \ test_bug445544.html \ - test_bug460353.html \ - test_bug474696.html \ - test_bug544462.html \ - test_bug744719.html \ - 744719.cacheManifest \ - 744719.cacheManifest^headers^ \ - test_bug765203.html \ - unknownSection.cacheManifest \ - unknownSection.cacheManifest^headers^ \ - test_bug744719-cancel.html \ - 744719-cancel.cacheManifest \ - 744719-cancel.cacheManifest^headers^ \ - subresource744719.html \ - test_foreign.html \ - test_fallback.html \ - test_overlap.html \ - test_redirectManifest.html \ - test_redirectUpdateItem.html \ - overlap.cacheManifest \ - overlap.cacheManifest^headers^ \ - test_updatingManifest.html \ - test_updateCheck.html \ 445544_part1.html \ 445544_part2.html \ 445544.cacheManifest \ 445544.cacheManifest^headers^ \ - 460353_iframe_nomanifest.html \ - 460353_iframe_ownmanifest.html \ - 460353_iframe_samemanifest.html \ - test_obsolete.html \ - obsolete.html \ - obsoletingManifest.sjs \ - badManifestMagic.cacheManifest \ - badManifestMagic.cacheManifest^headers^ \ - bypass.cacheManifest \ - bypass.cacheManifest^headers^ \ - bypass.html \ - dynamicRedirect.sjs \ - explicitRedirect.sjs \ - fallback.html \ - fallback2.html \ - fallbackTop.html \ - fallback.cacheManifest \ - fallback.cacheManifest^headers^ \ - foreign1.cacheManifest \ - foreign1.cacheManifest^headers^ \ - foreign2.cacheManifest \ - foreign2.cacheManifest^headers^ \ - foreign2.html \ - notonwhitelist.html \ - onwhitelist.html \ - onwhitelist.html^headers^ \ - updatingIframe.sjs \ - updatingImplicit.html \ - manifestRedirect.sjs \ - missingFile.cacheManifest \ - missingFile.cacheManifest^headers^ \ - redirects.sjs \ - simpleManifest.cacheManifest \ - simpleManifest.cacheManifest^headers^ \ - wildcardManifest.cacheManifest \ - wildcardManifest.cacheManifest^headers^ \ - updatingManifest.sjs \ - changing1Sec.sjs \ - changing1Hour.sjs \ - changingManifest.sjs \ - offlineChild.html \ - test_xhtmlManifest.xhtml \ - test_missingManifest.html \ - missing.html \ - jupiter.jpg \ - test_cancelOfflineCache.html \ - test_lowDeviceStorage.html \ - test_lowDeviceStorageDuringUpdate.html \ $(NULL) # test_offlineMode.html disabled due to bug 656943 diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index 06af275b017..5b78aab371e 100644 --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -194,7 +194,8 @@ frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject sco return NULL; // Saving source is not yet supported when parsing off thread. - JS_ASSERT_IF(!cx->isJSContext(), !extraSct && options.sourcePolicy == CompileOptions::NO_SOURCE); + JS_ASSERT_IF(!cx->isJSContext(), + !extraSct && options.sourcePolicy != CompileOptions::SAVE_SOURCE); SourceCompressionToken *sct = extraSct; Maybe mysct; diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index d353613751d..47832e43cdf 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -613,7 +613,7 @@ Fold(ExclusiveContext *cx, ParseNode **pnp, return true; RootedString left(cx, pn1->pn_atom); RootedString right(cx, pn2->pn_atom); - RootedString str(cx, ConcatStrings(cx->asJSContext(), left, right)); + RootedString str(cx, ConcatStrings(cx, left, right)); if (!str) return false; pn->pn_atom = AtomizeString(cx, str); diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index ceed2127464..d80b24b40ea 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -1232,7 +1232,8 @@ Parser::newFunction(GenericParseContext *pc, HandleAtom atom, pc = pc->parent; RootedObject parent(context); - parent = pc->sc->isFunctionBox() ? NULL : pc->sc->asGlobalSharedContext()->scopeChain(); + if (!pc->sc->isFunctionBox() && options().compileAndGo) + parent = pc->sc->asGlobalSharedContext()->scopeChain(); RootedFunction fun(context); JSFunction::Flags flags = (kind == Expression) @@ -1242,17 +1243,10 @@ Parser::newFunction(GenericParseContext *pc, HandleAtom atom, : JSFunction::INTERPRETED; fun = NewFunction(context, NullPtr(), NULL, 0, flags, parent, atom, JSFunction::FinalizeKind, MaybeSingletonObject); + if (!fun) + return NULL; if (options().selfHostingMode) fun->setIsSelfHostedBuiltin(); - if (fun && !options().compileAndGo) { - if (!context->shouldBeJSContext()) - return NULL; - if (!JSObject::clearParent(context->asJSContext(), fun)) - return NULL; - if (!JSObject::clearType(context->asJSContext(), fun)) - return NULL; - fun->setEnvironment(NULL); - } return fun; } diff --git a/js/src/gc/Zone.cpp b/js/src/gc/Zone.cpp index d0bbb673ab0..1d8aa9dccdd 100644 --- a/js/src/gc/Zone.cpp +++ b/js/src/gc/Zone.cpp @@ -73,6 +73,9 @@ Zone::setNeedsBarrier(bool needs, ShouldUpdateIon updateIon) } #endif + if (needs && runtimeFromMainThread()->isAtomsZone(this)) + JS_ASSERT(!runtimeFromMainThread()->exclusiveThreadsPresent()); + needsBarrier_ = needs; } diff --git a/js/src/jit/VMFunctions.h b/js/src/jit/VMFunctions.h index f5a3d4bba35..950316ad5b1 100644 --- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -387,6 +387,9 @@ template struct MatchContext { }; template <> struct MatchContext { static const ExecutionMode execMode = SequentialExecution; }; +template <> struct MatchContext { + static const ExecutionMode execMode = SequentialExecution; +}; template <> struct MatchContext { static const ExecutionMode execMode = ParallelExecution; }; diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index b340f555131..7f9b69e5cce 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -4841,6 +4841,69 @@ JS::Compile(JSContext *cx, HandleObject obj, CompileOptions options, const char return script; } +JS_PUBLIC_API(bool) +JS::CanCompileOffThread(JSContext *cx, const CompileOptions &options) +{ +#if defined(JS_THREADSAFE) && defined(JS_ION) + if (!cx->runtime()->useHelperThreads() || !cx->runtime()->helperThreadCount()) + return false; + + // Off thread compilation can't occur during incremental collections on the + // atoms compartment, to avoid triggering barriers. Outside the atoms + // compartment, the compilation will use a new zone which doesn't require + // barriers itself. + if (cx->runtime()->atomsZoneNeedsBarrier()) + return false; + + // Blacklist filenames which cause mysterious assertion failures in + // graphics code on OS X. These seem to tickle some preexisting race + // condition unrelated to off thread compilation. See bug 897655. + static const char *blacklist[] = { +#ifdef XP_MACOSX + "chrome://browser/content/places/editBookmarkOverlay.js", + "chrome://browser/content/nsContextMenu.js", + "chrome://browser/content/newtab/newTab.js", + "chrome://browser/content/places/browserPlacesViews.js", +#endif + NULL + }; + + const char *filename = options.filename; + for (const char **ptest = blacklist; *ptest; ptest++) { + if (!strcmp(*ptest, filename)) + return false; + } + + return true; +#else + return false; +#endif +} + +JS_PUBLIC_API(bool) +JS::CompileOffThread(JSContext *cx, Handle obj, CompileOptions options, + const jschar *chars, size_t length, + OffThreadCompileCallback callback, void *callbackData) +{ +#if defined(JS_THREADSAFE) && defined(JS_ION) + JS_ASSERT(CanCompileOffThread(cx, options)); + return StartOffThreadParseScript(cx, options, chars, length, obj, callback, callbackData); +#else + MOZ_ASSUME_UNREACHABLE("Off thread compilation is only available with JS_ION"); +#endif +} + +JS_PUBLIC_API(void) +JS::FinishOffThreadScript(JSRuntime *rt, JSScript *script) +{ +#if defined(JS_THREADSAFE) && defined(JS_ION) + JS_ASSERT(CurrentThreadCanAccessRuntime(rt)); + rt->workerThreadState->finishParseTaskForScript(script); +#else + MOZ_ASSUME_UNREACHABLE("Off thread compilation is only available with JS_ION"); +#endif +} + JS_PUBLIC_API(JSScript *) JS_CompileUCScriptForPrincipals(JSContext *cx, JSObject *objArg, JSPrincipals *principals, const jschar *chars, size_t length, diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 91f5b26a8ae..ce1f7450bde 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -4119,6 +4119,33 @@ Compile(JSContext *cx, JS::Handle obj, CompileOptions options, FILE * extern JS_PUBLIC_API(JSScript *) Compile(JSContext *cx, JS::Handle obj, CompileOptions options, const char *filename); +extern JS_PUBLIC_API(bool) +CanCompileOffThread(JSContext *cx, const CompileOptions &options); + +/* + * Off thread compilation control flow. + * + * After successfully triggering an off thread compile of a script, the + * callback will eventually be invoked with the specified data and the result + * script or NULL. The callback will be invoked while off the main thread, so + * must ensure that its operations are thread safe. Afterwards, + * FinishOffThreadScript must be invoked on the main thread to make the script + * usable (correct compartment/zone); this method must be invoked even if the + * off thread compilation produced a NULL script. + * + * The characters passed in to CompileOffThread must remain live until the + * callback is invoked, and the resulting script will be rooted until the call + * to FinishOffThreadScript. + */ + +extern JS_PUBLIC_API(bool) +CompileOffThread(JSContext *cx, Handle obj, CompileOptions options, + const jschar *chars, size_t length, + OffThreadCompileCallback callback, void *callbackData); + +extern JS_PUBLIC_API(void) +FinishOffThreadScript(JSRuntime *rt, JSScript *script); + extern JS_PUBLIC_API(JSFunction *) CompileFunction(JSContext *cx, JS::Handle obj, CompileOptions options, const char *name, unsigned nargs, const char **argnames, diff --git a/js/src/jsatom.cpp b/js/src/jsatom.cpp index ecf52b03620..7ed4381cdd2 100644 --- a/js/src/jsatom.cpp +++ b/js/src/jsatom.cpp @@ -350,16 +350,9 @@ js::AtomizeString(ExclusiveContext *cx, JSString *str, return &atom; } - const jschar *chars; - if (str->isLinear()) { - chars = str->asLinear().chars(); - } else { - if (!cx->shouldBeJSContext()) - return NULL; - chars = str->getChars(cx->asJSContext()); - if (!chars) - return NULL; - } + const jschar *chars = str->getChars(cx); + if (!chars) + return NULL; if (JSAtom *atom = AtomizeAndCopyChars(cx, chars, str->length(), ib)) return atom; diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 35228a43a63..98fb703567d 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -277,7 +277,6 @@ struct WorkerThread; class ExclusiveContext : public ThreadSafeContext { friend class gc::ArenaLists; - friend class CompartmentChecker; friend class AutoCompartment; friend class AutoLockForExclusiveAccess; friend struct StackBaseShape; diff --git a/js/src/jscntxtinlines.h b/js/src/jscntxtinlines.h index 2f83eaed8e1..1439954197f 100644 --- a/js/src/jscntxtinlines.h +++ b/js/src/jscntxtinlines.h @@ -28,7 +28,7 @@ class CompartmentChecker public: explicit CompartmentChecker(ExclusiveContext *cx) - : compartment(cx->compartment_) + : compartment(cx->compartment()) {} /* @@ -47,14 +47,14 @@ class CompartmentChecker /* Note: should only be used when neither c1 nor c2 may be the atoms compartment. */ static void check(JSCompartment *c1, JSCompartment *c2) { - JS_ASSERT(!c1->runtimeFromMainThread()->isAtomsCompartment(c1)); - JS_ASSERT(!c2->runtimeFromMainThread()->isAtomsCompartment(c2)); + JS_ASSERT(!c1->runtimeFromAnyThread()->isAtomsCompartment(c1)); + JS_ASSERT(!c2->runtimeFromAnyThread()->isAtomsCompartment(c2)); if (c1 != c2) fail(c1, c2); } void check(JSCompartment *c) { - if (c && !compartment->runtimeFromMainThread()->isAtomsCompartment(c)) { + if (c && !compartment->runtimeFromAnyThread()->isAtomsCompartment(c)) { if (!compartment) compartment = c; else if (c != compartment) diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index a5a017c73c2..1e9fb58150d 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -617,6 +617,34 @@ JSCompartment::purge() dtoaCache.purge(); } +void +JSCompartment::clearTables() +{ + global_ = NULL; + + regExps.clearTables(); + + // No scripts should have run in this compartment. This is used when + // merging a compartment that has been used off thread into another + // compartment and zone. + JS_ASSERT(crossCompartmentWrappers.empty()); + JS_ASSERT_IF(callsiteClones.initialized(), callsiteClones.empty()); + JS_ASSERT(!ionCompartment_); + JS_ASSERT(!debugScopes); + JS_ASSERT(!gcWeakMapList); + JS_ASSERT(!analysisLifoAlloc.used()); + JS_ASSERT(enumerators->next() == enumerators); + + if (baseShapes.initialized()) + baseShapes.clear(); + if (initialShapes.initialized()) + initialShapes.clear(); + if (newTypeObjects.initialized()) + newTypeObjects.clear(); + if (lazyTypeObjects.initialized()) + lazyTypeObjects.clear(); +} + bool JSCompartment::hasScriptsOnStack() { diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index b5965907610..2ea9662f470 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -310,6 +310,7 @@ struct JSCompartment void sweep(js::FreeOp *fop, bool releaseTypes); void sweepCrossCompartmentWrappers(); void purge(); + void clearTables(); void findOutgoingEdges(js::gc::ComponentFinder &finder); @@ -400,6 +401,12 @@ JSRuntime::isAtomsZone(JS::Zone *zone) return zone == atomsCompartment_->zone(); } +inline bool +JSRuntime::atomsZoneNeedsBarrier() +{ + return atomsCompartment_->zone()->needsBarrier(); +} + // For use when changing the debug mode flag on one or more compartments. // Do not run scripts in any compartment that is scheduled for GC using this // object. See comment in updateForDebugMode. diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 0082cfcf966..26dd00fe2a7 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -1079,6 +1079,12 @@ js::AddObjectRoot(JSContext *cx, JSObject **rp, const char *name) return AddRoot(cx, rp, name, JS_GC_ROOT_OBJECT_PTR); } +extern bool +js::AddObjectRoot(JSRuntime *rt, JSObject **rp, const char *name) +{ + return AddRoot(rt, rp, name, JS_GC_ROOT_OBJECT_PTR); +} + extern bool js::AddScriptRoot(JSContext *cx, JSScript **rp, const char *name) { @@ -1218,7 +1224,7 @@ ArenaLists::allocateFromArenaInline(Zone *zone, AllocKind thingKind) * background finalization runs and can modify head or cursor at any * moment. So we always allocate a new arena in that case. */ - maybeLock.lock(zone->runtimeFromMainThread()); + maybeLock.lock(zone->runtimeFromAnyThread()); if (*bfs == BFS_RUN) { JS_ASSERT(!*al->cursor); chunk = PickChunk(zone); @@ -4768,6 +4774,50 @@ js::NewCompartment(JSContext *cx, Zone *zone, JSPrincipals *principals, return compartment.forget(); } +void +gc::MergeCompartments(JSCompartment *source, JSCompartment *target) +{ + JSRuntime *rt = source->runtimeFromMainThread(); + AutoPrepareForTracing prepare(rt); + + // Cleanup tables and other state in the source compartment that will be + // meaningless after merging into the target compartment. + + source->clearTables(); + + // Fixup compartment pointers in source to refer to target. + + for (CellIter iter(source->zone(), FINALIZE_SCRIPT); !iter.done(); iter.next()) { + JSScript *script = iter.get(); + JS_ASSERT(script->compartment() == source); + script->compartment_ = target; + } + + for (CellIter iter(source->zone(), FINALIZE_BASE_SHAPE); !iter.done(); iter.next()) { + BaseShape *base = iter.get(); + JS_ASSERT(base->compartment() == source); + base->compartment_ = target; + } + + // Fixup zone pointers in source's zone to refer to target's zone. + + for (size_t thingKind = 0; thingKind != FINALIZE_LIMIT; thingKind++) { + for (ArenaIter aiter(source->zone(), AllocKind(thingKind)); !aiter.done(); aiter.next()) { + ArenaHeader *aheader = aiter.get(); + aheader->zone = target->zone(); + } + } + + // The source should be the only compartment in its zone. + for (CompartmentsInZoneIter c(source->zone()); !c.done(); c.next()) + JS_ASSERT(c.get() == source); + + // Merge the allocator in source's zone into target's zone. + target->zone()->allocator.arenas.adoptArenas(rt, &source->zone()->allocator.arenas); + target->zone()->gcBytes += source->zone()->gcBytes; + source->zone()->gcBytes = 0; +} + void gc::RunDebugGC(JSContext *cx) { @@ -5042,6 +5092,7 @@ ArenaLists::adoptArenas(JSRuntime *rt, ArenaLists *fromArenaLists) toList->insert(fromHeader); } + fromList->cursor = &fromList->head; } } diff --git a/js/src/jsgc.h b/js/src/jsgc.h index 0384e44e6ca..220d7bc92ce 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -409,7 +409,7 @@ class ArenaLists /* For each arena kind, a list of arenas remaining to be swept. */ ArenaHeader *arenaListsToSweep[FINALIZE_LIMIT]; - /* Shape areneas to be swept in the foreground. */ + /* Shape arenas to be swept in the foreground. */ ArenaHeader *gcShapeArenasToSweep; public: @@ -662,6 +662,9 @@ AddStringRoot(JSContext *cx, JSString **rp, const char *name); extern bool AddObjectRoot(JSContext *cx, JSObject **rp, const char *name); +extern bool +AddObjectRoot(JSRuntime *rt, JSObject **rp, const char *name); + extern bool AddScriptRoot(JSContext *cx, JSScript **rp, const char *name); @@ -1335,6 +1338,13 @@ SetFullCompartmentChecks(JSContext *cx, bool enabled); void FinishBackgroundFinalize(JSRuntime *rt); +/* + * Merge all contents of source into target. This can only be used if source is + * the only compartment in its zone. + */ +void +MergeCompartments(JSCompartment *source, JSCompartment *target); + const int ZealPokeValue = 1; const int ZealAllocValue = 2; const int ZealFrameGCValue = 3; diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 7b7b0ece7e9..8ee35e30f3e 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -5105,6 +5105,21 @@ js::IsDelegate(JSContext *cx, HandleObject obj, const js::Value &v, bool *result } } +JSObject * +js::GetClassPrototypePure(GlobalObject *global, JSProtoKey protoKey) +{ + JS_ASSERT(JSProto_Null <= protoKey); + JS_ASSERT(protoKey < JSProto_LIMIT); + + if (protoKey != JSProto_Null) { + const Value &v = global->getReservedSlot(JSProto_LIMIT + protoKey); + if (v.isObject()) + return &v.toObject(); + } + + return NULL; +} + /* * The first part of this function has been hand-expanded and optimized into * NewBuiltinClassInstance in jsobjinlines.h. @@ -5113,15 +5128,9 @@ bool js_GetClassPrototype(ExclusiveContext *cx, JSProtoKey protoKey, MutableHandleObject protop, Class *clasp) { - JS_ASSERT(JSProto_Null <= protoKey); - JS_ASSERT(protoKey < JSProto_LIMIT); - - if (protoKey != JSProto_Null) { - const Value &v = cx->global()->getReservedSlot(JSProto_LIMIT + protoKey); - if (v.isObject()) { - protop.set(&v.toObject()); - return true; - } + if (JSObject *proto = GetClassPrototypePure(cx->global(), protoKey)) { + protop.set(proto); + return true; } RootedValue v(cx); diff --git a/js/src/jsobj.h b/js/src/jsobj.h index dc7e457244a..cf13160a806 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -1440,6 +1440,9 @@ js_GetClassPrototype(js::ExclusiveContext *cx, JSProtoKey protoKey, js::MutableH namespace js { +JSObject * +GetClassPrototypePure(GlobalObject *global, JSProtoKey protoKey); + extern bool SetClassAndProto(JSContext *cx, HandleObject obj, Class *clasp, Handle proto, bool checkForCycles); diff --git a/js/src/jspubtd.h b/js/src/jspubtd.h index 92326065e61..98470053b6b 100644 --- a/js/src/jspubtd.h +++ b/js/src/jspubtd.h @@ -208,6 +208,9 @@ typedef bool JSCallOnceType; typedef bool (*JSInitCallback)(void); namespace JS { + +typedef void (*OffThreadCompileCallback)(JSScript *script, void *callbackData); + namespace shadow { struct Runtime diff --git a/js/src/jsworkers.cpp b/js/src/jsworkers.cpp index 28d8f4d0e08..808526cb9d7 100644 --- a/js/src/jsworkers.cpp +++ b/js/src/jsworkers.cpp @@ -17,6 +17,7 @@ #include "jscntxtinlines.h" #include "jscompartmentinlines.h" +#include "jsobjinlines.h" using namespace js; @@ -165,23 +166,32 @@ static JSClass workerGlobalClass = { JS_ConvertStub, NULL }; -ParseTask::ParseTask(JSRuntime *rt, ExclusiveContext *cx, const CompileOptions &options, - const jschar *chars, size_t length) - : runtime(rt), cx(cx), options(options), chars(chars), length(length), - alloc(JSRuntime::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE), script(NULL) +ParseTask::ParseTask(Zone *zone, ExclusiveContext *cx, const CompileOptions &options, + const jschar *chars, size_t length, JSObject *scopeChain, + JS::OffThreadCompileCallback callback, void *callbackData) + : zone(zone), cx(cx), options(options), chars(chars), length(length), + alloc(JSRuntime::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE), scopeChain(scopeChain), + callback(callback), callbackData(callbackData), script(NULL) { + JSRuntime *rt = zone->runtimeFromMainThread(); + if (options.principals()) JS_HoldPrincipals(options.principals()); if (options.originPrincipals()) JS_HoldPrincipals(options.originPrincipals()); + if (!AddObjectRoot(rt, &this->scopeChain, "ParseTask::scopeChain")) + MOZ_CRASH(); } ParseTask::~ParseTask() { + JSRuntime *rt = zone->runtimeFromMainThread(); + if (options.principals()) - JS_DropPrincipals(runtime, options.principals()); + JS_DropPrincipals(rt, options.principals()); if (options.originPrincipals()) - JS_DropPrincipals(runtime, options.originPrincipals()); + JS_DropPrincipals(rt, options.originPrincipals()); + JS_RemoveObjectRootRT(rt, &scopeChain); // ParseTask takes over ownership of its input exclusive context. js_delete(cx); @@ -189,8 +199,13 @@ ParseTask::~ParseTask() bool js::StartOffThreadParseScript(JSContext *cx, const CompileOptions &options, - const jschar *chars, size_t length) + const jschar *chars, size_t length, HandleObject scopeChain, + JS::OffThreadCompileCallback callback, void *callbackData) { + // Suppress GC so that calls below do not trigger a new incremental GC + // which could require barriers on the atoms compartment. + gc::AutoSuppressGC suppress(cx); + frontend::MaybeCallSourceHandler(cx, options, chars, length); JSRuntime *rt = cx->runtime(); @@ -205,16 +220,27 @@ js::StartOffThreadParseScript(JSContext *cx, const CompileOptions &options, if (!global) return false; - // For now, type inference is always disabled in exclusive zones. - // This restriction would be fairly easy to lift. + // For now, type inference is always disabled in exclusive zones, as type + // inference data is not merged between zones when finishing the off thread + // parse. This restriction would be fairly easy to lift. + JS_ASSERT(!cx->typeInferenceEnabled()); global->zone()->types.inferenceEnabled = false; + JS_SetCompartmentPrincipals(global->compartment(), cx->compartment()->principals); + + RootedObject obj(cx); + // Initialize all classes needed for parsing while we are still on the main - // thread. + // thread. Do this for both the target and the new global so that prototype + // pointers can be changed infallibly after parsing finishes. + if (!js_GetClassObject(cx, cx->global(), JSProto_Function, &obj) || + !js_GetClassObject(cx, cx->global(), JSProto_Array, &obj) || + !js_GetClassObject(cx, cx->global(), JSProto_RegExp, &obj)) + { + return false; + } { AutoCompartment ac(cx, global); - - RootedObject obj(cx); if (!js_GetClassObject(cx, global, JSProto_Function, &obj) || !js_GetClassObject(cx, global, JSProto_Array, &obj) || !js_GetClassObject(cx, global, JSProto_RegExp, &obj)) @@ -223,7 +249,7 @@ js::StartOffThreadParseScript(JSContext *cx, const CompileOptions &options, } } - global->zone()->usedByExclusiveThread = true; + cx->runtime()->setUsedByExclusiveThread(global->zone()); ScopedJSDeletePtr workercx( cx->new_(cx->runtime(), (PerThreadData *) NULL, @@ -234,7 +260,8 @@ js::StartOffThreadParseScript(JSContext *cx, const CompileOptions &options, workercx->enterCompartment(global->compartment()); ScopedJSDeletePtr task( - cx->new_(cx->runtime(), workercx.get(), options, chars, length)); + cx->new_(global->zone(), workercx.get(), options, chars, length, + scopeChain, callback, callbackData)); if (!task) return false; @@ -430,6 +457,82 @@ WorkerThreadState::canStartIonCompile() return true; } +bool +WorkerThreadState::canStartParseTask() +{ + // Don't allow simultaneous off thread parses, to reduce contention on the + // atoms table. Note that asm.js compilation depends on this to avoid + // stalling the worker thread, as off thread parse tasks can trigger and + // block on other off thread asm.js compilation tasks. + JS_ASSERT(isLocked()); + if (parseWorklist.empty()) + return false; + for (size_t i = 0; i < numThreads; i++) { + if (threads[i].parseTask) + return false; + } + return true; +} + +void +WorkerThreadState::finishParseTaskForScript(JSScript *script) +{ + JSRuntime *rt = script->compartment()->runtimeFromMainThread(); + ParseTask *parseTask = NULL; + + { + AutoLockWorkerThreadState lock(rt); + for (size_t i = 0; i < parseFinishedList.length(); i++) { + if (parseFinishedList[i]->script == script) { + parseTask = parseFinishedList[i]; + parseFinishedList[i] = parseFinishedList.back(); + parseFinishedList.popBack(); + break; + } + } + } + JS_ASSERT(parseTask); + + // Mark the zone as no longer in use by an ExclusiveContext, and available + // to be collected by the GC. + rt->clearUsedByExclusiveThread(parseTask->zone); + + if (!script) { + // Parsing failed and there is nothing to finish, but there still may + // be lingering ParseTask instances holding roots which need to be + // cleaned up. The ParseTask which we picked might not be the right + // one but this is ok as finish calls will be 1:1 with calls that + // create a ParseTask. + js_delete(parseTask); + return; + } + + // Point the prototypes of any objects in the script's compartment to refer + // to the corresponding prototype in the new compartment. This will briefly + // create cross compartment pointers, which will be fixed by the + // MergeCompartments call below. + for (gc::CellIter iter(parseTask->zone, gc::FINALIZE_TYPE_OBJECT); !iter.done(); iter.next()) { + types::TypeObject *object = iter.get(); + JSObject *proto = object->proto; + if (!proto) + continue; + + JSProtoKey key = js_IdentifyClassPrototype(proto); + if (key == JSProto_Null) + continue; + + JSObject *newProto = GetClassPrototypePure(&parseTask->scopeChain->global(), key); + JS_ASSERT(newProto); + + object->proto = newProto; + } + + // Move the parsed script and all its contents into the desired compartment. + gc::MergeCompartments(parseTask->script->compartment(), parseTask->scopeChain->compartment()); + + js_delete(parseTask); +} + void WorkerThread::destroy() { @@ -548,7 +651,7 @@ void WorkerThread::handleParseWorkload(WorkerThreadState &state) { JS_ASSERT(state.isLocked()); - JS_ASSERT(!state.parseWorklist.empty()); + JS_ASSERT(state.canStartParseTask()); JS_ASSERT(idle()); parseTask = state.parseWorklist.popCopy(); @@ -562,7 +665,13 @@ WorkerThread::handleParseWorkload(WorkerThreadState &state) parseTask->chars, parseTask->length); } + // The callback is invoked while we are still off the main thread. + parseTask->callback(parseTask->script, parseTask->callbackData); + + // FinishOffThreadScript will need to be called on the script to + // migrate it into the correct compartment. state.parseFinishedList.append(parseTask); + parseTask = NULL; // Notify the main thread in case it is waiting for the parse/emit to finish. @@ -583,7 +692,7 @@ WorkerThread::threadLoop() // Block until a task is available. while (!state.canStartIonCompile() && !state.canStartAsmJSCompile() && - state.parseWorklist.empty()) + !state.canStartParseTask()) { if (state.shouldPause) pause(); @@ -597,7 +706,7 @@ WorkerThread::threadLoop() handleAsmJSWorkload(state); else if (state.canStartIonCompile()) handleIonWorkload(state); - else if (!state.parseWorklist.empty()) + else if (state.canStartParseTask()) handleParseWorkload(state); } } @@ -695,7 +804,8 @@ js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script) bool js::StartOffThreadParseScript(JSContext *cx, const CompileOptions &options, - const jschar *chars, size_t length) + const jschar *chars, size_t length, HandleObject scopeChain, + JS::OffThreadCompileCallback callback, void *callbackData) { MOZ_ASSUME_UNREACHABLE("Off thread compilation not available in non-THREADSAFE builds"); } diff --git a/js/src/jsworkers.h b/js/src/jsworkers.h index 1f877b7ed34..7fa16965df0 100644 --- a/js/src/jsworkers.h +++ b/js/src/jsworkers.h @@ -89,6 +89,7 @@ class WorkerThreadState bool canStartAsmJSCompile(); bool canStartIonCompile(); + bool canStartParseTask(); uint32_t harvestFailedAsmJSJobs() { JS_ASSERT(isLocked()); @@ -114,6 +115,8 @@ class WorkerThreadState return asmJSFailedFunction; } + void finishParseTaskForScript(JSScript *script); + private: /* @@ -224,7 +227,8 @@ CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script); */ bool StartOffThreadParseScript(JSContext *cx, const CompileOptions &options, - const jschar *chars, size_t length); + const jschar *chars, size_t length, HandleObject scopeChain, + JS::OffThreadCompileCallback callback, void *callbackData); /* Block until in progress and pending off thread parse jobs have finished. */ void @@ -332,17 +336,31 @@ struct AsmJSParallelTask struct ParseTask { - JSRuntime *runtime; + Zone *zone; ExclusiveContext *cx; CompileOptions options; const jschar *chars; size_t length; LifoAlloc alloc; + // Rooted pointer to the scope in the target compartment which the + // resulting script will be merged into. This is not safe to use off the + // main thread. + JSObject *scopeChain; + + // Callback invoked off the main thread when the parse finishes. + JS::OffThreadCompileCallback callback; + void *callbackData; + + // Holds the final script between the invocation of the callback and the + // point where FinishOffThreadScript is called, which will destroy the + // ParseTask. JSScript *script; - ParseTask(JSRuntime *rt, ExclusiveContext *cx, const CompileOptions &options, - const jschar *chars, size_t length); + ParseTask(Zone *zone, ExclusiveContext *cx, const CompileOptions &options, + const jschar *chars, size_t length, JSObject *scopeChain, + JS::OffThreadCompileCallback callback, void *callbackData); + ~ParseTask(); }; diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 5d41069823b..adbdf41a0de 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -3235,6 +3235,13 @@ SyntaxParse(JSContext *cx, unsigned argc, jsval *vp) #ifdef JS_THREADSAFE +static void +OffThreadCompileScriptCallback(JSScript *script, void *callbackData) +{ + // This callback is invoked off the main thread and there isn't a good way + // to pass the script on to the main thread. Just let the script leak. +} + static bool OffThreadCompileScript(JSContext *cx, unsigned argc, jsval *vp) { @@ -3271,8 +3278,11 @@ OffThreadCompileScript(JSContext *cx, unsigned argc, jsval *vp) if (!JS_AddStringRoot(cx, permanentRoot)) return false; - if (!StartOffThreadParseScript(cx, options, chars, length)) + if (!StartOffThreadParseScript(cx, options, chars, length, cx->global(), + OffThreadCompileScriptCallback, NULL)) + { return false; + } args.rval().setUndefined(); return true; diff --git a/js/src/vm/RegExpObject.cpp b/js/src/vm/RegExpObject.cpp index 6d186f5ba84..11d6874724d 100644 --- a/js/src/vm/RegExpObject.cpp +++ b/js/src/vm/RegExpObject.cpp @@ -681,6 +681,13 @@ RegExpCompartment::sweep(JSRuntime *rt) } } +void +RegExpCompartment::clearTables() +{ + JS_ASSERT(inUse_.empty()); + map_.clear(); +} + bool RegExpCompartment::get(ExclusiveContext *cx, JSAtom *source, RegExpFlag flags, RegExpGuard *g) { diff --git a/js/src/vm/RegExpObject.h b/js/src/vm/RegExpObject.h index 24de7d2a9f9..3180422cc22 100644 --- a/js/src/vm/RegExpObject.h +++ b/js/src/vm/RegExpObject.h @@ -319,6 +319,7 @@ class RegExpCompartment bool init(JSContext *cx); void sweep(JSRuntime *rt); + void clearTables(); bool get(ExclusiveContext *cx, JSAtom *source, RegExpFlag flags, RegExpGuard *g); diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index 6a5a6ed7a11..c516079e9cb 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -713,6 +713,22 @@ JSRuntime::onOutOfMemory(void *p, size_t nbytes, JSContext *cx) #ifdef JS_THREADSAFE +void +JSRuntime::setUsedByExclusiveThread(Zone *zone) +{ + JS_ASSERT(!zone->usedByExclusiveThread); + zone->usedByExclusiveThread = true; + numExclusiveThreads++; +} + +void +JSRuntime::clearUsedByExclusiveThread(Zone *zone) +{ + JS_ASSERT(zone->usedByExclusiveThread); + zone->usedByExclusiveThread = false; + numExclusiveThreads--; +} + bool js::CurrentThreadCanAccessRuntime(JSRuntime *rt) { diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h index 0bd401e0d13..b5d8221be87 100644 --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -774,6 +774,9 @@ struct JSRuntime : public JS::shadow::Runtime, friend class js::AutoPauseWorkersForGC; public: + void setUsedByExclusiveThread(JS::Zone *zone); + void clearUsedByExclusiveThread(JS::Zone *zone); + #endif // JS_THREADSAFE bool currentThreadHasExclusiveAccess() { @@ -1298,7 +1301,7 @@ struct JSRuntime : public JS::shadow::Runtime, js::GCHelperThread gcHelperThread; -#ifdef XP_MACOSX +#if defined(XP_MACOSX) && defined(JS_ION) js::AsmJSMachExceptionHandler asmJSMachExceptionHandler; #endif @@ -1403,6 +1406,8 @@ struct JSRuntime : public JS::shadow::Runtime, // The atoms compartment is the only one in its zone. inline bool isAtomsZone(JS::Zone *zone); + inline bool atomsZoneNeedsBarrier(); + union { /* * Cached pointers to various interned property names, initialized in diff --git a/js/src/vm/Shape.h b/js/src/vm/Shape.h index e443b2b0bdb..5499e3b03fc 100644 --- a/js/src/vm/Shape.h +++ b/js/src/vm/Shape.h @@ -230,12 +230,17 @@ class Shape; class UnownedBaseShape; struct StackBaseShape; +namespace gc { +void MergeCompartments(JSCompartment *source, JSCompartment *target); +} + class BaseShape : public js::gc::Cell { public: friend class Shape; friend struct StackBaseShape; friend struct StackShape; + friend void gc::MergeCompartments(JSCompartment *source, JSCompartment *target); enum Flag { /* Owned by the referring shape. */ diff --git a/js/src/vm/String.cpp b/js/src/vm/String.cpp index 141db6931ca..54d40d4f93c 100644 --- a/js/src/vm/String.cpp +++ b/js/src/vm/String.cpp @@ -251,7 +251,7 @@ JSRope::getCharsNonDestructiveInternal(ThreadSafeContext *cx, ScopedJSFreePtr JSFlatString * -JSRope::flattenInternal(JSContext *maybecx) +JSRope::flattenInternal(ExclusiveContext *maybecx) { /* * Perform a depth-first dag traversal, splatting each node's characters @@ -393,7 +393,7 @@ JSRope::flattenInternal(JSContext *maybecx) } JSFlatString * -JSRope::flatten(JSContext *maybecx) +JSRope::flatten(ExclusiveContext *maybecx) { #if JSGC_INCREMENTAL if (zone()->needsBarrier()) @@ -471,7 +471,7 @@ JSDependentString::getCharsZNonDestructive(ThreadSafeContext *cx, ScopedJSFreePt } JSFlatString * -JSDependentString::undepend(JSContext *cx) +JSDependentString::undepend(ExclusiveContext *cx) { JS_ASSERT(JSString::isDependent()); @@ -502,7 +502,7 @@ JSDependentString::undepend(JSContext *cx) } JSStableString * -JSInlineString::uninline(JSContext *maybecx) +JSInlineString::uninline(ExclusiveContext *maybecx) { JS_ASSERT(isInline()); size_t n = length(); @@ -571,8 +571,8 @@ ScopedThreadSafeStringInspector::ensureChars(ThreadSafeContext *cx) if (chars_) return true; - if (cx->isJSContext()) { - JSLinearString *linear = str_->ensureLinear(cx->asJSContext()); + if (cx->isExclusiveContext()) { + JSLinearString *linear = str_->ensureLinear(cx->asExclusiveContext()); if (!linear) return false; chars_ = linear->chars(); diff --git a/js/src/vm/String.h b/js/src/vm/String.h index be18810e52f..6e64febf630 100644 --- a/js/src/vm/String.h +++ b/js/src/vm/String.h @@ -266,9 +266,9 @@ class JSString : public js::gc::Cell * getCharsZ additionally ensures the array is null terminated. */ - inline const jschar *getChars(JSContext *cx); - inline const jschar *getCharsZ(JSContext *cx); - inline bool getChar(JSContext *cx, size_t index, jschar *code); + inline const jschar *getChars(js::ExclusiveContext *cx); + inline const jschar *getCharsZ(js::ExclusiveContext *cx); + inline bool getChar(js::ExclusiveContext *cx, size_t index, jschar *code); /* * Returns chars() if the string is already linear or flat. Otherwise @@ -289,11 +289,11 @@ class JSString : public js::gc::Cell /* Fallible conversions to more-derived string types. */ - inline JSLinearString *ensureLinear(JSContext *cx); - inline JSFlatString *ensureFlat(JSContext *cx); - inline JSStableString *ensureStable(JSContext *cx); + inline JSLinearString *ensureLinear(js::ExclusiveContext *cx); + inline JSFlatString *ensureFlat(js::ExclusiveContext *cx); + inline JSStableString *ensureStable(js::ExclusiveContext *cx); - static bool ensureLinear(JSContext *cx, JSString *str) { + static bool ensureLinear(js::ExclusiveContext *cx, JSString *str) { return str->ensureLinear(cx) != NULL; } @@ -465,10 +465,10 @@ class JSRope : public JSString enum UsingBarrier { WithIncrementalBarrier, NoBarrier }; template - JSFlatString *flattenInternal(JSContext *cx); + JSFlatString *flattenInternal(js::ExclusiveContext *cx); friend class JSString; - JSFlatString *flatten(JSContext *cx); + JSFlatString *flatten(js::ExclusiveContext *cx); void init(js::ThreadSafeContext *cx, JSString *left, JSString *right, size_t length); @@ -531,7 +531,7 @@ class JSDependentString : public JSLinearString js::ScopedJSFreePtr &out) const; friend class JSString; - JSFlatString *undepend(JSContext *cx); + JSFlatString *undepend(js::ExclusiveContext *cx); void init(js::ThreadSafeContext *cx, JSLinearString *base, const jschar *chars, size_t length); @@ -703,7 +703,7 @@ class JSInlineString : public JSFlatString inline jschar *init(size_t length); - JSStableString *uninline(JSContext *cx); + JSStableString *uninline(js::ExclusiveContext *cx); inline void resetLength(size_t length); @@ -1009,7 +1009,7 @@ class AutoNameVector : public AutoVectorRooter /* Avoid requiring vm/String-inl.h just to call getChars. */ JS_ALWAYS_INLINE const jschar * -JSString::getChars(JSContext *cx) +JSString::getChars(js::ExclusiveContext *cx) { if (JSLinearString *str = ensureLinear(cx)) return str->chars(); @@ -1017,7 +1017,7 @@ JSString::getChars(JSContext *cx) } JS_ALWAYS_INLINE bool -JSString::getChar(JSContext *cx, size_t index, jschar *code) +JSString::getChar(js::ExclusiveContext *cx, size_t index, jschar *code) { JS_ASSERT(index < length()); @@ -1051,7 +1051,7 @@ JSString::getChar(JSContext *cx, size_t index, jschar *code) } JS_ALWAYS_INLINE const jschar * -JSString::getCharsZ(JSContext *cx) +JSString::getCharsZ(js::ExclusiveContext *cx) { if (JSFlatString *str = ensureFlat(cx)) return str->chars(); @@ -1095,7 +1095,7 @@ JSString::getCharsZNonDestructive(js::ThreadSafeContext *cx, } JS_ALWAYS_INLINE JSLinearString * -JSString::ensureLinear(JSContext *cx) +JSString::ensureLinear(js::ExclusiveContext *cx) { return isLinear() ? &asLinear() @@ -1103,7 +1103,7 @@ JSString::ensureLinear(JSContext *cx) } JS_ALWAYS_INLINE JSFlatString * -JSString::ensureFlat(JSContext *cx) +JSString::ensureFlat(js::ExclusiveContext *cx) { return isFlat() ? &asFlat() @@ -1113,7 +1113,7 @@ JSString::ensureFlat(JSContext *cx) } JS_ALWAYS_INLINE JSStableString * -JSString::ensureStable(JSContext *maybecx) +JSString::ensureStable(js::ExclusiveContext *maybecx) { if (isRope()) { JSFlatString *flat = asRope().flatten(maybecx);