Bug 912321: Define the 'runOffThreadScript' function in the JavaScript shell, for testing off-thread compilation. r=bhackett1024

This commit is contained in:
Jim Blandy 2013-09-20 12:07:15 -07:00
parent 0e4982edc2
commit 7dfc0f08ec
3 changed files with 164 additions and 11 deletions

View File

@ -0,0 +1,21 @@
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/licenses/publicdomain/
// Test off-thread parsing.
load(libdir + 'asserts.js');
if (!getBuildConfiguration().threadsafe)
quit(0);
offThreadCompileScript('Math.sin(Math.PI/2)');
assertEq(runOffThreadScript(), 1);
offThreadCompileScript('a string which cannot be reduced to the start symbol');
assertThrowsInstanceOf(runOffThreadScript, SyntaxError);
offThreadCompileScript('smerg;');
assertThrowsInstanceOf(runOffThreadScript, ReferenceError);
offThreadCompileScript('throw "blerg";');
assertThrowsValue(runOffThreadScript, 'blerg');

View File

@ -0,0 +1,18 @@
// We still get onNewScript notifications for code compiled off the main thread.
if (!getBuildConfiguration().threadsafe)
quit(0);
var g = newGlobal();
var dbg = new Debugger(g);
var log;
dbg.onNewScript = function (s) {
log += 's';
assertEq(s.source.text, '"t" + "wine"');
}
log = '';
g.offThreadCompileScript('"t" + "wine"');
assertEq(runOffThreadScript(), 'twine');
assertEq(log, 's');

View File

@ -60,6 +60,7 @@
#include "shell/jsheaptools.h"
#include "shell/jsoptparse.h"
#include "vm/ArgumentsObject.h"
#include "vm/Monitor.h"
#include "vm/Shape.h"
#include "vm/TypedArrayObject.h"
#include "vm/WrapperObject.h"
@ -3228,11 +3229,91 @@ SyntaxParse(JSContext *cx, unsigned argc, jsval *vp)
#ifdef JS_THREADSAFE
class OffThreadState {
public:
enum State {
IDLE, /* ready to work; no token, no source */
COMPILING, /* working; no token, have source */
DONE /* compilation done: have token and source */
};
OffThreadState() : monitor(), state(IDLE), token() { }
bool init() { return monitor.init(); }
bool startIfIdle(JSContext *cx, JSString *newSource) {
AutoLockMonitor alm(monitor);
if (state != IDLE)
return false;
JS_ASSERT(!token);
JS_ASSERT(!source);
source = newSource;
if (!JS_AddStringRoot(cx, &source))
return false;
state = COMPILING;
return true;
}
void abandon(JSContext *cx) {
AutoLockMonitor alm(monitor);
JS_ASSERT(state == COMPILING);
JS_ASSERT(!token);
JS_ASSERT(source);
JS_RemoveStringRoot(cx, &source);
source = NULL;
state = IDLE;
}
void markDone(void *newToken) {
AutoLockMonitor alm(monitor);
JS_ASSERT(state == COMPILING);
JS_ASSERT(!token);
JS_ASSERT(source);
JS_ASSERT(newToken);
token = newToken;
state = DONE;
alm.notify();
}
void *waitUntilDone(JSContext *cx) {
AutoLockMonitor alm(monitor);
if (state == IDLE)
return NULL;
if (state == COMPILING) {
while (state != DONE)
alm.wait();
}
JS_ASSERT(source);
JS_RemoveStringRoot(cx, &source);
source = NULL;
JS_ASSERT(token);
void *holdToken = token;
token = NULL;
state = IDLE;
return holdToken;
}
private:
Monitor monitor;
State state;
void *token;
JSString *source;
};
static OffThreadState offThreadState;
static void
OffThreadCompileScriptCallback(void *token, 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.
offThreadState.markDone(token);
}
static bool
@ -3257,23 +3338,26 @@ OffThreadCompileScript(JSContext *cx, unsigned argc, jsval *vp)
.setCompileAndGo(true)
.setSourcePolicy(CompileOptions::SAVE_SOURCE);
if (!JS::CanCompileOffThread(cx, options)) {
JS_ReportError(cx, "cannot compile code on worker thread");
return false;
}
const jschar *chars = JS_GetStringCharsZ(cx, scriptContents);
if (!chars)
return false;
size_t length = JS_GetStringLength(scriptContents);
// Prevent the string contents from ever being GC'ed. This will leak memory
// but since the compiled script is never consumed there isn't much choice.
JSString **permanentRoot = cx->new_<JSString *>();
if (!permanentRoot)
return false;
*permanentRoot = scriptContents;
if (!JS_AddStringRoot(cx, permanentRoot))
if (!offThreadState.startIfIdle(cx, scriptContents)) {
JS_ReportError(cx, "called offThreadCompileScript without calling runOffThreadScript"
" to receive prior off-thread compilation");
return false;
}
if (!StartOffThreadParseScript(cx, options, chars, length, cx->global(),
OffThreadCompileScriptCallback, NULL))
if (!JS::CompileOffThread(cx, cx->global(), options, chars, length,
OffThreadCompileScriptCallback, NULL))
{
offThreadState.abandon(cx);
return false;
}
@ -3281,6 +3365,24 @@ OffThreadCompileScript(JSContext *cx, unsigned argc, jsval *vp)
return true;
}
static bool
runOffThreadScript(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
void *token = offThreadState.waitUntilDone(cx);
if (!token) {
JS_ReportError(cx, "called runOffThreadScript when no compilation is pending");
return false;
}
RootedScript script(cx, JS::FinishOffThreadScript(cx, cx->runtime(), token));
if (!script)
return false;
return JS_ExecuteScript(cx, cx->global(), script, args.rval().address());
}
#endif // JS_THREADSAFE
struct FreeOnReturn
@ -3917,6 +4019,13 @@ static const JSFunctionSpecWithHelp shell_functions[] = {
JS_FN_HELP("offThreadCompileScript", OffThreadCompileScript, 1, 0,
"offThreadCompileScript(code)",
" Trigger an off thread parse/emit for the input string"),
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."),
#endif
JS_FN_HELP("timeout", Timeout, 1, 0,
@ -5267,6 +5376,11 @@ main(int argc, char **argv, char **envp)
JS_SetNativeStackQuota(rt, gMaxStackSize);
#ifdef JS_THREADSAFE
if (!offThreadState.init())
return 1;
#endif
if (!InitWatchdog(rt))
return 1;