Bug 1432794 - Skip prototype and constructor intialization for off-thread parsing r=jandem
authorJon Coppeard <jcoppeard@mozilla.com>
Tue, 30 Jan 2018 17:57:40 +0000
changeset 454007 1b4d5be7203199c65559ef7e35893587afa9da67
parent 454006 ecf18629444aa6514b36dbd137c83ba10f60b66f
child 454008 f4f1f4df5e760d0f6fab287235826ddd6c711e52
push id8799
push usermtabara@mozilla.com
push dateThu, 01 Mar 2018 16:46:23 +0000
treeherdermozilla-beta@15334014dc67 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1432794
milestone60.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 1432794 - Skip prototype and constructor intialization for off-thread parsing r=jandem
js/public/Value.h
js/src/builtin/ModuleObject.cpp
js/src/frontend/Parser.cpp
js/src/jit-test/tests/parser/bug-1431353-2.js
js/src/jsfun.cpp
js/src/jsgc.cpp
js/src/jsscript.cpp
js/src/vm/GlobalObject.cpp
js/src/vm/GlobalObject.h
js/src/vm/HelperThreads.cpp
js/src/vm/HelperThreads.h
--- a/js/public/Value.h
+++ b/js/public/Value.h
@@ -211,16 +211,19 @@ typedef enum JSWhyMagic
     JS_ION_BAILOUT,
 
     /** optimized out slot */
     JS_OPTIMIZED_OUT,
 
     /** uninitialized lexical bindings that produce ReferenceError on touch. */
     JS_UNINITIALIZED_LEXICAL,
 
