Bug 1243808 - Allow modules to be compiled off main thread r=shu

This commit is contained in:
Jon Coppeard 2016-02-10 10:31:02 +00:00
Родитель 9d724cbe13
Коммит cb077bbb33
13 изменённых файлов: 550 добавлений и 101 удалений

Просмотреть файл

@ -6,6 +6,8 @@
#include "builtin/ModuleObject.h"
#include "mozilla/DebugOnly.h"
#include "builtin/SelfHostingDefines.h"
#include "frontend/ParseNode.h"
#include "frontend/SharedContext.h"
@ -109,7 +111,7 @@ GlobalObject::initImportEntryProto(JSContext* cx, Handle<GlobalObject*> global)
}
/* static */ ImportEntryObject*
ImportEntryObject::create(JSContext* cx,
ImportEntryObject::create(ExclusiveContext* cx,
HandleAtom moduleRequest,
HandleAtom importName,
HandleAtom localName)
@ -181,7 +183,7 @@ StringOrNullValue(JSString* maybeString)
}
/* static */ ExportEntryObject*
ExportEntryObject::create(JSContext* cx,
ExportEntryObject::create(ExclusiveContext* cx,
HandleAtom maybeExportName,
HandleAtom maybeModuleRequest,
HandleAtom maybeImportName,
@ -701,6 +703,62 @@ ModuleObject::initImportExportData(HandleArrayObject requestedModules,
initReservedSlot(StarExportEntriesSlot, ObjectValue(*starExportEntries));
}
static bool
FreezeObjectProperty(JSContext* cx, HandleNativeObject obj, uint32_t slot)
{
RootedObject property(cx, &obj->getSlot(slot).toObject());
return FreezeObject(cx, property);
}
/* static */ bool
ModuleObject::FreezeArrayProperties(JSContext* cx, HandleModuleObject self)
{
return FreezeObjectProperty(cx, self, RequestedModulesSlot) &&
FreezeObjectProperty(cx, self, ImportEntriesSlot) &&
FreezeObjectProperty(cx, self, LocalExportEntriesSlot) &&
FreezeObjectProperty(cx, self, IndirectExportEntriesSlot) &&
FreezeObjectProperty(cx, self, StarExportEntriesSlot);
}
static inline void
AssertObjectPropertyFrozen(JSContext* cx, HandleNativeObject obj, uint32_t slot)
{
#ifdef DEBUG
bool frozen = false;
RootedObject property(cx, &obj->getSlot(slot).toObject());
MOZ_ALWAYS_TRUE(TestIntegrityLevel(cx, property, IntegrityLevel::Frozen, &frozen));
MOZ_ASSERT(frozen);
#endif
}
/* static */ inline void
ModuleObject::AssertArrayPropertiesFrozen(JSContext* cx, HandleModuleObject self)
{
AssertObjectPropertyFrozen(cx, self, RequestedModulesSlot);
AssertObjectPropertyFrozen(cx, self, ImportEntriesSlot);
AssertObjectPropertyFrozen(cx, self, LocalExportEntriesSlot);
AssertObjectPropertyFrozen(cx, self, IndirectExportEntriesSlot);
AssertObjectPropertyFrozen(cx, self, StarExportEntriesSlot);
}
inline static void
AssertModuleScopesMatch(ModuleObject* module)
{
mozilla::DebugOnly<StaticModuleScope*> staticScope = module->staticScope();
MOZ_ASSERT(IsStaticGlobalLexicalScope(staticScope->enclosingScope()));
MOZ_ASSERT(&module->initialEnvironment().staticScope() == staticScope);
}
void
ModuleObject::fixScopesAfterCompartmentMerge(JSContext* cx)
{
AssertModuleScopesMatch(this);
Rooted<ClonedBlockObject*> lexicalScope(cx, &script()->global().lexicalScope());
staticScope()->setEnclosingScope(lexicalScope->staticScope());
initialEnvironment().setEnclosingScope(lexicalScope);
AssertModuleScopesMatch(this);
}
bool
ModuleObject::hasScript() const
{
@ -777,6 +835,8 @@ ModuleObject::noteFunctionDeclaration(ExclusiveContext* cx, HandleAtom name, Han
/* static */ bool
ModuleObject::instantiateFunctionDeclarations(JSContext* cx, HandleModuleObject self)
{
AssertArrayPropertiesFrozen(cx, self);
FunctionDeclarationVector* funDecls = self->functionDeclarations();
if (!funDecls) {
JS_ReportError(cx, "Module function declarations have already been instantiated");
@ -813,6 +873,8 @@ ModuleObject::setEvaluated()
/* static */ bool
ModuleObject::evaluate(JSContext* cx, HandleModuleObject self, MutableHandleValue rval)
{
AssertArrayPropertiesFrozen(cx, self);
RootedScript script(cx, self->script());
RootedModuleEnvironmentObject scope(cx, self->environment());
if (!scope) {
@ -903,7 +965,7 @@ js::InitModuleClasses(JSContext* cx, HandleObject obj)
///////////////////////////////////////////////////////////////////////////
// ModuleBuilder
ModuleBuilder::ModuleBuilder(JSContext* cx, HandleModuleObject module)
ModuleBuilder::ModuleBuilder(ExclusiveContext* cx, HandleModuleObject module)
: cx_(cx),
module_(cx, module),
requestedModules_(cx, AtomVector(cx)),
@ -1186,8 +1248,6 @@ ArrayObject* ModuleBuilder::createArray(const GCVector<T>& vector)
array->setDenseInitializedLength(length);
for (uint32_t i = 0; i < length; i++)
array->initDenseElement(i, MakeElementValue(vector[i]));
if (!JS_FreezeObject(cx_, array))
return nullptr;
return array;
}

Просмотреть файл

@ -45,9 +45,9 @@ class ImportEntryObject : public NativeObject
};
static const Class class_;
static JSObject* initClass(JSContext* cx, HandleObject obj);
static JSObject* initClass(ExclusiveContext* cx, HandleObject obj);
static bool isInstance(HandleValue value);
static ImportEntryObject* create(JSContext* cx,
static ImportEntryObject* create(ExclusiveContext* cx,
HandleAtom moduleRequest,
HandleAtom importName,
HandleAtom localName);
@ -72,9 +72,9 @@ class ExportEntryObject : public NativeObject
};
static const Class class_;
static JSObject* initClass(JSContext* cx, HandleObject obj);
static JSObject* initClass(ExclusiveContext* cx, HandleObject obj);
static bool isInstance(HandleValue value);
static ExportEntryObject* create(JSContext* cx,
static ExportEntryObject* create(ExclusiveContext* cx,
HandleAtom maybeExportName,
HandleAtom maybeModuleRequest,
HandleAtom maybeImportName,
@ -234,6 +234,9 @@ class ModuleObject : public NativeObject
HandleArrayObject localExportEntries,
HandleArrayObject indiretExportEntries,
HandleArrayObject starExportEntries);
static bool FreezeArrayProperties(JSContext* cx, HandleModuleObject self);
static void AssertArrayPropertiesFrozen(JSContext* cx, HandleModuleObject self);
void fixScopesAfterCompartmentMerge(JSContext* cx);
JSScript* script() const;
StaticModuleScope* staticScope() const;
@ -275,7 +278,7 @@ class ModuleObject : public NativeObject
class MOZ_STACK_CLASS ModuleBuilder
{
public:
explicit ModuleBuilder(JSContext* cx, HandleModuleObject module);
explicit ModuleBuilder(ExclusiveContext* cx, HandleModuleObject module);
bool processImport(frontend::ParseNode* pn);
bool processExport(frontend::ParseNode* pn);
@ -298,7 +301,7 @@ class MOZ_STACK_CLASS ModuleBuilder
using RootedImportEntryVector = JS::Rooted<ImportEntryVector>;
using RootedExportEntryVector = JS::Rooted<ExportEntryVector>;
JSContext* cx_;
ExclusiveContext* cx_;
RootedModuleObject module_;
RootedAtomVector requestedModules_;
RootedAtomVector importedBoundNames_;

Просмотреть файл

@ -575,7 +575,7 @@ BytecodeCompiler::compileModule()
module->init(script);
ModuleBuilder builder(cx->asJSContext(), module);
ModuleBuilder builder(cx, module);
ParseNode* pn = parser->standaloneModule(module, builder);
if (!pn)
return nullptr;
@ -759,20 +759,40 @@ frontend::CompileScript(ExclusiveContext* cx, LifoAlloc* alloc, HandleObject sco
}
ModuleObject*
frontend::CompileModule(JSContext* cx, HandleObject obj,
const ReadOnlyCompileOptions& optionsInput,
SourceBufferHolder& srcBuf)
frontend::CompileModule(ExclusiveContext* cx, const ReadOnlyCompileOptions& optionsInput,
SourceBufferHolder& srcBuf, LifoAlloc* alloc)
{
MOZ_ASSERT(srcBuf.get());
MOZ_ASSERT(cx->isJSContext() == (alloc == nullptr));
if (!alloc)
alloc = &cx->asJSContext()->tempLifoAlloc();
CompileOptions options(cx, optionsInput);
options.maybeMakeStrictMode(true); // ES6 10.2.1 Module code is always strict mode code.
options.setIsRunOnce(true);
// ! WARNING WARNING WARNING !
//
// See comment in Parser::bindLexical about optimizing global lexical
// bindings. If we start optimizing them, passing in cx's global lexical
// scope would be incorrect as cx can be an off-main-thread parse task's
// context here!
//
// ! WARNING WARNING WARNING !
Rooted<StaticScope*> staticScope(cx, &cx->global()->lexicalScope().staticBlock());
BytecodeCompiler compiler(cx, &cx->tempLifoAlloc(), options, srcBuf, staticScope,
BytecodeCompiler compiler(cx, alloc, options, srcBuf, staticScope,
TraceLogger_ParserCompileModule);
return compiler.compileModule();
RootedModuleObject module(cx, compiler.compileModule());
if (!module)
return nullptr;
// This happens in GlobalHelperThreadState::finishModuleParseTask() when a
// module is compiled off main thread.
if (cx->isJSContext() && !ModuleObject::FreezeArrayProperties(cx->asJSContext(), module))
return nullptr;
return module;
}
bool

Просмотреть файл

@ -32,9 +32,9 @@ CompileScript(ExclusiveContext* cx, LifoAlloc* alloc,
SourceCompressionTask* extraSct = nullptr,
ScriptSourceObject** sourceObjectOut = nullptr);
ModuleObject *
CompileModule(JSContext *cx, HandleObject obj, const ReadOnlyCompileOptions &options,
SourceBufferHolder &srcBuf);
ModuleObject*
CompileModule(ExclusiveContext *cx, const ReadOnlyCompileOptions &options,
SourceBufferHolder &srcBuf, LifoAlloc* alloc = nullptr);
bool
CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const char16_t* chars, size_t length);

Просмотреть файл

@ -0,0 +1,32 @@
// Test interaction with global object and global lexical scope.
function parseAndEvaluate(source) {
let m = parseModule(source);
m.declarationInstantiation();
return m.evaluation();
}
var x = 1;
assertEq(parseAndEvaluate("let r = x; x = 2; r"), 1);
assertEq(x, 2);
let y = 3;
assertEq(parseAndEvaluate("let r = y; y = 4; r"), 3);
assertEq(y, 4);
if (helperThreadCount() == 0)
quit();
function offThreadParseAndEvaluate(source) {
offThreadCompileModule(source);
let m = finishOffThreadModule();
print("compiled");
m.declarationInstantiation();
return m.evaluation();
}
assertEq(offThreadParseAndEvaluate("let r = x; x = 5; r"), 2);
assertEq(x, 5);
assertEq(offThreadParseAndEvaluate("let r = y; y = 6; r"), 4);
assertEq(y, 6);

Просмотреть файл

@ -0,0 +1,30 @@
// Test off thread module compilation.
if (helperThreadCount() == 0)
quit();
load(libdir + "dummyModuleResolveHook.js");
function offThreadParse(source) {
offThreadCompileModule(source);
return finishOffThreadModule();
}
const sa =
`export default 20;
export let a = 22;
export function f(x, y) { return x + y }
`;
const sb =
`import x from "a";
import { a as y } from "a";
import * as ns from "a";
ns.f(x, y);
`;
let a = moduleRepo['a'] = offThreadParse(sa);
let b = moduleRepo['b'] = offThreadParse(sb);
b.declarationInstantiation();
assertEq(b.evaluation(), 42);

Просмотреть файл

@ -4133,11 +4133,11 @@ JS::FinishOffThreadScript(JSContext* maybecx, JSRuntime* rt, void* token)
RootedScript script(maybecx);
{
AutoLastFrameCheck lfc(maybecx);
script = HelperThreadState().finishParseTask(maybecx, rt, token);
script = HelperThreadState().finishScriptParseTask(maybecx, rt, token);
}
return script;
} else {
return HelperThreadState().finishParseTask(maybecx, rt, token);
return HelperThreadState().finishScriptParseTask(maybecx, rt, token);
}
}

Просмотреть файл

@ -3423,7 +3423,6 @@ ParseModule(JSContext* cx, unsigned argc, Value* vp)
return false;
}
RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
JSFlatString* scriptContents = args[0].toString()->ensureFlat(cx);
if (!scriptContents)
return false;
@ -3455,7 +3454,7 @@ ParseModule(JSContext* cx, unsigned argc, Value* vp)
SourceBufferHolder srcBuf(chars, scriptContents->length(),
SourceBufferHolder::NoOwnership);
RootedObject module(cx, frontend::CompileModule(cx, global, options, srcBuf));
RootedObject module(cx, frontend::CompileModule(cx, options, srcBuf));
if (!module)
return false;
@ -3789,6 +3788,109 @@ runOffThreadScript(JSContext* cx, unsigned argc, Value* vp)
return JS_ExecuteScript(cx, script, args.rval());
}
static bool
CompileOffThreadModule(JSContext* cx, const ReadOnlyCompileOptions& options,
const char16_t* chars, size_t length,
JS::OffThreadCompileCallback callback, void* callbackData)
{
MOZ_ASSERT(JS::CanCompileOffThread(cx, options, length));
return StartOffThreadParseModule(cx, options, chars, length, callback, callbackData);
}
static JSObject*
FinishOffThreadModule(JSContext* maybecx, JSRuntime* rt, void* token)
{
MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
return HelperThreadState().finishModuleParseTask(maybecx, rt, token);
}
static bool
OffThreadCompileModule(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1 || !args[0].isString()) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
"offThreadCompileModule");
return false;
}
JSAutoByteString fileNameBytes;
CompileOptions options(cx);
options.setIntroductionType("js shell offThreadCompileModule")
.setFileAndLine("<string>", 1);
options.setIsRunOnce(true)
.setSourceIsLazy(false);
options.forceAsync = true;
JSString* scriptContents = args[0].toString();
AutoStableStringChars stableChars(cx);
if (!stableChars.initTwoByte(cx, scriptContents))
return false;
size_t length = scriptContents->length();
const char16_t* chars = stableChars.twoByteRange().start().get();
// Make sure we own the string's chars, so that they are not freed before
// the compilation is finished.
ScopedJSFreePtr<char16_t> ownedChars;
if (stableChars.maybeGiveOwnershipToCaller()) {
ownedChars = const_cast<char16_t*>(chars);
} else {
char16_t* copy = cx->pod_malloc<char16_t>(length);
if (!copy)
return false;
mozilla::PodCopy(copy, chars, length);
ownedChars = copy;
chars = copy;
}
if (!JS::CanCompileOffThread(cx, options, length)) {
JS_ReportError(cx, "cannot compile code on worker thread");
return false;
}
if (!offThreadState.startIfIdle(cx, ownedChars)) {
JS_ReportError(cx, "called offThreadCompileModule without receiving prior off-thread "
"compilation");
return false;
}
if (!CompileOffThreadModule(cx, options, chars, length,
OffThreadCompileScriptCallback, nullptr))
{
offThreadState.abandon(cx);
return false;
}
args.rval().setUndefined();
return true;
}
static bool
FinishOffThreadModule(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
JSRuntime* rt = cx->runtime();
if (OffThreadParsingMustWaitForGC(rt))
gc::AutoFinishGC finishgc(rt);
void* token = offThreadState.waitUntilDone(cx);
if (!token) {
JS_ReportError(cx, "called finishOffThreadModule when no compilation is pending");
return false;
}
RootedObject module(cx, FinishOffThreadModule(cx, rt, token));
if (!module)
return false;
args.rval().setObject(*module);
return true;
}
struct MOZ_RAII FreeOnReturn
{
JSContext* cx;
@ -5150,6 +5252,16 @@ static const JSFunctionSpecWithHelp shell_functions[] = {
" 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|."),
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"),
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"

Просмотреть файл

@ -745,3 +745,18 @@ GlobalObject::addIntrinsicValue(JSContext* cx, Handle<GlobalObject*> global,
holder->setSlot(shape->slot(), value);
return true;
}
/* static */ bool
GlobalObject::ensureModulePrototypesCreated(JSContext *cx, Handle<GlobalObject*> global)
{
if (global->getSlot(MODULE_PROTO).isUndefined()) {
MOZ_ASSERT(global->getSlot(IMPORT_ENTRY_PROTO).isUndefined() &&
global->getSlot(EXPORT_ENTRY_PROTO).isUndefined());
if (!js::InitModuleClasses(cx, global))
return false;
}
MOZ_ASSERT(global->getSlot(MODULE_PROTO).isObject() &&
global->getSlot(IMPORT_ENTRY_PROTO).isObject() &&
global->getSlot(EXPORT_ENTRY_PROTO).isObject());
return true;
}

Просмотреть файл

@ -469,16 +469,39 @@ class GlobalObject : public NativeObject
return getOrCreateObject(cx, DATE_TIME_FORMAT_PROTO, initDateTimeFormatProto);
}
static bool ensureModulePrototypesCreated(JSContext *cx, Handle<GlobalObject*> global);
JSObject* maybeGetModulePrototype() {
Value value = getSlot(MODULE_PROTO);
return value.isUndefined() ? nullptr : &value.toObject();
}
JSObject* maybeGetImportEntryPrototype() {
Value value = getSlot(IMPORT_ENTRY_PROTO);
return value.isUndefined() ? nullptr : &value.toObject();
}
JSObject* maybeGetExportEntryPrototype() {
Value value = getSlot(EXPORT_ENTRY_PROTO);
return value.isUndefined() ? nullptr : &value.toObject();
}
JSObject* getModulePrototype() {
return &getSlot(MODULE_PROTO).toObject();
JSObject* proto = maybeGetModulePrototype();
MOZ_ASSERT(proto);
return proto;
}
JSObject* getImportEntryPrototype() {
return &getSlot(IMPORT_ENTRY_PROTO).toObject();
JSObject* proto = maybeGetImportEntryPrototype();
MOZ_ASSERT(proto);
return proto;
}
JSObject* getExportEntryPrototype() {
return &getSlot(EXPORT_ENTRY_PROTO).toObject();
JSObject* proto = maybeGetExportEntryPrototype();
MOZ_ASSERT(proto);
return proto;
}
static JSFunction*

Просмотреть файл

@ -195,10 +195,10 @@ static const JSClass parseTaskGlobalClass = {
JS_GlobalObjectTraceHook
};
ParseTask::ParseTask(ExclusiveContext* cx, JSObject* exclusiveContextGlobal, JSContext* initCx,
const char16_t* chars, size_t length,
ParseTask::ParseTask(ParseTaskKind kind, ExclusiveContext* cx, JSObject* exclusiveContextGlobal,
JSContext* initCx, const char16_t* chars, size_t length,
JS::OffThreadCompileCallback callback, void* callbackData)
: cx(cx), options(initCx), chars(chars), length(length),
: kind(kind), cx(cx), options(initCx), chars(chars), length(length),
alloc(JSRuntime::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
exclusiveContextGlobal(initCx->runtime(), exclusiveContextGlobal),
callback(callback), callbackData(callbackData),
@ -244,6 +244,52 @@ ParseTask::~ParseTask()
js_delete(errors[i]);
}
ScriptParseTask::ScriptParseTask(ExclusiveContext* cx, JSObject* exclusiveContextGlobal,
JSContext* initCx, const char16_t* chars, size_t length,
JS::OffThreadCompileCallback callback, void* callbackData)
: ParseTask(ParseTaskKind::Script, cx, exclusiveContextGlobal, initCx, chars, length, callback,
callbackData)
{
}
void
ScriptParseTask::parse()
{
SourceBufferHolder srcBuf(chars, length, SourceBufferHolder::NoOwnership);
// ! WARNING WARNING WARNING !
//
// See comment in Parser::bindLexical about optimizing global lexical
// bindings. If we start optimizing them, passing in task->cx's
// global lexical scope would be incorrect!
//
// ! WARNING WARNING WARNING !
Rooted<ClonedBlockObject*> globalLexical(cx, &cx->global()->lexicalScope());
Rooted<StaticScope*> staticScope(cx, &globalLexical->staticBlock());
script = frontend::CompileScript(cx, &alloc, globalLexical, staticScope, nullptr,
options, srcBuf,
/* source_ = */ nullptr,
/* extraSct = */ nullptr,
/* sourceObjectOut = */ sourceObject.address());
}
ModuleParseTask::ModuleParseTask(ExclusiveContext* cx, JSObject* exclusiveContextGlobal,
JSContext* initCx, const char16_t* chars, size_t length,
JS::OffThreadCompileCallback callback, void* callbackData)
: ParseTask(ParseTaskKind::Module, cx, exclusiveContextGlobal, initCx, chars, length, callback,
callbackData)
{
}
void
ModuleParseTask::parse()
{
SourceBufferHolder srcBuf(chars, length, SourceBufferHolder::NoOwnership);
ModuleObject* module = frontend::CompileModule(cx, options, srcBuf, &alloc);
if (module)
script = module->script();
}
void
js::CancelOffThreadParses(JSRuntime* rt)
{
@ -285,7 +331,8 @@ js::CancelOffThreadParses(JSRuntime* rt)
if (task->runtimeMatches(rt)) {
found = true;
AutoUnlockHelperThreadState unlock;
HelperThreadState().finishParseTask(/* maybecx = */ nullptr, rt, task);
HelperThreadState().finishParseTask(/* maybecx = */ nullptr, rt, task->kind,
task);
}
}
if (!found)
@ -319,7 +366,7 @@ EnsureConstructor(JSContext* cx, Handle<GlobalObject*> global, JSProtoKey key)
// Initialize all classes potentially created during parsing for use in parser
// data structures, template objects, &c.
static bool
EnsureParserCreatedClasses(JSContext* cx)
EnsureParserCreatedClasses(JSContext* cx, ParseTaskKind kind)
{
Handle<GlobalObject*> global = cx->global();
@ -338,18 +385,15 @@ EnsureParserCreatedClasses(JSContext* cx)
if (!GlobalObject::initStarGenerators(cx, global))
return false; // needed by function*() {} and generator comprehensions
if (kind == ParseTaskKind::Module && !GlobalObject::ensureModulePrototypesCreated(cx, global))
return false;
return true;
}
bool
js::StartOffThreadParseScript(JSContext* cx, const ReadOnlyCompileOptions& options,
const char16_t* chars, size_t length,
JS::OffThreadCompileCallback callback, void* callbackData)
static JSObject*
CreateGlobalForOffThreadParse(JSContext* cx, ParseTaskKind kind, const gc::AutoSuppressGC& nogc)
{
// 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);
JSCompartment* currentCompartment = cx->compartment();
JS::CompartmentOptions compartmentOptions(currentCompartment->creationOptions(),
@ -367,21 +411,60 @@ js::StartOffThreadParseScript(JSContext* cx, const ReadOnlyCompileOptions& optio
JSObject* global = JS_NewGlobalObject(cx, &parseTaskGlobalClass, nullptr,
JS::FireOnNewGlobalHook, compartmentOptions);
if (!global)
return false;
return nullptr;
JS_SetCompartmentPrincipals(global->compartment(), currentCompartment->principals());
// Initialize all classes required for parsing while still on the main
// thread, for both the target and the new global so that prototype
// pointers can be changed infallibly after parsing finishes.
if (!EnsureParserCreatedClasses(cx))
return false;
if (!EnsureParserCreatedClasses(cx, kind))
return nullptr;
{
AutoCompartment ac(cx, global);
if (!EnsureParserCreatedClasses(cx))
return false;
if (!EnsureParserCreatedClasses(cx, kind))
return nullptr;
}
return global;
}
static bool
QueueOffThreadParseTask(JSContext* cx, ParseTask* task)
{
if (OffThreadParsingMustWaitForGC(cx->runtime())) {
AutoLockHelperThreadState lock;
if (!HelperThreadState().parseWaitingOnGC().append(task)) {
ReportOutOfMemory(cx);
return false;
}
} else {
AutoLockHelperThreadState lock;
if (!HelperThreadState().parseWorklist().append(task)) {
ReportOutOfMemory(cx);
return false;
}
task->activate(cx->runtime());
HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER);
}
return true;
}
bool
js::StartOffThreadParseScript(JSContext* cx, const ReadOnlyCompileOptions& options,
const char16_t* chars, size_t length,
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 nogc(cx);
JSObject* global = CreateGlobalForOffThreadParse(cx, ParseTaskKind::Script, nogc);
if (!global)
return false;
ScopedJSDeletePtr<ExclusiveContext> helpercx(
cx->new_<ExclusiveContext>(cx->runtime(), (PerThreadData*) nullptr,
ExclusiveContext::Context_Exclusive));
@ -389,32 +472,50 @@ js::StartOffThreadParseScript(JSContext* cx, const ReadOnlyCompileOptions& optio
return false;
ScopedJSDeletePtr<ParseTask> task(
cx->new_<ParseTask>(helpercx.get(), global, cx, chars, length,
callback, callbackData));
cx->new_<ScriptParseTask>(helpercx.get(), global, cx, chars, length,
callback, callbackData));
if (!task)
return false;
helpercx.forget();
if (!task->init(cx, options))
if (!task->init(cx, options) || !QueueOffThreadParseTask(cx, task))
return false;
if (OffThreadParsingMustWaitForGC(cx->runtime())) {
AutoLockHelperThreadState lock;
if (!HelperThreadState().parseWaitingOnGC().append(task.get())) {
ReportOutOfMemory(cx);
return false;
}
} else {
AutoLockHelperThreadState lock;
if (!HelperThreadState().parseWorklist().append(task.get())) {
ReportOutOfMemory(cx);
return false;
}
task.forget();
task->activate(cx->runtime());
HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER);
}
return true;
}
bool
js::StartOffThreadParseModule(JSContext* cx, const ReadOnlyCompileOptions& options,
const char16_t* chars, size_t length,
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 nogc(cx);
JSObject* global = CreateGlobalForOffThreadParse(cx, ParseTaskKind::Module, nogc);
if (!global)
return false;
ScopedJSDeletePtr<ExclusiveContext> helpercx(
cx->new_<ExclusiveContext>(cx->runtime(), (PerThreadData*) nullptr,
ExclusiveContext::Context_Exclusive));
if (!helpercx)
return false;
ScopedJSDeletePtr<ParseTask> task(
cx->new_<ModuleParseTask>(helpercx.get(), global, cx, chars, length,
callback, callbackData));
if (!task)
return false;
helpercx.forget();
if (!task->init(cx, options) || !QueueOffThreadParseTask(cx, task))
return false;
task.forget();
@ -1015,7 +1116,8 @@ LeaveParseTaskZone(JSRuntime* rt, ParseTask* task)
}
JSScript*
GlobalHelperThreadState::finishParseTask(JSContext* maybecx, JSRuntime* rt, void* token)
GlobalHelperThreadState::finishParseTask(JSContext* maybecx, JSRuntime* rt, ParseTaskKind kind,
void* token)
{
ScopedJSDeletePtr<ParseTask> parseTask;
@ -1033,6 +1135,7 @@ GlobalHelperThreadState::finishParseTask(JSContext* maybecx, JSRuntime* rt, void
}
}
MOZ_ASSERT(parseTask);
MOZ_ASSERT(parseTask->kind == kind);
if (!maybecx) {
LeaveParseTaskZone(rt, parseTask);
@ -1045,7 +1148,7 @@ GlobalHelperThreadState::finishParseTask(JSContext* maybecx, JSRuntime* rt, void
// Make sure we have all the constructors we need for the prototype
// remapping below, since we can't GC while that's happening.
Rooted<GlobalObject*> global(cx, &cx->global()->as<GlobalObject>());
if (!EnsureParserCreatedClasses(cx)) {
if (!EnsureParserCreatedClasses(cx, kind)) {
LeaveParseTaskZone(rt, parseTask);
return nullptr;
}
@ -1091,6 +1194,34 @@ GlobalHelperThreadState::finishParseTask(JSContext* maybecx, JSRuntime* rt, void
return script;
}
JSScript*
GlobalHelperThreadState::finishScriptParseTask(JSContext* maybecx, JSRuntime* rt, void* token)
{
JSScript* script = finishParseTask(maybecx, rt, ParseTaskKind::Script, token);
MOZ_ASSERT_IF(script, script->isGlobalCode());
return script;
}
JSObject*
GlobalHelperThreadState::finishModuleParseTask(JSContext* maybecx, JSRuntime* rt, void* token)
{
JSScript* script = finishParseTask(maybecx, rt, ParseTaskKind::Module, token);
if (!script)
return nullptr;
MOZ_ASSERT(script->module());
if (!maybecx)
return nullptr;
JSContext* cx = maybecx;
RootedModuleObject module(cx, script->module());
module->fixScopesAfterCompartmentMerge(cx);
if (!ModuleObject::FreezeArrayProperties(cx, module))
return nullptr;
return module;
}
JSObject*
GlobalObject::getStarGeneratorFunctionPrototype()
{
@ -1118,8 +1249,13 @@ GlobalHelperThreadState::mergeParseTaskCompartment(JSRuntime* rt, ParseTask* par
// Generator functions don't have Function.prototype as prototype but a
// different function object, so the IdentifyStandardPrototype trick
// below won't work. Just special-case it.
JSObject* parseTaskStarGenFunctionProto =
parseTask->exclusiveContextGlobal->as<GlobalObject>().getStarGeneratorFunctionPrototype();
GlobalObject* parseGlobal = &parseTask->exclusiveContextGlobal->as<GlobalObject>();
JSObject* parseTaskStarGenFunctionProto = parseGlobal->getStarGeneratorFunctionPrototype();
// Module objects don't have standard prototypes either.
JSObject* moduleProto = parseGlobal->maybeGetModulePrototype();
JSObject* importEntryProto = parseGlobal->maybeGetImportEntryPrototype();
JSObject* exportEntryProto = parseGlobal->maybeGetExportEntryPrototype();
// Point the prototypes of any objects in the script's compartment to refer
// to the corresponding prototype in the new compartment. This will briefly
@ -1134,21 +1270,24 @@ GlobalHelperThreadState::mergeParseTaskCompartment(JSRuntime* rt, ParseTask* par
JSObject* protoObj = proto.toObject();
JSObject* newProto;
if (protoObj == parseTaskStarGenFunctionProto) {
newProto = global->getStarGeneratorFunctionPrototype();
} else {
JSProtoKey key = JS::IdentifyStandardPrototype(protoObj);
if (key == JSProto_Null)
continue;
JSProtoKey key = JS::IdentifyStandardPrototype(protoObj);
if (key != JSProto_Null) {
MOZ_ASSERT(key == JSProto_Object || key == JSProto_Array ||
key == JSProto_Function || key == JSProto_RegExp ||
key == JSProto_Iterator);
newProto = GetBuiltinPrototypePure(global, key);
} else if (protoObj == parseTaskStarGenFunctionProto) {
newProto = global->getStarGeneratorFunctionPrototype();
} else if (protoObj == moduleProto) {
newProto = global->getModulePrototype();
} else if (protoObj == importEntryProto) {
newProto = global->getImportEntryPrototype();
} else if (protoObj == exportEntryProto) {
newProto = global->getExportEntryPrototype();
} else {
continue;
}
MOZ_ASSERT(newProto);
group->setProtoUnchecked(TaggedProto(newProto));
}
}
@ -1389,25 +1528,7 @@ HelperThread::handleParseWorkload()
AutoUnlockHelperThreadState unlock;
PerThreadData::AutoEnterRuntime enter(threadData.ptr(),
task->exclusiveContextGlobal->runtimeFromAnyThread());
SourceBufferHolder srcBuf(task->chars, task->length,
SourceBufferHolder::NoOwnership);
// ! WARNING WARNING WARNING !
//
// See comment in Parser::bindLexical about optimizing global lexical
// bindings. If we start optimizing them, passing in task->cx's
// global lexical scope would be incorrect!
//
// ! WARNING WARNING WARNING !
ExclusiveContext* parseCx = task->cx;
Rooted<ClonedBlockObject*> globalLexical(parseCx, &parseCx->global()->lexicalScope());
Rooted<StaticScope*> staticScope(parseCx, &globalLexical->staticBlock());
task->script = frontend::CompileScript(parseCx, &task->alloc,
globalLexical, staticScope, nullptr,
task->options, srcBuf,
/* source_ = */ nullptr,
/* extraSct = */ nullptr,
/* sourceObjectOut = */ task->sourceObject.address());
task->parse();
}
// The callback is invoked while we are still off the main thread.

Просмотреть файл

@ -37,6 +37,12 @@ namespace wasm {
typedef Vector<IonCompileTask*, 0, SystemAllocPolicy> IonCompileTaskVector;
} // namespace wasm
enum class ParseTaskKind
{
Script,
Module
};
// Per-process state for off thread work items.
class GlobalHelperThreadState
{
@ -219,6 +225,11 @@ class GlobalHelperThreadState
return bool(numWasmFailedJobs);
}
JSScript* finishParseTask(JSContext* maybecx, JSRuntime* rt, ParseTaskKind kind, void* token);
void mergeParseTaskCompartment(JSRuntime* rt, ParseTask* parseTask,
Handle<GlobalObject*> global,
JSCompartment* dest);
private:
/*
* Number of wasm jobs that encountered failure for the active module.
@ -227,10 +238,8 @@ class GlobalHelperThreadState
uint32_t numWasmFailedJobs;
public:
JSScript* finishParseTask(JSContext* maybecx, JSRuntime* rt, void* token);
void mergeParseTaskCompartment(JSRuntime* rt, ParseTask* parseTask,
Handle<GlobalObject*> global,
JSCompartment* dest);
JSScript* finishScriptParseTask(JSContext* maybecx, JSRuntime* rt, void* token);
JSObject* finishModuleParseTask(JSContext* maybecx, JSRuntime* rt, void* token);
bool compressionInProgress(SourceCompressionTask* task);
SourceCompressionTask* compressionTaskForSource(ScriptSource* ss);
@ -410,6 +419,11 @@ StartOffThreadParseScript(JSContext* cx, const ReadOnlyCompileOptions& options,
const char16_t* chars, size_t length,
JS::OffThreadCompileCallback callback, void* callbackData);
bool
StartOffThreadParseModule(JSContext* cx, const ReadOnlyCompileOptions& options,
const char16_t* chars, size_t length,
JS::OffThreadCompileCallback callback, void* callbackData);
/*
* Called at the end of GC to enqueue any Parse tasks that were waiting on an
* atoms-zone GC to finish.
@ -463,6 +477,7 @@ class MOZ_RAII AutoUnlockHelperThreadState
struct ParseTask
{
ParseTaskKind kind;
ExclusiveContext* cx;
OwningCompileOptions options;
const char16_t* chars;
@ -490,19 +505,36 @@ struct ParseTask
bool overRecursed;
bool outOfMemory;
ParseTask(ExclusiveContext* cx, JSObject* exclusiveContextGlobal,
ParseTask(ParseTaskKind kind, ExclusiveContext* cx, JSObject* exclusiveContextGlobal,
JSContext* initCx, const char16_t* chars, size_t length,
JS::OffThreadCompileCallback callback, void* callbackData);
bool init(JSContext* cx, const ReadOnlyCompileOptions& options);
void activate(JSRuntime* rt);
virtual void parse() = 0;
bool finish(JSContext* cx);
bool runtimeMatches(JSRuntime* rt) {
return exclusiveContextGlobal->runtimeFromAnyThread() == rt;
}
~ParseTask();
virtual ~ParseTask();
};
struct ScriptParseTask : public ParseTask
{
ScriptParseTask(ExclusiveContext* cx, JSObject* exclusiveContextGlobal,
JSContext* initCx, const char16_t* chars, size_t length,
JS::OffThreadCompileCallback callback, void* callbackData);
void parse() override;
};
struct ModuleParseTask : public ParseTask
{
ModuleParseTask(ExclusiveContext* cx, JSObject* exclusiveContextGlobal,
JSContext* initCx, const char16_t* chars, size_t length,
JS::OffThreadCompileCallback callback, void* callbackData);
void parse() override;
};
// Return whether, if a new parse task was started, it would need to wait for

Просмотреть файл

@ -18,6 +18,7 @@
#include "js/GCAPI.h"
#include "vm/Debugger.h"
#include "vm/Opcodes.h"
#include "vm/ScopeObject.h"
#include "jit/JitFrameIterator-inl.h"
#include "vm/Interpreter-inl.h"