Bug 944121: Add options argument to the JS shell's offThreadCompileScript function. For off-thread compilation, put off initializing some slots of ScriptSourceObject until after the compartment merge. r=bhackett
authorJim Blandy <jimb@mozilla.com>
Wed, 22 Jan 2014 16:41:16 -0800
changeset 164770 d5da9f1e91fe4c1874aab476395a292db428b089
parent 164769 8b21c9d169995415f650f52a43dc2ee8b5e3b9a6
child 164771 8bcd545cf484d7036e67ddfb0dc152655bf2c29b
push id26060
push usercbook@mozilla.com
push dateThu, 23 Jan 2014 09:18:25 +0000
treeherdermozilla-central@d418cc97bacb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbhackett
bugs944121
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 944121: Add options argument to the JS shell's offThreadCompileScript function. For off-thread compilation, put off initializing some slots of ScriptSourceObject until after the compartment merge. r=bhackett
js/src/jit-test/tests/basic/offThreadCompileScript-01.js
js/src/jit-test/tests/basic/offThreadCompileScript-02.js
js/src/jit-test/tests/basic/offThreadCompileScript.js
js/src/jsscript.cpp
js/src/jsscript.h
js/src/jsworkers.cpp
js/src/jsworkers.h
js/src/shell/js.cpp
rename from js/src/jit-test/tests/basic/offThreadCompileScript.js
rename to js/src/jit-test/tests/basic/offThreadCompileScript-01.js
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/offThreadCompileScript-02.js
@@ -0,0 +1,17 @@
+// Test offThreadCompileScript option handling.
+
+offThreadCompileScript('Error()');
+assertEq(!!runOffThreadScript().stack.match(/^@<string>:1\n/), true);
+
+offThreadCompileScript('Error()',
+                       { fileName: "candelabra", lineNumber: 6502 });
+assertEq(!!runOffThreadScript().stack.match(/^@candelabra:6502\n/), true);
+
+var element = {};
+offThreadCompileScript('Error()', { element: element }); // shouldn't crash
+runOffThreadScript();
+
+var elementAttribute = "molybdenum";
+elementAttribute += elementAttribute + elementAttribute + elementAttribute;
+offThreadCompileScript('Error()', { elementProperty: elementAttribute }); // shouldn't crash
+runOffThreadScript();
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -985,16 +985,23 @@ ScriptSourceObject::setSource(ScriptSour
 }
 
 JSObject *
 ScriptSourceObject::element() const
 {
     return getReservedSlot(ELEMENT_SLOT).toObjectOrNull();
 }
 
+void
+ScriptSourceObject::initElement(HandleObject element)
+{
+    JS_ASSERT(getReservedSlot(ELEMENT_SLOT).isNull());
+    setReservedSlot(ELEMENT_SLOT, ObjectOrNullValue(element));
+}
+
 const Value &
 ScriptSourceObject::elementProperty() const
 {
     const Value &prop = getReservedSlot(ELEMENT_PROPERTY_SLOT);
     JS_ASSERT(prop.isUndefined() || prop.isString());
     return prop;
 }
 
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -492,16 +492,18 @@ class ScriptSourceObject : public JSObje
         AutoThreadSafeAccess ts1(lastProperty());
         AutoThreadSafeAccess ts2(lastProperty()->base());
         return static_cast<ScriptSource *>(getReservedSlot(SOURCE_SLOT).toPrivate());
     }
 
     void setSource(ScriptSource *source);
 
     JSObject *element() const;
+    void initElement(HandleObject element);
+
     const Value &elementProperty() const;
 
   private:
     static const uint32_t SOURCE_SLOT = 0;
     static const uint32_t ELEMENT_SLOT = 1;
     static const uint32_t ELEMENT_PROPERTY_SLOT = 2;
     static const uint32_t RESERVED_SLOTS = 3;
 };
--- a/js/src/jsworkers.cpp
+++ b/js/src/jsworkers.cpp
@@ -186,34 +186,53 @@ static const JSClass workerGlobalClass =
     JS_ConvertStub,   nullptr
 };
 
 ParseTask::ParseTask(ExclusiveContext *cx, JSObject *exclusiveContextGlobal, JSContext *initCx,
                      const jschar *chars, size_t length, JSObject *scopeChain,
                      JS::OffThreadCompileCallback callback, void *callbackData)
   : cx(cx), options(initCx), chars(chars), length(length),
     alloc(JSRuntime::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE), scopeChain(initCx, scopeChain),