+    /** standard constructors are not created for off-thread parsing. */
+    JS_OFF_THREAD_CONSTRUCTOR,
+
     /** for local use */
     JS_GENERIC_MAGIC,
 
     JS_WHY_MAGIC_COUNT
 } JSWhyMagic;
 
 namespace js {
 static inline JS::Value PoisonedObjectValue(uintptr_t poison);
--- a/js/src/builtin/ModuleObject.cpp
+++ b/js/src/builtin/ModuleObject.cpp
@@ -140,17 +140,20 @@ ImportEntryObject::create(JSContext* cx,
                           HandleAtom moduleRequest,
                           HandleAtom importName,
                           HandleAtom localName,
                           uint32_t lineNumber,
                           uint32_t columnNumber)
 {
     MOZ_ASSERT(lineNumber > 0);
 
-    RootedObject proto(cx, cx->global()->getImportEntryPrototype());
+    RootedObject proto(cx, GlobalObject::getOrCreateImportEntryPrototype(cx, cx->global()));
+    if (!proto)
+        return nullptr;
+
     RootedObject obj(cx, NewObjectWithGivenProto(cx, &class_, proto));
     if (!obj)
         return nullptr;
 
     RootedImportEntryObject self(cx, &obj->as<ImportEntryObject>());
     self->initReservedSlot(ModuleRequestSlot, StringValue(moduleRequest));
     self->initReservedSlot(ImportNameSlot, StringValue(importName));
     self->initReservedSlot(LocalNameSlot, StringValue(localName));
@@ -226,17 +229,20 @@ ExportEntryObject::create(JSContext* cx,
                           HandleAtom maybeImportName,
                           HandleAtom maybeLocalName,
                           uint32_t lineNumber,
                           uint32_t columnNumber)
 {
     // Line and column numbers are optional for export entries since direct
     // entries are checked at parse time.
 
-    RootedObject proto(cx, cx->global()->getExportEntryPrototype());
+    RootedObject proto(cx, GlobalObject::getOrCreateExportEntryPrototype(cx, cx->global()));
+    if (!proto)
+        return nullptr;
+
     RootedObject obj(cx, NewObjectWithGivenProto(cx, &class_, proto));
     if (!obj)
         return nullptr;
 
     RootedExportEntryObject self(cx, &obj->as<ExportEntryObject>());
     self->initReservedSlot(ExportNameSlot, StringOrNullValue(maybeExportName));
     self->initReservedSlot(ModuleRequestSlot, StringOrNullValue(maybeModuleRequest));
     self->initReservedSlot(ImportNameSlot, StringOrNullValue(maybeImportName));
@@ -294,17 +300,20 @@ GlobalObject::initRequestedModuleProto(J
 /* static */ RequestedModuleObject*
 RequestedModuleObject::create(JSContext* cx,
                               HandleAtom moduleSpecifier,
                               uint32_t lineNumber,
                               uint32_t columnNumber)
 {
     MOZ_ASSERT(lineNumber > 0);
 
-    RootedObject proto(cx, cx->global()->getRequestedModulePrototype());
+    RootedObject proto(cx, GlobalObject::getOrCreateRequestedModulePrototype(cx, cx->global()));
+    if (!proto)
+        return nullptr;
+
     RootedObject obj(cx, NewObjectWithGivenProto(cx, &class_, proto));
     if (!obj)
         return nullptr;
 
     RootedRequestedModuleObject self(cx, &obj->as<RequestedModuleObject>());
     self->initReservedSlot(ModuleSpecifierSlot, StringValue(moduleSpecifier));
     self->initReservedSlot(LineNumberSlot, Int32Value(lineNumber));
     self->initReservedSlot(ColumnNumberSlot, Int32Value(columnNumber));
@@ -755,17 +764,20 @@ DEFINE_ARRAY_SLOT_ACCESSOR(ModuleObject,
 ModuleObject::isInstance(HandleValue value)
 {
     return value.isObject() && value.toObject().is<ModuleObject>();
 }
 
 /* static */ ModuleObject*
 ModuleObject::create(JSContext* cx)
 {
-    RootedObject proto(cx, cx->global()->getModulePrototype());
+    RootedObject proto(cx, GlobalObject::getOrCreateModulePrototype(cx, cx->global()));
+    if (!proto)
+        return nullptr;
+
     RootedObject obj(cx, NewObjectWithGivenProto(cx, &class_, proto));
     if (!obj)
         return nullptr;
 
     RootedModuleObject self(cx, &obj->as<ModuleObject>());
 
     Zone* zone = cx->zone();
     IndirectBindingMap* bindings = zone->new_<IndirectBindingMap>();
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -3386,21 +3386,17 @@ GeneralParser<ParseHandler, CharT>::func
 
         return funcNode;
     }
 
     RootedObject proto(context);
     if (generatorKind == GeneratorKind::Generator ||
         asyncKind == FunctionAsyncKind::AsyncFunction)
     {
-        // If we are off thread, the generator meta-objects have
-        // already been created by js::StartOffThreadParseTask, so cx will not
-        // be necessary.
-        JSContext* cx = context->helperThread() ? nullptr : context;
-        proto = GlobalObject::getOrCreateGeneratorFunctionPrototype(cx, context->global());
+        proto = GlobalObject::getOrCreateGeneratorFunctionPrototype(context, context->global());
         if (!proto)
             return null();
     }
     RootedFunction fun(context, newFunction(funName, kind, generatorKind, asyncKind, proto));
     if (!fun)
         return null();
 
     // Speculatively parse using the directives of the parent parsing context.
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/parser/bug-1431353-2.js
@@ -0,0 +1,56 @@
+// Test off-thread parsing correctly fixes up prototypes of special objects when
+// merging back to the target compartment.
+
+if (helperThreadCount() === 0)
+    quit();
+
+function execOffThread(source)
+{
+    offThreadCompileScript(source);
+    return runOffThreadScript();
+}
+
+function parseModuleOffThread(source)
+{
+    offThreadCompileModule(source);
+    return finishOffThreadModule();
+}
+
+let a = { x: 1 };
+let b = execOffThread("undefined, { x: 1 }")
+let c = execOffThread("undefined, { x: 1 }")
+
+assertEq(Object.getPrototypeOf(a), Object.prototype);
+assertEq(Object.getPrototypeOf(b), Object.prototype);
+assertEq(Object.getPrototypeOf(c), Object.prototype);
+
+a = () => 1;
+b = execOffThread("() => 1")
+c = execOffThread("() => 1")
+
+assertEq(Object.getPrototypeOf(a), Function.prototype);
+assertEq(Object.getPrototypeOf(b), Function.prototype);
+assertEq(Object.getPrototypeOf(c), Function.prototype);
+
+a = [1, 2, 3];
+b = execOffThread("[1, 2, 3]")
+c = execOffThread("[1, 2, 3]")
+
+assertEq(Object.getPrototypeOf(a), Array.prototype);
+assertEq(Object.getPrototypeOf(b), Array.prototype);
+assertEq(Object.getPrototypeOf(c), Array.prototype);
+
+a = /a/;
+b = execOffThread("/a/")
+c = execOffThread("/a/")
+
+assertEq(Object.getPrototypeOf(a), RegExp.prototype);
+assertEq(Object.getPrototypeOf(b), RegExp.prototype);
+assertEq(Object.getPrototypeOf(c), RegExp.prototype);
+
+a = parseModule("");
+b = parseModuleOffThread("");
+c = parseModuleOffThread("");
+
+assertEq(Object.getPrototypeOf(b), Object.getPrototypeOf(a));
+assertEq(Object.getPrototypeOf(c), Object.getPrototypeOf(a));
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -634,21 +634,17 @@ js::XDRInterpretedFunction(XDRState<mode
     if ((firstword & HasAtom) && !XDRAtom(xdr, &atom))
         return false;
     if (!xdr->codeUint32(&flagsword))
         return false;
 
     if (mode == XDR_DECODE) {
         RootedObject proto(cx);
         if (firstword & HasGeneratorProto) {
-            // If we are off thread, the generator meta-objects have
-            // already been created by js::StartOffThreadParseTask, so
-            // JSContext* will not be necessary.
-            JSContext* context = cx->helperThread() ? nullptr : cx;
-            proto = GlobalObject::getOrCreateGeneratorFunctionPrototype(context, cx->global());
+            proto = GlobalObject::getOrCreateGeneratorFunctionPrototype(cx, cx->global());
             if (!proto)
                 return false;
         }
 
         gc::AllocKind allocKind = gc::AllocKind::FUNCTION;
         if (uint16_t(flagsword) & JSFunction::EXTENDED)
             allocKind = gc::AllocKind::FUNCTION_EXTENDED;
         fun = NewFunctionWithProto(cx, nullptr, 0, JSFunction::INTERPRETED,
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -7923,17 +7923,34 @@ GCRuntime::mergeCompartments(JSCompartme
     // type information generations are in sync.
 
     for (auto script = source->zone()->cellIter<JSScript>(); !script.done(); script.next()) {
         MOZ_ASSERT(script->compartment() == source);
         script->compartment_ = target;
         script->setTypesGeneration(target->zone()->types.generation);
     }
 
+    GlobalObject* global = target->maybeGlobal();
+    MOZ_ASSERT(global);
+
     for (auto group = source->zone()->cellIter<ObjectGroup>(); !group.done(); group.next()) {
+        // Replace placeholder object prototypes with the correct prototype in
+        // the target compartment.
+        TaggedProto proto(group->proto());
+        if (proto.isObject()) {
+            JSObject* obj = proto.toObject();
+            if (GlobalObject::isOffThreadPrototypePlaceholder(obj)) {
+                JSObject* targetProto = global->getPrototypeForOffThreadPlaceholder(obj);
+                MOZ_ASSERT(targetProto->isDelegate());
+                group->setProtoUnchecked(TaggedProto(targetProto));
+                if (targetProto->isNewGroupUnknown())
+                    group->markUnknown(cx);
+            }
+        }
+
         group->setGeneration(target->zone()->types.generation);
         group->compartment_ = target;
 
         // Remove any unboxed layouts from the list in the off thread
         // compartment. These do not need to be reinserted in the target
         // compartment's list, as the list is not required to be complete.
         if (UnboxedLayout* layout = group->maybeUnboxedLayoutDontCheckGeneration())
             layout->detachFromCompartment();
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -537,26 +537,25 @@ js::XDRScript(XDRState<mode>* xdr, Handl
              * ScriptSourceObject. Most CompileOptions fields aren't used by
              * ScriptSourceObject, and those that are (element; elementAttributeName)
              * aren't preserved by XDR. So this can be simple.
              */
             if (!ss->initFromOptions(cx, *options))
                 return false;
 
             sourceObject = ScriptSourceObject::create(cx, ss);
+            if (!sourceObject)
+                return false;
+
             if (xdr->hasScriptSourceObjectOut()) {
                 // When the ScriptSourceObjectOut is provided by ParseTask, it
                 // is stored in a location which is traced by the GC.
                 *xdr->scriptSourceObjectOut() = sourceObject;
-            } else {
-                if (!sourceObject ||
-                    !ScriptSourceObject::initFromOptions(cx, sourceObject, *options))
-                {
-                    return false;
-                }
+            } else if (!ScriptSourceObject::initFromOptions(cx, sourceObject, *options)) {
+                return false;
             }
         }
 
         script = JSScript::Create(cx, *options, sourceObject, 0, 0, 0, 0);
         if (!script)
             return false;
 
         // Set the script in its function now so that inner scripts to be
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -116,16 +116,20 @@ GlobalObject::skipDeselectedConstructor(
         return false;
     }
 }
 
 /* static*/ bool
 GlobalObject::resolveConstructor(JSContext* cx, Handle<GlobalObject*> global, JSProtoKey key)
 {
     MOZ_ASSERT(!global->isStandardClassResolved(key));
+
+    if (global->zone()->group()->createdForHelperThread())
+        return resolveOffThreadConstructor(cx, global, key);
+
     MOZ_ASSERT(!cx->helperThread());
 
     // Prohibit collection of allocation metadata. Metadata builders shouldn't
     // need to observe lazily-constructed prototype objects coming into
     // existence. And assertions start to fail when the builder itself attempts
     // an allocation that re-entrantly tries to create the same prototype.
     AutoSuppressAllocationMetadataBuilder suppressMetadata(cx);
 
@@ -269,16 +273,115 @@ GlobalObject::resolveConstructor(JSConte
         global->setConstructor(key, ObjectValue(*ctor));
         if (proto)
             global->setPrototype(key, ObjectValue(*proto));
     }
 
     return true;
 }
 
+/* static */ JSObject*
+GlobalObject::createObject(JSContext* cx, Handle<GlobalObject*> global, unsigned slot, ObjectInitOp init)
+{
+    if (global->zone()->group()->createdForHelperThread())
+        return createOffThreadObject(cx, global, slot);
+
+    MOZ_ASSERT(!cx->helperThread());
+    if (!init(cx, global))
+        return nullptr;
+
+    return &global->getSlot(slot).toObject();
+}
+
+const Class GlobalObject::OffThreadPlaceholderObject::class_ = {
+    "off-thread-prototype-placeholder",
+    JSCLASS_IS_ANONYMOUS | JSCLASS_HAS_RESERVED_SLOTS(1)
+};
+
+/* static */ GlobalObject::OffThreadPlaceholderObject*
+GlobalObject::OffThreadPlaceholderObject::New(JSContext* cx, unsigned slot)
+{
+    Rooted<OffThreadPlaceholderObject*> placeholder(cx);
+    placeholder =
+        NewObjectWithGivenTaggedProto<OffThreadPlaceholderObject>(cx, AsTaggedProto(nullptr));
+    if (!placeholder)
+        return nullptr;
+
+    placeholder->setReservedSlot(SlotIndexSlot, Int32Value(slot));
+    return placeholder;
+}
+
+inline int32_t
+GlobalObject::OffThreadPlaceholderObject::getSlotIndex() const
+{
+    return getReservedSlot(SlotIndexSlot).toInt32();
+}
+
+/* static */ bool
+GlobalObject::resolveOffThreadConstructor(JSContext* cx,
+                                          Handle<GlobalObject*> global,
+                                          JSProtoKey key)
+{
+    // Don't resolve constructors for off-thread parse globals. Instead create a
+    // placeholder object for the prototype which we can use to find the real
+    // prototype when the off-thread compartment is merged back into the target
+    // compartment.
+
+    MOZ_ASSERT(global->zone()->group()->createdForHelperThread());
+    MOZ_ASSERT(key == JSProto_Object ||
+               key == JSProto_Function ||
+               key == JSProto_Array ||
+               key == JSProto_RegExp);
+
+    Rooted<OffThreadPlaceholderObject*> placeholder(cx);
+    placeholder = OffThreadPlaceholderObject::New(cx, prototypeSlot(key));
+    if (!placeholder)
+        return false;
+
+    if (key == JSProto_Object &&
+        !JSObject::setFlags(cx, placeholder, BaseShape::IMMUTABLE_PROTOTYPE))
+    {
+        return false;
+    }
+
+    global->setPrototype(key, ObjectValue(*placeholder));
+    global->setConstructor(key, MagicValue(JS_OFF_THREAD_CONSTRUCTOR));
+    return true;
+}
+
+/* static */ JSObject*
+GlobalObject::createOffThreadObject(JSContext* cx, Handle<GlobalObject*> global, unsigned slot)
+{
+    // Don't create prototype objects for off-thread parse globals. Instead
+    // create a placeholder object which we can use to find the real prototype
+    // when the off-thread compartment is merged back into the target
+    // compartment.
+
+    MOZ_ASSERT(global->zone()->group()->createdForHelperThread());
+    MOZ_ASSERT(slot == GENERATOR_FUNCTION_PROTO ||
+               slot == MODULE_PROTO ||
+               slot == IMPORT_ENTRY_PROTO ||
+               slot == EXPORT_ENTRY_PROTO ||
+               slot == REQUESTED_MODULE_PROTO);
+
+    auto placeholder = OffThreadPlaceholderObject::New(cx, slot);
+    if (!placeholder)
+        return nullptr;
+
+    global->setSlot(slot, ObjectValue(*placeholder));
+    return placeholder;
+}
+
+JSObject*
+GlobalObject::getPrototypeForOffThreadPlaceholder(JSObject* obj)
+{
+    auto placeholder = &obj->as<OffThreadPlaceholderObject>();
+    return &getSlot(placeholder->getSlotIndex()).toObject();
+}
+
 /* static */ bool
 GlobalObject::initBuiltinConstructor(JSContext* cx, Handle<GlobalObject*> global,
                                      JSProtoKey key, HandleObject ctor, HandleObject proto)
 {
     MOZ_ASSERT(!global->empty()); // reserved slots already allocated
     MOZ_ASSERT(key != JSProto_Null);
     MOZ_ASSERT(ctor);
     MOZ_ASSERT(proto);
@@ -864,13 +967,13 @@ GlobalObject::addIntrinsicValue(JSContex
 
     holder->setSlot(shape->slot(), value);
     return true;
 }
 
 /* static */ bool
 GlobalObject::ensureModulePrototypesCreated(JSContext *cx, Handle<GlobalObject*> global)
 {
-    return getOrCreateObject(cx, global, MODULE_PROTO, initModuleProto) &&
-           getOrCreateObject(cx, global, IMPORT_ENTRY_PROTO, initImportEntryProto) &&
-           getOrCreateObject(cx, global, EXPORT_ENTRY_PROTO, initExportEntryProto) &&
-           getOrCreateObject(cx, global, REQUESTED_MODULE_PROTO, initRequestedModuleProto);
+    return getOrCreateModulePrototype(cx, global) &&
+           getOrCreateImportEntryPrototype(cx, global) &&
+           getOrCreateExportEntryPrototype(cx, global) &&
+           getOrCreateRequestedModulePrototype(cx, global);
 }
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -115,28 +115,37 @@ class GlobalObject : public NativeObject
     /*
      * The slot count must be in the public API for JSCLASS_GLOBAL_FLAGS, and
      * we won't expose GlobalObject, so just assert that the two values are
      * synchronized.
      */
     static_assert(JSCLASS_GLOBAL_SLOT_COUNT == RESERVED_SLOTS,
                   "global object slot counts are inconsistent");
 
+    static unsigned constructorSlot(JSProtoKey key) {
+        MOZ_ASSERT(key <= JSProto_LIMIT);
+        return APPLICATION_SLOTS + key;
+    }
+
+    static unsigned prototypeSlot(JSProtoKey key) {
+        MOZ_ASSERT(key <= JSProto_LIMIT);
+        return APPLICATION_SLOTS + JSProto_LIMIT + key;
+    }
+
   public:
     LexicalEnvironmentObject& lexicalEnvironment() const;
     GlobalScope& emptyGlobalScope() const;
 
     void setOriginalEval(JSObject* evalobj) {
         MOZ_ASSERT(getSlotRef(EVAL).isUndefined());
         setSlot(EVAL, ObjectValue(*evalobj));
     }
 
     Value getConstructor(JSProtoKey key) const {
-        MOZ_ASSERT(key <= JSProto_LIMIT);
-        return getSlot(APPLICATION_SLOTS + key);
+        return getSlot(constructorSlot(key));
     }
     static bool skipDeselectedConstructor(JSContext* cx, JSProtoKey key);
     static bool initBuiltinConstructor(JSContext* cx, Handle<GlobalObject*> global,
                                        JSProtoKey key, HandleObject ctor, HandleObject proto);
 
   private:
     static bool resolveConstructor(JSContext* cx, Handle<GlobalObject*> global, JSProtoKey key);
 
@@ -166,28 +175,25 @@ class GlobalObject : public NativeObject
     JSObject* maybeGetPrototype(JSProtoKey protoKey) const {
         MOZ_ASSERT(JSProto_Null < protoKey);
         MOZ_ASSERT(protoKey < JSProto_LIMIT);
         const Value& v = getPrototype(protoKey);
         return v.isObject() ? &v.toObject() : nullptr;
     }
 
     void setConstructor(JSProtoKey key, const Value& v) {
-        MOZ_ASSERT(key <= JSProto_LIMIT);
-        setSlot(APPLICATION_SLOTS + key, v);
+        setSlot(constructorSlot(key), v);
     }
 
     Value getPrototype(JSProtoKey key) const {
-        MOZ_ASSERT(key <= JSProto_LIMIT);
-        return getSlot(APPLICATION_SLOTS + JSProto_LIMIT + key);
+        return getSlot(prototypeSlot(key));
     }
 
     void setPrototype(JSProtoKey key, const Value& value) {
-        MOZ_ASSERT(key <= JSProto_LIMIT);
-        setSlot(APPLICATION_SLOTS + JSProto_LIMIT + key, value);
+        setSlot(prototypeSlot(key), value);
     }
 
     bool classIsInitialized(JSProtoKey key) const {
         bool inited = !getConstructor(key).isUndefined();
         MOZ_ASSERT(inited == !getPrototype(key).isUndefined());
         return inited;
     }
 
@@ -204,19 +210,21 @@ class GlobalObject : public NativeObject
      * getConstructor(key) reserved slot to indicate that they've been
      * initialized.
      *
      * Note: A few builtin objects, like JSON and Math, are not constructors,
      * so getConstructor is a bit of a misnomer.
      */
     bool isStandardClassResolved(JSProtoKey key) const {
         // If the constructor is undefined, then it hasn't been initialized.
-        MOZ_ASSERT(getConstructor(key).isUndefined() ||
-                   getConstructor(key).isObject());
-        return !getConstructor(key).isUndefined();
+        Value value = getConstructor(key);
+        MOZ_ASSERT(value.isUndefined() ||
+                   value.isObject() ||
+                   value.isMagic(JS_OFF_THREAD_CONSTRUCTOR));
+        return !value.isUndefined();
     }
 
     /*
      * Using a Handle<GlobalObject*> as a Handle<Object*> is always safe as
      * GlobalObject derives JSObject. However, with C++'s semantics, Handle<T>
      * is not related to Handle<S>, independent of how S and T are related.
      * Further, Handle stores an indirect pointer and, again because of C++'s
      * semantics, T** is not related to S**, independent of how S and T are
@@ -506,58 +514,34 @@ class GlobalObject : public NativeObject
 
     static JSObject*
     getOrCreateRelativeTimeFormatPrototype(JSContext* cx, Handle<GlobalObject*> global) {
         return getOrCreateObject(cx, global, RELATIVE_TIME_FORMAT_PROTO, initIntlObject);
     }
 
     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* maybeGetRequestedModulePrototype() {
-        Value value = getSlot(REQUESTED_MODULE_PROTO);
-        return value.isUndefined() ? nullptr : &value.toObject();
+    static JSObject*
+    getOrCreateModulePrototype(JSContext* cx, Handle<GlobalObject*> global) {
+        return getOrCreateObject(cx, global, MODULE_PROTO, initModuleProto);
     }
 
-    JSObject* getModulePrototype() {
-        JSObject* proto = maybeGetModulePrototype();
-        MOZ_ASSERT(proto);
-        return proto;
+    static JSObject*
+    getOrCreateImportEntryPrototype(JSContext* cx, Handle<GlobalObject*> global) {
+        return getOrCreateObject(cx, global, IMPORT_ENTRY_PROTO, initImportEntryProto);
     }
 
-    JSObject* getImportEntryPrototype() {
-        JSObject* proto = maybeGetImportEntryPrototype();
-        MOZ_ASSERT(proto);
-        return proto;
+    static JSObject*
+    getOrCreateExportEntryPrototype(JSContext* cx, Handle<GlobalObject*> global) {
+        return getOrCreateObject(cx, global, EXPORT_ENTRY_PROTO, initExportEntryProto);
     }
 
-    JSObject* getExportEntryPrototype() {
-        JSObject* proto = maybeGetExportEntryPrototype();
-        MOZ_ASSERT(proto);
-        return proto;
-    }
-
-    JSObject* getRequestedModulePrototype() {
-        JSObject* proto = maybeGetRequestedModulePrototype();
-        MOZ_ASSERT(proto);
-        return proto;
+    static JSObject*
+    getOrCreateRequestedModulePrototype(JSContext* cx, Handle<GlobalObject*> global) {
+        return getOrCreateObject(cx, global, REQUESTED_MODULE_PROTO, initRequestedModuleProto);
     }
 
     static JSFunction*
     getOrCreateTypedArrayConstructor(JSContext* cx, Handle<GlobalObject*> global) {
         if (!ensureConstructor(cx, global, JSProto_TypedArray))
             return nullptr;
         return &global->getConstructor(JSProto_TypedArray).toObject().as<JSFunction>();
     }
@@ -574,21 +558,23 @@ class GlobalObject : public NativeObject
 
     static JSObject*
     getOrCreateObject(JSContext* cx, Handle<GlobalObject*> global, unsigned slot,
                       ObjectInitOp init)
     {
         Value v = global->getSlotRef(slot);
         if (v.isObject())
             return &v.toObject();
-        if (!init(cx, global))
-            return nullptr;
-        return &global->getSlot(slot).toObject();
+
+        return createObject(cx, global, slot, init);
     }
 
+    static JSObject*
+    createObject(JSContext* cx, Handle<GlobalObject*> global, unsigned slot, ObjectInitOp init);
+
   public:
     static NativeObject*
     getOrCreateIteratorPrototype(JSContext* cx, Handle<GlobalObject*> global) {
         return MaybeNativeObject(getOrCreateObject(cx, global, ITERATOR_PROTO, initIteratorProto));
     }
 
     static NativeObject*
     getOrCreateArrayIteratorPrototype(JSContext* cx, Handle<GlobalObject*> global) {
@@ -604,20 +590,19 @@ class GlobalObject : public NativeObject
 
     static NativeObject*
     getOrCreateGeneratorObjectPrototype(JSContext* cx, Handle<GlobalObject*> global)
     {
         return MaybeNativeObject(getOrCreateObject(cx, global, GENERATOR_OBJECT_PROTO,
                                                    initGenerators));
     }
 
-    static NativeObject*
+    static JSObject*
     getOrCreateGeneratorFunctionPrototype(JSContext* cx, Handle<GlobalObject*> global) {
-        return MaybeNativeObject(getOrCreateObject(cx, global, GENERATOR_FUNCTION_PROTO,
-                                                   initGenerators));
+        return getOrCreateObject(cx, global, GENERATOR_FUNCTION_PROTO, initGenerators);
     }
 
     static JSObject*
     getOrCreateGeneratorFunction(JSContext* cx, Handle<GlobalObject*> global) {
         return getOrCreateObject(cx, global, GENERATOR_FUNCTION, initGenerators);
     }
 
     static NativeObject*
@@ -858,20 +843,36 @@ class GlobalObject : public NativeObject
     JSFunction* moduleResolveHook() {
         Value value = getSlotRef(MODULE_RESOLVE_HOOK);
         if (value.isUndefined())
             return nullptr;
 
         return &value.toObject().as<JSFunction>();
     }
 
-    // Returns either this global's generator function prototype, or null if
-    // that object was never created.  Dodgy; for use only in also-dodgy
-    // GlobalHelperThreadState::mergeParseTaskCompartment().
-    JSObject* getGeneratorFunctionPrototype();
+    // A class used in place of a prototype during off-thread parsing.
+    struct OffThreadPlaceholderObject : public NativeObject
+    {
+        static const int32_t SlotIndexSlot = 0;
+        static const Class class_;
+        static OffThreadPlaceholderObject* New(JSContext* cx, unsigned slot);
+        inline int32_t getSlotIndex() const;
+    };
+
+    static bool isOffThreadPrototypePlaceholder(JSObject* obj) {
+        return obj->is<OffThreadPlaceholderObject>();
+    }
+
+    JSObject* getPrototypeForOffThreadPlaceholder(JSObject* placeholder);
+
+  private:
+     static bool resolveOffThreadConstructor(JSContext* cx, Handle<GlobalObject*> global,
+                                            JSProtoKey key);
+     static JSObject* createOffThreadObject(JSContext* cx, Handle<GlobalObject*> global,
+                                            unsigned slot);
 };
 
 /*
  * Unless otherwise specified, define ctor.prototype = proto as non-enumerable,
  * non-configurable, and non-writable; and define proto.constructor = ctor as
  * non-enumerable but configurable and writable.
  */
 extern bool
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -471,16 +471,17 @@ ParseTask::~ParseTask()
         js_delete(errors[i]);
 }
 
 void
 ParseTask::trace(JSTracer* trc)
 {
     if (parseGlobal->runtimeFromAnyThread() != trc->runtime())
         return;
+
     Zone* zone = MaybeForwarded(parseGlobal)->zoneFromAnyThread();
     if (zone->usedByHelperThread()) {
         MOZ_ASSERT(!zone->isCollecting());
         return;
     }
 
     TraceManuallyBarrieredEdge(trc, &parseGlobal, "ParseTask::parseGlobal");
     scripts.trace(trc);
@@ -706,79 +707,64 @@ EnsureParserCreatedClasses(JSContext* cx
         return false; // needed by function*() {}
 
     if (kind == ParseTaskKind::Module && !GlobalObject::ensureModulePrototypesCreated(cx, global))
         return false;
 
     return true;
 }
 
-class AutoClearUsedByHelperThread
+class MOZ_RAII AutoSetCreatedForHelperThread
 {
     ZoneGroup* group;
 
   public:
-    explicit AutoClearUsedByHelperThread(JSObject* global)
+    explicit AutoSetCreatedForHelperThread(JSObject* global)
       : group(global->zone()->group())
-    {}
+    {
+        group->setCreatedForHelperThread();
+    }
 
     void forget() {
         group = nullptr;
     }
 
-    ~AutoClearUsedByHelperThread() {
+    ~AutoSetCreatedForHelperThread() {
         if (group)
             group->clearUsedByHelperThread();
     }
 };
 
 static JSObject*
 CreateGlobalForOffThreadParse(JSContext* cx, ParseTaskKind kind,
-                              Maybe<AutoClearUsedByHelperThread>& clearUseGuard,
                               const gc::AutoSuppressGC& nogc)
 {
     JSCompartment* currentCompartment = cx->compartment();
 
     JS::CompartmentOptions compartmentOptions(currentCompartment->creationOptions(),
                                               currentCompartment->behaviors());
 
     auto& creationOptions = compartmentOptions.creationOptions();
 
     creationOptions.setInvisibleToDebugger(true)
                    .setMergeable(true)
                    .setNewZoneInNewZoneGroup();
 
     // Don't falsely inherit the host's global trace hook.
     creationOptions.setTrace(nullptr);
 
-    JSObject* global = JS_NewGlobalObject(cx, &parseTaskGlobalClass, nullptr,
-                                          JS::FireOnNewGlobalHook, compartmentOptions);
-    if (!global)
+    JSObject* obj = JS_NewGlobalObject(cx, &parseTaskGlobalClass, nullptr,
+                                       JS::DontFireOnNewGlobalHook, compartmentOptions);
+    if (!obj)
         return nullptr;
 
+    Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
+
     JS_SetCompartmentPrincipals(global->compartment(), currentCompartment->principals());
 
-    // Mark this zone group as created for a helper thread. This prevents it
-    // from being collected until clearUsedByHelperThread() is called.
-    ZoneGroup* group = global->zone()->group();
-    group->setCreatedForHelperThread();
-    clearUseGuard.emplace(global);
-
-    // Initialize all classes required for parsing while still on the active
-    // thread, for both the target and the new global so that prototype
-    // pointers can be changed infallibly after parsing finishes.
-    if (!EnsureParserCreatedClasses(cx, kind))
-        return nullptr;
-
-    {
-        AutoCompartment ac(cx, global);
-        if (!EnsureParserCreatedClasses(cx, kind))
-            return nullptr;
-    }
-
     return global;
 }
 
 static bool
 QueueOffThreadParseTask(JSContext* cx, ParseTask* task)
 {
     AutoLockHelperThreadState lock;
 
@@ -805,35 +791,38 @@ StartOffThreadParseTask(JSContext* cx, c
                         ParseTaskKind kind, TaskFunctor& taskFunctor)
 {
     // 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);
     gc::AutoAssertNoNurseryAlloc noNurseryAlloc;
     AutoSuppressAllocationMetadataBuilder suppressMetadata(cx);
 
-    Maybe<AutoClearUsedByHelperThread> clearUseGuard;
-    JSObject* global = CreateGlobalForOffThreadParse(cx, kind, clearUseGuard, nogc);
+    JSObject* global = CreateGlobalForOffThreadParse(cx, kind, nogc);
     if (!global)
         return false;
 
+    // Mark the global's zone group as created for a helper thread. This
+    // prevents it from being collected until clearUsedByHelperThread() is
+    // called after parsing is complete. If this function exits due to error
+    // this state is cleared automatically.
+    AutoSetCreatedForHelperThread createdForHelper(global);
+
     ScopedJSDeletePtr<ParseTask> task(taskFunctor(global));
-    if (!task)
+    if (!task || !task->init(cx, options))
         return false;
 
-    if (!task->init(cx, options) || !QueueOffThreadParseTask(cx, task))
+    if (!QueueOffThreadParseTask(cx, task))
         return false;
 
     task.forget();
-    clearUseGuard->forget();
-
+    createdForHelper.forget();
     return true;
 }
 
-
 bool
 js::StartOffThreadParseScript(JSContext* cx, const ReadOnlyCompileOptions& options,
                               const char16_t* chars, size_t length,
                               JS::OffThreadCompileCallback callback, void* callbackData)
 {
     auto functor = [&](JSObject* global) -> ScriptParseTask* {
         return cx->new_<ScriptParseTask>(cx, global, chars, length,
                                          callback, callbackData);
@@ -1711,84 +1700,28 @@ GlobalHelperThreadState::finishModulePar
 
 void
 GlobalHelperThreadState::cancelParseTask(JSRuntime* rt, ParseTaskKind kind, void* token)
 {
     ScopedJSDeletePtr<ParseTask> parseTask(removeFinishedParseTask(kind, token));
     LeaveParseTaskZone(rt, parseTask);
 }
 
-JSObject*
-GlobalObject::getGeneratorFunctionPrototype()
-{
-    const Value& v = getReservedSlot(GENERATOR_FUNCTION_PROTO);
-    return v.isObject() ? &v.toObject() : nullptr;
-}
-
 void
 GlobalHelperThreadState::mergeParseTaskCompartment(JSContext* cx, ParseTask* parseTask,
                                                    Handle<GlobalObject*> global,
                                                    JSCompartment* dest)
 {
     // After we call LeaveParseTaskZone() it's not safe to GC until we have
     // finished merging the contents of the parse task's compartment into the
     // destination compartment.
     JS::AutoAssertNoGC nogc(cx);
 
     LeaveParseTaskZone(cx->runtime(), parseTask);
 
-    {
-        AutoCompartment ac(cx, parseTask->parseGlobal);
-
-        // 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.
-        GlobalObject* parseGlobal = &parseTask->parseGlobal->as<GlobalObject>();
-        JSObject* parseTaskGenFunctionProto = parseGlobal->getGeneratorFunctionPrototype();
-
-        // 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
-        // create cross compartment pointers, which will be fixed by the
-        // MergeCompartments call below.
-        Zone* parseZone = parseTask->parseGlobal->zone();
-        for (auto group = parseZone->cellIter<ObjectGroup>(); !group.done(); group.next()) {
-            TaggedProto proto(group->proto());
-            if (!proto.isObject())
-                continue;
-
-            JSObject* protoObj = proto.toObject();
-
-            JSObject* newProto;
-            JSProtoKey key = JS::IdentifyStandardPrototype(protoObj);
-            if (key != JSProto_Null) {
-                MOZ_ASSERT(key == JSProto_Object || key == JSProto_Array ||
-                           key == JSProto_Function || key == JSProto_RegExp);
-                newProto = global->maybeGetPrototype(key);
-                MOZ_ASSERT(newProto);
-            } else if (protoObj == parseTaskGenFunctionProto) {
-                newProto = global->getGeneratorFunctionPrototype();
-            } else if (protoObj == moduleProto) {
-                newProto = global->getModulePrototype();
-            } else if (protoObj == importEntryProto) {
-                newProto = global->getImportEntryPrototype();
-            } else if (protoObj == exportEntryProto) {
-                newProto = global->getExportEntryPrototype();
-            } else {
-                continue;
-            }
-
-            group->setProtoUnchecked(TaggedProto(newProto));
-        }
-    }
-
     // Move the parsed script and all its contents into the desired compartment.
     gc::MergeCompartments(parseTask->parseGlobal->compartment(), dest);
 }
 
 void
 HelperThread::destroy()
 {
     if (thread.isSome()) {
--- a/js/src/vm/HelperThreads.h
+++ b/js/src/vm/HelperThreads.h
@@ -634,17 +634,17 @@ struct ParseTask
     OwningCompileOptions options;
 
     mozilla::Variant<const JS::TranscodeRange,
                      JS::TwoByteChars,
                      JS::TranscodeSources*> data;
 
     LifoAlloc alloc;
 
-    // Rooted pointer to the global object to use while parsing.
+    // The global object to use while parsing.
     JSObject* parseGlobal;
 
     // Callback invoked off thread when the parse finishes.
     JS::OffThreadCompileCallback callback;
     void* callbackData;
 
     // Holds the final scripts between the invocation of the callback and the
     // point where FinishOffThreadScript is called, which will destroy the