зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1431353 - Regoranise the shell interface to off-thread parsing to allow concurrent off-thread parse jobs r=luke
This commit is contained in:
Родитель
d46c20f6fe
Коммит
d4526d2471
|
@ -0,0 +1,153 @@
|
|||
// Test multiple concurrent off-thread parse jobs.
|
||||
|
||||
if (helperThreadCount() === 0)
|
||||
quit();
|
||||
|
||||
function assertFails(f) {
|
||||
let failed = false;
|
||||
try {
|
||||
f();
|
||||
} catch (e) {
|
||||
failed = true;
|
||||
}
|
||||
assertEq(failed, true);
|
||||
}
|
||||
|
||||
function encodeScript(source)
|
||||
{
|
||||
let entry = cacheEntry(source);
|
||||
let global = newGlobal({ cloneSingletons: true });
|
||||
evaluate(entry, { global: global, saveBytecode: true });
|
||||
return entry;
|
||||
}
|
||||
|
||||
let a, b, c;
|
||||
|
||||
// Calling run functions without arguments assumes a single off-thread job.
|
||||
|
||||
// Test run functions fail when no jobs exist.
|
||||
|
||||
assertFails(() => runOffThreadScript());
|
||||
|
||||
assertFails(() => finishOffThreadModule());
|
||||
|
||||
assertFails(() => runOffThreadDecodedScript());
|
||||
|
||||
// Test run functions fail when multiple jobs exist and no ID specified.
|
||||
|
||||
a = offThreadCompileScript("");
|
||||
b = offThreadCompileScript("");
|
||||
assertFails(() => runOffThreadScript());
|
||||
runOffThreadScript(a);
|
||||
runOffThreadScript(b);
|
||||
|
||||
a = offThreadCompileModule("");
|
||||
b = offThreadCompileModule("");
|
||||
assertFails(() => finishOffThreadModule());
|
||||
finishOffThreadModule(a);
|
||||
finishOffThreadModule(b);
|
||||
|
||||
a = offThreadDecodeScript(encodeScript(""));
|
||||
b = offThreadDecodeScript(encodeScript(""));
|
||||
assertFails(() => runOffThreadScript());
|
||||
runOffThreadDecodedScript(a);
|
||||
runOffThreadDecodedScript(b);
|
||||
|
||||
// Test fun functions succeed when a single job exist and no ID specified.
|
||||
|
||||
offThreadCompileScript("42");
|
||||
assertEq(runOffThreadScript(), 42);
|
||||
|
||||
offThreadCompileModule("");
|
||||
assertEq(typeof finishOffThreadModule(), "object");
|
||||
|
||||
offThreadDecodeScript(encodeScript("23"));
|
||||
assertEq(runOffThreadDecodedScript(), 23);
|
||||
|
||||
// Run functions take an ID argument returned from the compile function.
|
||||
|
||||
// Test bad ID type and unknown ID.
|
||||
|
||||
offThreadCompileScript("");
|
||||
assertFails(() => runOffThreadScript("foo"));
|
||||
assertFails(() => runOffThreadScript(42));
|
||||
runOffThreadScript();
|
||||
|
||||
offThreadCompileModule("");
|
||||
assertFails(() => finishOffThreadModule("foo"));
|
||||
assertFails(() => finishOffThreadModule(42));
|
||||
finishOffThreadModule();
|
||||
|
||||
offThreadDecodeScript(encodeScript(""));
|
||||
assertFails(() => runOffThreadDecodedScript("foo"));
|
||||
assertFails(() => runOffThreadDecodedScript(42));
|
||||
runOffThreadDecodedScript();
|
||||
|
||||
// Test stale ID.
|
||||
|
||||
a = offThreadCompileScript("");
|
||||
runOffThreadScript(a);
|
||||
assertFails(() => runOffThreadScript(a));
|
||||
|
||||
a = offThreadCompileModule("");
|
||||
finishOffThreadModule(a);
|
||||
assertFails(() => finishOffThreadModule(a));
|
||||
|
||||
a = offThreadDecodeScript(encodeScript(""));
|
||||
runOffThreadDecodedScript(a);
|
||||
assertFails(() => runOffThreadDecodedScript(a));
|
||||
|
||||
// Test wrong job kind.
|
||||
|
||||
a = offThreadCompileScript("");
|
||||
b = offThreadCompileModule("");
|
||||
c = offThreadDecodeScript(encodeScript(""));
|
||||
assertFails(() => runOffThreadScript(b));
|
||||
assertFails(() => runOffThreadScript(c));
|
||||
assertFails(() => finishOffThreadModule(a));
|
||||
assertFails(() => finishOffThreadModule(c));
|
||||
assertFails(() => runOffThreadDecodedScript(a));
|
||||
assertFails(() => runOffThreadDecodedScript(b));
|
||||
runOffThreadScript(a);
|
||||
finishOffThreadModule(b);
|
||||
runOffThreadDecodedScript(c);
|
||||
|
||||
// Test running multiple jobs.
|
||||
|
||||
a = offThreadCompileScript("1");
|
||||
b = offThreadCompileScript("2");
|
||||
assertEq(runOffThreadScript(a), 1);
|
||||
assertEq(runOffThreadScript(b), 2);
|
||||
|
||||
a = offThreadCompileModule("");
|
||||
b = offThreadCompileModule("");
|
||||
assertEq(typeof finishOffThreadModule(a), "object");
|
||||
assertEq(typeof finishOffThreadModule(b), "object");
|
||||
|
||||
a = offThreadDecodeScript(encodeScript("3"));
|
||||
b = offThreadDecodeScript(encodeScript("4"));
|
||||
assertEq(runOffThreadDecodedScript(a), 3);
|
||||
assertEq(runOffThreadDecodedScript(b), 4);
|
||||
|
||||
// Test many jobs.
|
||||
|
||||
const count = 100;
|
||||
let jobs;
|
||||
|
||||
jobs = new Array(count);
|
||||
for (let i = 0; i < jobs.length; i++)
|
||||
jobs[i] = offThreadCompileScript(`${i} * ${i}`);
|
||||
for (let i = 0; i < jobs.length; i++)
|
||||
assertEq(runOffThreadScript(jobs[i]), i * i);
|
||||
|
||||
jobs = new Array(count);
|
||||
for (let i = 0; i < jobs.length; i++)
|
||||
jobs[i] = offThreadCompileModule("");
|
||||
for (let i = 0; i < jobs.length; i++)
|
||||
assertEq(typeof finishOffThreadModule(jobs[i]), "object");
|
||||
|
||||
jobs = new Array(count);
|
||||
for (let i = 0; i < jobs.length; i++)
|
||||
jobs[i] = offThreadDecodeScript(encodeScript(`${i} * ${i}`));
|
||||
for (let i = 0; i < jobs.length; i++)
|
||||
assertEq(runOffThreadDecodedScript(jobs[i]), i * i);
|
|
@ -230,132 +230,209 @@ InstallCoverageSignalHandlers()
|
|||
}
|
||||
#endif
|
||||
|
||||
class OffThreadState {
|
||||
// An off-thread parse or decode job.
|
||||
class js::shell::OffThreadJob {
|
||||
enum State {
|
||||
IDLE, /* ready to work; no token, no source */
|
||||
COMPILING, /* working; no token, have source */
|
||||
DONE /* compilation done: have token and source */
|
||||
RUNNING, // Working; no token.
|
||||
DONE, // Finished; have token.
|
||||
CANCELLED // Cancelled due to error.
|
||||
};
|
||||
|
||||
public:
|
||||
OffThreadState()
|
||||
: monitor(mutexid::ShellOffThreadState),
|
||||
state(IDLE),
|
||||
runtime(nullptr),
|
||||
token(),
|
||||
source(nullptr)
|
||||
{ }
|
||||
using Source = mozilla::Variant<JS::UniqueTwoByteChars, JS::TranscodeBuffer>;
|
||||
|
||||
bool startIfIdle(JSContext* cx, ScriptKind kind, ScopedJSFreePtr<char16_t>& newSource);
|
||||
|
||||
bool startIfIdle(JSContext* cx, ScriptKind kind, JS::TranscodeBuffer&& newXdr);
|
||||
|
||||
void abandon(JSContext* cx);
|
||||
OffThreadJob(ShellContext* sc, ScriptKind kind, Source&& source);
|
||||
~OffThreadJob();
|
||||
|
||||
void cancel();
|
||||
void markDone(void* newToken);
|
||||
void* waitUntilDone(JSContext* cx);
|
||||
|
||||
void* waitUntilDone(JSContext* cx, ScriptKind kind);
|
||||
char16_t* sourceChars() { return source.as<UniqueTwoByteChars>().get(); }
|
||||
JS::TranscodeBuffer& xdrBuffer() { return source.as<JS::TranscodeBuffer>(); }
|
||||
|
||||
JS::TranscodeBuffer& xdrBuffer() { return xdr; }
|
||||
public:
|
||||
const int32_t id;
|
||||
const ScriptKind kind;
|
||||
|
||||
private:
|
||||
js::Monitor monitor;
|
||||
ScriptKind scriptKind;
|
||||
js::Monitor& monitor;
|
||||
State state;
|
||||
JSRuntime* runtime;
|
||||
void* token;
|
||||
char16_t* source;
|
||||
JS::TranscodeBuffer xdr;
|
||||
Source source;
|
||||
};
|
||||
static OffThreadState* gOffThreadState;
|
||||
|
||||
bool
|
||||
OffThreadState::startIfIdle(JSContext* cx, ScriptKind kind, ScopedJSFreePtr<char16_t>& newSource)
|
||||
static OffThreadJob*
|
||||
NewOffThreadJob(JSContext* cx, ScriptKind kind, OffThreadJob::Source&& source)
|
||||
{
|
||||
AutoLockMonitor alm(monitor);
|
||||
if (state != IDLE)
|
||||
return false;
|
||||
ShellContext* sc = GetShellContext(cx);
|
||||
UniquePtr<OffThreadJob> job(cx->new_<OffThreadJob>(sc, kind, Move(source)));
|
||||
if (!job)
|
||||
return nullptr;
|
||||
|
||||
MOZ_ASSERT(!token);
|
||||
|
||||
source = newSource.forget();
|
||||
|
||||
scriptKind = kind;
|
||||
runtime = cx->runtime();
|
||||
state = COMPILING;
|
||||
return true;
|
||||
if (!sc->offThreadJobs.append(job.get())) {
|
||||
JS_ReportErrorASCII(cx, "OOM adding off-thread job");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
OffThreadState::startIfIdle(JSContext* cx, ScriptKind kind, JS::TranscodeBuffer&& newXdr)
|
||||
return job.release();
|
||||
}
|
||||
|
||||
static OffThreadJob*
|
||||
GetSingleOffThreadJob(JSContext* cx, ScriptKind kind)
|
||||
{
|
||||
AutoLockMonitor alm(monitor);
|
||||
if (state != IDLE)
|
||||
return false;
|
||||
ShellContext* sc = GetShellContext(cx);
|
||||
const auto& jobs = sc->offThreadJobs;
|
||||
if (jobs.empty()) {
|
||||
JS_ReportErrorASCII(cx, "No off-thread jobs are pending");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!token);
|
||||
if (jobs.length() > 1) {
|
||||
JS_ReportErrorASCII(cx, "Multiple off-thread jobs are pending: must specify job ID");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
xdr = mozilla::Move(newXdr);
|
||||
OffThreadJob* job = jobs[0];
|
||||
if (job->kind != kind) {
|
||||
JS_ReportErrorASCII(cx, "Off-thread job is the wrong kind");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
scriptKind = kind;
|
||||
runtime = cx->runtime();
|
||||
state = COMPILING;
|
||||
return true;
|
||||
return job;
|
||||
}
|
||||
|
||||
static OffThreadJob*
|
||||
LookupOffThreadJobByID(JSContext* cx, ScriptKind kind, int32_t id)
|
||||
{
|
||||
if (id <= 0) {
|
||||
JS_ReportErrorASCII(cx, "Bad off-thread job ID");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ShellContext* sc = GetShellContext(cx);
|
||||
const auto& jobs = sc->offThreadJobs;
|
||||
if (jobs.empty()) {
|
||||
JS_ReportErrorASCII(cx, "No off-thread jobs are pending");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
OffThreadJob* job = nullptr;
|
||||
for (auto someJob : jobs) {
|
||||
if (someJob->id == id) {
|
||||
job = someJob;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!job) {
|
||||
JS_ReportErrorASCII(cx, "Off-thread job not found");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (job->kind != kind) {
|
||||
JS_ReportErrorASCII(cx, "Off-thread job is the wrong kind");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
static OffThreadJob*
|
||||
LookupOffThreadJobForArgs(JSContext* cx, ScriptKind kind, const CallArgs& args, size_t arg)
|
||||
{
|
||||
// If the optional ID argument isn't present, get the single pending job.
|
||||
if (args.length() <= arg)
|
||||
return GetSingleOffThreadJob(cx, kind);
|
||||
|
||||
// Lookup the job using the specified ID.
|
||||
int32_t id = 0;
|
||||
RootedValue value(cx, args[arg]);
|
||||
if (!ToInt32(cx, value, &id))
|
||||
return nullptr;
|
||||
|
||||
return LookupOffThreadJobByID(cx, kind, id);
|
||||
}
|
||||
|
||||
static void
|
||||
DeleteOffThreadJob(JSContext* cx, OffThreadJob* job)
|
||||
{
|
||||
ShellContext* sc = GetShellContext(cx);
|
||||
for (size_t i = 0; i < sc->offThreadJobs.length(); i++) {
|
||||
if (sc->offThreadJobs[i] == job) {
|
||||
sc->offThreadJobs.erase(&sc->offThreadJobs[i]);
|
||||
js_delete(job);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_CRASH("Off-thread job not found");
|
||||
}
|
||||
|
||||
static void
|
||||
CancelAllOffThreadJobs(JSContext* cx)
|
||||
{
|
||||
// Parse jobs may be blocked waiting on GC.
|
||||
gc::FinishGC(cx);
|
||||
|
||||
CancelOffThreadParses(cx->runtime());
|
||||
|
||||
ShellContext* sc = GetShellContext(cx);
|
||||
while (!sc->offThreadJobs.empty())
|
||||
js_delete(sc->offThreadJobs.popCopy());
|
||||
}
|
||||
|
||||
mozilla::Atomic<int32_t> gOffThreadJobSerial(1);
|
||||
|
||||
OffThreadJob::OffThreadJob(ShellContext* sc, ScriptKind kind, Source&& source)
|
||||
: id(gOffThreadJobSerial++),
|
||||
kind(kind),
|
||||
monitor(sc->offThreadMonitor),
|
||||
state(RUNNING),
|
||||
token(nullptr),
|
||||
source(Move(source))
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(id > 0, "Off-thread job IDs exhausted");
|
||||
}
|
||||
|
||||
OffThreadJob::~OffThreadJob()
|
||||
{
|
||||
MOZ_ASSERT(state != RUNNING);
|
||||
}
|
||||
|
||||
void
|
||||
OffThreadState::abandon(JSContext* cx)
|
||||
OffThreadJob::cancel()
|
||||
{
|
||||
AutoLockMonitor alm(monitor);
|
||||
MOZ_ASSERT(state == COMPILING);
|
||||
MOZ_ASSERT(state == RUNNING);
|
||||
MOZ_ASSERT(!token);
|
||||
MOZ_ASSERT(source || !xdr.empty());
|
||||
|
||||
if (source)
|
||||
js_free(source);
|
||||
source = nullptr;
|
||||
xdr.clearAndFree();
|
||||
|
||||
state = IDLE;
|
||||
state = CANCELLED;
|
||||
}
|
||||
|
||||
void
|
||||
OffThreadState::markDone(void* newToken)
|
||||
OffThreadJob::markDone(void* newToken)
|
||||
{
|
||||
AutoLockMonitor alm(monitor);
|
||||
MOZ_ASSERT(state == COMPILING);
|
||||
MOZ_ASSERT(state == RUNNING);
|
||||
MOZ_ASSERT(!token);
|
||||
MOZ_ASSERT(source || !xdr.empty());
|
||||
MOZ_ASSERT(newToken);
|
||||
|
||||
token = newToken;
|
||||
state = DONE;
|
||||
alm.notify();
|
||||
alm.notifyAll();
|
||||
}
|
||||
|
||||
void*
|
||||
OffThreadState::waitUntilDone(JSContext* cx, ScriptKind kind)
|
||||
OffThreadJob::waitUntilDone(JSContext* cx)
|
||||
{
|
||||
AutoLockMonitor alm(monitor);
|
||||
if (state == IDLE || cx->runtime() != runtime || scriptKind != kind)
|
||||
return nullptr;
|
||||
MOZ_ASSERT(state != CANCELLED);
|
||||
|
||||
if (state == COMPILING) {
|
||||
while (state != DONE)
|
||||
alm.wait();
|
||||
}
|
||||
|
||||
MOZ_ASSERT(source || !xdr.empty());
|
||||
if (source)
|
||||
js_free(source);
|
||||
source = nullptr;
|
||||
xdr.clearAndFree();
|
||||
|
||||
MOZ_ASSERT(token);
|
||||
void* holdToken = token;
|
||||
token = nullptr;
|
||||
state = IDLE;
|
||||
return holdToken;
|
||||
return token;
|
||||
}
|
||||
|
||||
struct ShellCompartmentPrivate {
|
||||
|
@ -505,9 +582,15 @@ ShellContext::ShellContext(JSContext* cx)
|
|||
quitting(false),
|
||||
readLineBufPos(0),
|
||||
errFilePtr(nullptr),
|
||||
outFilePtr(nullptr)
|
||||
outFilePtr(nullptr),
|
||||
offThreadMonitor(mutexid::ShellOffThreadState)
|
||||
{}
|
||||
|
||||
ShellContext::~ShellContext()
|
||||
{
|
||||
MOZ_ASSERT(offThreadJobs.empty());
|
||||
}
|
||||
|
||||
ShellContext*
|
||||
js::shell::GetShellContext(JSContext* cx)
|
||||
{
|
||||
|
@ -3597,6 +3680,7 @@ WorkerMain(void* arg)
|
|||
return;
|
||||
|
||||
auto guard = mozilla::MakeScopeExit([&] {
|
||||
CancelAllOffThreadJobs(cx);
|
||||
JS_DestroyContext(cx);
|
||||
js_delete(sc);
|
||||
if (input->siblingContext) {
|
||||
|
@ -4564,7 +4648,8 @@ SyntaxParse(JSContext* cx, unsigned argc, Value* vp)
|
|||
static void
|
||||
OffThreadCompileScriptCallback(void* token, void* callbackData)
|
||||
{
|
||||
gOffThreadState->markDone(token);
|
||||
auto job = static_cast<OffThreadJob*>(callbackData);
|
||||
job->markDone(token);
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -4619,21 +4704,19 @@ OffThreadCompileScript(JSContext* cx, unsigned argc, Value* vp)
|
|||
return false;
|
||||
|
||||
size_t length = scriptContents->length();
|
||||
const char16_t* chars = stableChars.twoByteRange().begin().get();
|
||||
const char16_t* chars = stableChars.twoByteChars();
|
||||
|
||||
// Make sure we own the string's chars, so that they are not freed before
|
||||
// the compilation is finished.
|
||||
ScopedJSFreePtr<char16_t> ownedChars;
|
||||
UniqueTwoByteChars ownedChars;
|
||||
if (stableChars.maybeGiveOwnershipToCaller()) {
|
||||
ownedChars = const_cast<char16_t*>(chars);
|
||||
ownedChars.reset(const_cast<char16_t*>(chars));
|
||||
} else {
|
||||
char16_t* copy = cx->pod_malloc<char16_t>(length);
|
||||
if (!copy)
|
||||
ownedChars.reset(cx->pod_malloc<char16_t>(length));
|
||||
if (!ownedChars)
|
||||
return false;
|
||||
|
||||
mozilla::PodCopy(copy, chars, length);
|
||||
ownedChars = copy;
|
||||
chars = copy;
|
||||
mozilla::PodCopy(ownedChars.get(), chars, length);
|
||||
}
|
||||
|
||||
if (!JS::CanCompileOffThread(cx, options, length)) {
|
||||
|
@ -4641,20 +4724,20 @@ OffThreadCompileScript(JSContext* cx, unsigned argc, Value* vp)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!gOffThreadState->startIfIdle(cx, ScriptKind::Script, ownedChars)) {
|
||||
JS_ReportErrorASCII(cx, "called offThreadCompileScript without calling runOffThreadScript"
|
||||
" to receive prior off-thread compilation");
|
||||
OffThreadJob* job = NewOffThreadJob(cx, ScriptKind::Script,
|
||||
OffThreadJob::Source(Move(ownedChars)));
|
||||
if (!job)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!JS::CompileOffThread(cx, options, chars, length,
|
||||
OffThreadCompileScriptCallback, nullptr))
|
||||
if (!JS::CompileOffThread(cx, options, job->sourceChars(), length,
|
||||
OffThreadCompileScriptCallback, job))
|
||||
{
|
||||
gOffThreadState->abandon(cx);
|
||||
job->cancel();
|
||||
DeleteOffThreadJob(cx, job);
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
args.rval().setInt32(job->id);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -4666,11 +4749,14 @@ runOffThreadScript(JSContext* cx, unsigned argc, Value* vp)
|
|||
if (OffThreadParsingMustWaitForGC(cx->runtime()))
|
||||
gc::FinishGC(cx);
|
||||
|
||||
void* token = gOffThreadState->waitUntilDone(cx, ScriptKind::Script);
|
||||
if (!token) {
|
||||
JS_ReportErrorASCII(cx, "called runOffThreadScript when no compilation is pending");
|
||||
OffThreadJob* job = LookupOffThreadJobForArgs(cx, ScriptKind::Script, args, 0);
|
||||
if (!job)
|
||||
return false;
|
||||
}
|
||||
|
||||
void* token = job->waitUntilDone(cx);
|
||||
MOZ_ASSERT(token);
|
||||
|
||||
DeleteOffThreadJob(cx, job);
|
||||
|
||||
RootedScript script(cx, JS::FinishOffThreadScript(cx, token));
|
||||
if (!script)
|
||||
|
@ -4704,21 +4790,19 @@ OffThreadCompileModule(JSContext* cx, unsigned argc, Value* vp)
|
|||
return false;
|
||||
|
||||
size_t length = scriptContents->length();
|
||||
const char16_t* chars = stableChars.twoByteRange().begin().get();
|
||||
const char16_t* chars = stableChars.twoByteChars();
|
||||
|
||||
// Make sure we own the string's chars, so that they are not freed before
|
||||
// the compilation is finished.
|
||||
ScopedJSFreePtr<char16_t> ownedChars;
|
||||
UniqueTwoByteChars ownedChars;
|
||||
if (stableChars.maybeGiveOwnershipToCaller()) {
|
||||
ownedChars = const_cast<char16_t*>(chars);
|
||||
ownedChars.reset(const_cast<char16_t*>(chars));
|
||||
} else {
|
||||
char16_t* copy = cx->pod_malloc<char16_t>(length);
|
||||
if (!copy)
|
||||
ownedChars.reset(cx->pod_malloc<char16_t>(length));
|
||||
if (!ownedChars)
|
||||
return false;
|
||||
|
||||
mozilla::PodCopy(copy, chars, length);
|
||||
ownedChars = copy;
|
||||
chars = copy;
|
||||
mozilla::PodCopy(ownedChars.get(), chars, length);
|
||||
}
|
||||
|
||||
if (!JS::CanCompileOffThread(cx, options, length)) {
|
||||
|
@ -4726,20 +4810,20 @@ OffThreadCompileModule(JSContext* cx, unsigned argc, Value* vp)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!gOffThreadState->startIfIdle(cx, ScriptKind::Module, ownedChars)) {
|
||||
JS_ReportErrorASCII(cx, "called offThreadCompileModule without receiving prior off-thread "
|
||||
"compilation");
|
||||
OffThreadJob* job = NewOffThreadJob(cx, ScriptKind::Module,
|
||||
OffThreadJob::Source(Move(ownedChars)));
|
||||
if (!job)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!JS::CompileOffThreadModule(cx, options, chars, length,
|
||||
OffThreadCompileScriptCallback, nullptr))
|
||||
if (!JS::CompileOffThreadModule(cx, options, job->sourceChars(), length,
|
||||
OffThreadCompileScriptCallback, job))
|
||||
{
|
||||
gOffThreadState->abandon(cx);
|
||||
job->cancel();
|
||||
DeleteOffThreadJob(cx, job);
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
args.rval().setInt32(job->id);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -4751,11 +4835,14 @@ FinishOffThreadModule(JSContext* cx, unsigned argc, Value* vp)
|
|||
if (OffThreadParsingMustWaitForGC(cx->runtime()))
|
||||
gc::FinishGC(cx);
|
||||
|
||||
void* token = gOffThreadState->waitUntilDone(cx, ScriptKind::Module);
|
||||
if (!token) {
|
||||
JS_ReportErrorASCII(cx, "called finishOffThreadModule when no compilation is pending");
|
||||
OffThreadJob* job = LookupOffThreadJobForArgs(cx, ScriptKind::Module, args, 0);
|
||||
if (!job)
|
||||
return false;
|
||||
}
|
||||
|
||||
void* token = job->waitUntilDone(cx);
|
||||
MOZ_ASSERT(token);
|
||||
|
||||
DeleteOffThreadJob(cx, job);
|
||||
|
||||
RootedObject module(cx, JS::FinishOffThreadModule(cx, token));
|
||||
if (!module)
|
||||
|
@ -4828,20 +4915,20 @@ OffThreadDecodeScript(JSContext* cx, unsigned argc, Value* vp)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!gOffThreadState->startIfIdle(cx, ScriptKind::DecodeScript, mozilla::Move(loadBuffer))) {
|
||||
JS_ReportErrorASCII(cx, "called offThreadDecodeScript without calling "
|
||||
"runOffThreadDecodedScript to receive prior off-thread compilation");
|
||||
OffThreadJob* job = NewOffThreadJob(cx, ScriptKind::DecodeScript,
|
||||
OffThreadJob::Source(Move(loadBuffer)));
|
||||
if (!job)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!JS::DecodeOffThreadScript(cx, options, gOffThreadState->xdrBuffer(), 0,
|
||||
OffThreadCompileScriptCallback, nullptr))
|
||||
if (!JS::DecodeOffThreadScript(cx, options, job->xdrBuffer(), 0,
|
||||
OffThreadCompileScriptCallback, job))
|
||||
{
|
||||
gOffThreadState->abandon(cx);
|
||||
job->cancel();
|
||||
DeleteOffThreadJob(cx, job);
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
args.rval().setInt32(job->id);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -4853,11 +4940,14 @@ runOffThreadDecodedScript(JSContext* cx, unsigned argc, Value* vp)
|
|||
if (OffThreadParsingMustWaitForGC(cx->runtime()))
|
||||
gc::FinishGC(cx);
|
||||
|
||||
void* token = gOffThreadState->waitUntilDone(cx, ScriptKind::DecodeScript);
|
||||
if (!token) {
|
||||
JS_ReportErrorASCII(cx, "called runOffThreadDecodedScript when no compilation is pending");
|
||||
OffThreadJob* job = LookupOffThreadJobForArgs(cx, ScriptKind::DecodeScript, args, 0);
|
||||
if (!job)
|
||||
return false;
|
||||
}
|
||||
|
||||
void* token = job->waitUntilDone(cx);
|
||||
MOZ_ASSERT(token);
|
||||
|
||||
DeleteOffThreadJob(cx, job);
|
||||
|
||||
RootedScript script(cx, JS::FinishOffThreadScriptDecoder(cx, token));
|
||||
if (!script)
|
||||
|
@ -6858,8 +6948,9 @@ JS_FN_HELP("parseBin", BinParse, 1, 0,
|
|||
|
||||
JS_FN_HELP("offThreadCompileScript", OffThreadCompileScript, 1, 0,
|
||||
"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"
|
||||
" Compile |code| on a helper thread, returning a job ID.\n"
|
||||
" To wait for the compilation to finish and run the code, call\n"
|
||||
" |runOffThreadScript| passing the job ID. 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"
|
||||
|
@ -6871,36 +6962,39 @@ JS_FN_HELP("parseBin", BinParse, 1, 0,
|
|||
" any DOM element.\n"
|
||||
" elementAttributeName: if present and not undefined, the name of\n"
|
||||
" property of 'element' that holds this code. This is what\n"
|
||||
" Debugger.Source.prototype.elementAttributeName returns.\n"),
|
||||
" Debugger.Source.prototype.elementAttributeName returns."),
|
||||
|
||||
JS_FN_HELP("runOffThreadScript", runOffThreadScript, 0, 0,
|
||||
"runOffThreadScript()",
|
||||
" Wait for off-thread compilation to complete. If an error occurred,\n"
|
||||
"runOffThreadScript([jobID])",
|
||||
" Wait for an off-thread compilation job to complete. The job ID can be\n"
|
||||
" ommitted if there is only one job pending. If an error occurred,\n"
|
||||
" throw the appropriate exception; otherwise, run the script and return\n"
|
||||
" its value."),
|
||||
|
||||
JS_FN_HELP("offThreadCompileModule", OffThreadCompileModule, 1, 0,
|
||||
"offThreadCompileModule(code)",
|
||||
" Compile |code| on a helper thread. To wait for the compilation to finish\n"
|
||||
" and get the module object, call |finishOffThreadModule|."),
|
||||
" Compile |code| on a helper thread, returning a job ID. To wait for the\n"
|
||||
" compilation to finish and and get the module record object call\n"
|
||||
" |finishOffThreadModule| passing the job ID."),
|
||||
|
||||
JS_FN_HELP("finishOffThreadModule", FinishOffThreadModule, 0, 0,
|
||||
"finishOffThreadModule()",
|
||||
" Wait for off-thread compilation to complete. If an error occurred,\n"
|
||||
" throw the appropriate exception; otherwise, return the module object"),
|
||||
"finishOffThreadModule([jobID])",
|
||||
" Wait for an off-thread compilation job to complete. The job ID can be\n"
|
||||
" ommitted if there is only one job pending. If an error occurred,\n"
|
||||
" throw the appropriate exception; otherwise, return the module record object."),
|
||||
|
||||
JS_FN_HELP("offThreadDecodeScript", OffThreadDecodeScript, 1, 0,
|
||||
"offThreadDecodeScript(cacheEntry[, options])",
|
||||
" Decode |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"
|
||||
" (see also offThreadCompileScript)\n"),
|
||||
" Decode |code| on a helper thread, returning a job ID. To wait for the\n"
|
||||
" decoding to finish and run the code, call |runOffThreadDecodeScript| passing\n"
|
||||
" the job ID. If present, |options| may have properties saying how the code\n"
|
||||
" should be compiled (see also offThreadCompileScript)."),
|
||||
|
||||
JS_FN_HELP("runOffThreadDecodedScript", runOffThreadDecodedScript, 0, 0,
|
||||
"runOffThreadDecodedScript()",
|
||||
" Wait for off-thread decoding to complete. If an error occurred,\n"
|
||||
" throw the appropriate exception; otherwise, run the script and return\n"
|
||||
" its value."),
|
||||
"runOffThreadDecodedScript([jobID])",
|
||||
" Wait for off-thread decoding to complete. The job ID can be ommitted if there\n"
|
||||
" is only one job pending. If an error occurred, throw the appropriate\n"
|
||||
" exception; otherwise, run the script and return its value."),
|
||||
|
||||
JS_FN_HELP("timeout", Timeout, 1, 0,
|
||||
"timeout([seconds], [func])",
|
||||
|
@ -9121,13 +9215,6 @@ main(int argc, char** argv, char** envp)
|
|||
});
|
||||
JS::InitConsumeStreamCallback(cx, ConsumeBufferSource);
|
||||
|
||||
gOffThreadState = js_new<OffThreadState>();
|
||||
if (!gOffThreadState)
|
||||
return 1;
|
||||
auto deleteOffThreadState = MakeScopeExit([] {
|
||||
js_delete(gOffThreadState);
|
||||
});
|
||||
|
||||
JS_SetNativeStackQuota(cx, gMaxStackSize);
|
||||
|
||||
JS::dbg::SetDebuggerMallocSizeOf(cx, moz_malloc_size_of);
|
||||
|
@ -9180,6 +9267,8 @@ main(int argc, char** argv, char** envp)
|
|||
|
||||
DestructSharedArrayBufferMailbox();
|
||||
|
||||
CancelAllOffThreadJobs(cx);
|
||||
|
||||
JS_DestroyContext(cx);
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/Variant.h"
|
||||
|
||||
#include "jsapi.h"
|
||||
|
||||
|
@ -131,10 +132,13 @@ using MarkBitObservers = JS::WeakCache<NonshrinkingGCObjectVector>;
|
|||
using StackChars = Vector<char16_t, 0, SystemAllocPolicy>;
|
||||
#endif
|
||||
|
||||
class OffThreadJob;
|
||||
|
||||
// Per-context shell state.
|
||||
struct ShellContext
|
||||
{
|
||||
explicit ShellContext(JSContext* cx);
|
||||
~ShellContext();
|
||||
|
||||
bool isWorker;
|
||||
double timeoutInterval;
|
||||
|
@ -172,6 +176,11 @@ struct ShellContext
|
|||
|
||||
JS::UniqueChars moduleLoadPath;
|
||||
UniquePtr<MarkBitObservers> markObservers;
|
||||
|
||||
// Off-thread parse state.
|
||||
js::Monitor offThreadMonitor;
|
||||
Vector<OffThreadJob*, 0, SystemAllocPolicy> offThreadJobs;
|
||||
|
||||
};
|
||||
|
||||
extern ShellContext*
|
||||
|
|
Загрузка…
Ссылка в новой задаче