-    exclusiveContextGlobal(initCx, exclusiveContextGlobal), callback(callback),
+    exclusiveContextGlobal(initCx, exclusiveContextGlobal), optionsElement(initCx), callback(callback),
     callbackData(callbackData), script(nullptr), errors(cx), overRecursed(false)
 {
 }
 
 bool
 ParseTask::init(JSContext *cx, const ReadOnlyCompileOptions &options)
 {
-    return this->options.copy(cx, options);
+    if (!this->options.copy(cx, options))
+        return false;
+
+    // Save those compilation options that the ScriptSourceObject can't
+    // point at while it's in the compilation's temporary compartment.
+    optionsElement = this->options.element();
+    this->options.setElement(nullptr);
+
+    return true;
 }
 
 void
 ParseTask::activate(JSRuntime *rt)
 {
     rt->setUsedByExclusiveThread(exclusiveContextGlobal->zone());
     cx->enterCompartment(exclusiveContextGlobal->compartment());
 }
 
+void
+ParseTask::finish()
+{
+    if (script) {
+        // Initialize the ScriptSourceObject slots that we couldn't while the SSO
+        // was in the temporary compartment.
+        ScriptSourceObject &sso = script->sourceObject()->as<ScriptSourceObject>();
+        sso.initElement(optionsElement);
+    }
+}
+
 ParseTask::~ParseTask()
 {
     // ParseTask takes over ownership of its input exclusive context.
     js_delete(cx);
 
     for (size_t i = 0; i < errors.length(); i++)
         js_delete(errors[i]);
 }
@@ -635,16 +654,17 @@ WorkerThreadState::finishParseTask(JSCon
 
         // Note: this is safe to do without requiring the compilation lock, as
         // the new type is not yet available to compilation threads.
         object->setProtoUnchecked(newProto);
     }
 
     // Move the parsed script and all its contents into the desired compartment.
     gc::MergeCompartments(parseTask->cx->compartment(), parseTask->scopeChain->compartment());
+    parseTask->finish();
 
     RootedScript script(rt, parseTask->script);
 
     // If we have a context, report any error or warnings generated during the
     // parse, and inform the debugger about the compiled scripts.
     if (maybecx) {
         AutoCompartment ac(maybecx, parseTask->scopeChain);
         for (size_t i = 0; i < parseTask->errors.length(); i++)
--- a/js/src/jsworkers.h
+++ b/js/src/jsworkers.h
@@ -342,16 +342,23 @@ struct ParseTask
     // 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.
     PersistentRootedObject scopeChain;
 
     // Rooted pointer to the global object used by 'cx'.
     PersistentRootedObject exclusiveContextGlobal;
 
+    // Saved GC-managed CompileOptions fields that will populate slots in
+    // the ScriptSourceObject. We create the ScriptSourceObject in the
+    // compilation's temporary compartment, so storing these values there
+    // at that point would create cross-compartment references. Instead we
+    // hold them here, and install them after merging the compartments.
+    PersistentRootedObject optionsElement;
+
     // 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;
@@ -362,16 +369,17 @@ struct ParseTask
     bool overRecursed;
 
     ParseTask(ExclusiveContext *cx, JSObject *exclusiveContextGlobal, JSContext *initCx,
               const jschar *chars, size_t length, JSObject *scopeChain,
               JS::OffThreadCompileCallback callback, void *callbackData);
     bool init(JSContext *cx, const ReadOnlyCompileOptions &options);
 
     void activate(JSRuntime *rt);
+    void finish();
 
     ~ParseTask();
 };
 
 // Return whether, if a new parse task was started, it would need to wait for
 // an in-progress GC to complete before starting.
 extern bool
 OffThreadParsingMustWaitForGC(JSRuntime *rt);
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -3302,26 +3302,40 @@ OffThreadCompileScript(JSContext *cx, un
         return false;
     }
     if (!args[0].isString()) {
         const char *typeName = JS_GetTypeName(cx, JS_TypeOfValue(cx, args[0]));
         JS_ReportError(cx, "expected string to parse, got %s", typeName);
         return false;
     }
 
-    JSString *scriptContents = args[0].toString();
+    JSAutoByteString fileNameBytes;
     CompileOptions options(cx);
-    options.setFileAndLine("<string>", 1)
-           .setCompileAndGo(true)
+    options.setFileAndLine("<string>", 1);
+
+    if (args.length() >= 2) {
+        if (args[1].isPrimitive()) {
+            JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "evaluate");
+            return false;
+        }
+
+        RootedObject opts(cx, &args[1].toObject());
+        if (!ParseCompileOptions(cx, options, opts, fileNameBytes))
+            return false;
+    }
+
+    // These option settings must override whatever the caller requested.
+    options.setCompileAndGo(true)
            .setSourcePolicy(CompileOptions::SAVE_SOURCE);
 
     // We assume the caller wants caching if at all possible, ignoring
     // heuristics that make sense for a real browser.
     options.forceAsync = true;
 
+    JSString *scriptContents = args[0].toString();
     const jschar *chars = JS_GetStringCharsZ(cx, scriptContents);
     if (!chars)
         return false;
     size_t length = JS_GetStringLength(scriptContents);
 
     if (!JS::CanCompileOffThread(cx, options, length)) {
         JS_ReportError(cx, "cannot compile code on worker thread");
         return false;
@@ -4045,18 +4059,18 @@ static const JSFunctionSpecWithHelp shel
 "      lineNumber: starting line number for error messages and debug info\n"
 "      global: global in which to execute the code\n"
 "      newContext: if true, create and use a new cx (default: false)\n"
 "      saveFrameChain: if true, save the frame chain before evaluating code\n"
 "         and restore it afterwards\n"
 "      catchTermination: if true, catch termination (failure without\n"
 "         an exception value, as for slow scripts or out-of-memory)\n"
 "         and return 'terminated'\n"
-"      element: if present with value |v|, convert |v| to an object |o| mark\n"
-"         the source as being attached to the DOM element |o|. If the\n"
+"      element: if present with value |v|, convert |v| to an object |o| and\n"
+"         mark the source as being attached to the DOM element |o|. If the\n"
 "         property is omitted or |v| is null, don't attribute the source to\n"
 "         any DOM element.\n"
 "      elementProperty: if present and not undefined, the name of property\n"
 "         of 'element' that holds this code. This is what Debugger.Source\n"
 "         .prototype.elementProperty returns.\n"
 "      sourceMapURL: if present with value |v|, convert |v| to a string, and\n"
 "         provide that as the code's source map URL. If omitted, attach no\n"
 "         source map URL to the code (although the code may provide one itself,\n"
@@ -4240,24 +4254,36 @@ static const JSFunctionSpecWithHelp shel
 "  Parses a string, potentially throwing."),
 
     JS_FN_HELP("syntaxParse", SyntaxParse, 1, 0,
 "syntaxParse(code)",
 "  Check the syntax of a string, returning success value"),
 
 #ifdef JS_THREADSAFE
     JS_FN_HELP("offThreadCompileScript", OffThreadCompileScript, 1, 0,
-"offThreadCompileScript(code)",
-"  Trigger an off thread parse/emit for the input string"),
+"offThreadCompileScript(code[, options])",
+"  Compile |code| on a helper thread. To wait for the compilation to finish\n"
+"  and run the code, call |runOffThreadScript|. If present, |options| may\n"
+"  have properties saying how the code should be compiled:\n"
+"      noScriptRval: use the no-script-rval compiler option (default: false)\n"
+"      fileName: filename for error messages and debug info\n"
+"      lineNumber: starting line number for error messages and debug info\n"
+"      element: if present with value |v|, convert |v| to an object |o| and\n"
+"         mark the source as being attached to the DOM element |o|. If the\n"
+"         property is omitted or |v| is null, don't attribute the source to\n"
+"         any DOM element.\n"
+"      elementProperty: if present and not undefined, the name of property\n"
+"         of 'element' that holds this code. This is what Debugger.Source\n"
+"         .prototype.elementProperty returns.\n"),
 
     JS_FN_HELP("runOffThreadScript", runOffThreadScript, 0, 0,
 "runOffThreadScript()",
 "  Wait for off-thread compilation to complete. If an error occurred,\n"
 "  throw the appropriate exception; otherwise, run the script and return\n"
-               "  its value."),
+"  its value."),
 
 #endif
 
     JS_FN_HELP("timeout", Timeout, 1, 0,
 "timeout([seconds], [func])",
 "  Get/Set the limit in seconds for the execution time for the current context.\n"
 "  A negative value (default) means that the execution time is unlimited.\n"
 "  If a second argument is provided, it will be invoked when the timer elapses.\n"),