diff --git a/js/src/vm/ObjectGroup.h b/js/src/vm/ObjectGroup.h index 26b54dfb0e46..72debd99e96f 100644 --- a/js/src/vm/ObjectGroup.h +++ b/js/src/vm/ObjectGroup.h @@ -252,7 +252,7 @@ class ObjectGroup : public gc::TenuredCell } TypeNewScript *anyNewScript(); - void detachNewScript(bool writeBarrier); + void detachNewScript(bool writeBarrier, ObjectGroup *replacement); ObjectGroupFlags flagsDontCheckGeneration() { return flags_; @@ -480,7 +480,7 @@ class ObjectGroup : public gc::TenuredCell void setFlags(ExclusiveContext *cx, ObjectGroupFlags flags); void markUnknown(ExclusiveContext *cx); void maybeClearNewScriptOnOOM(); - void clearNewScript(ExclusiveContext *cx); + void clearNewScript(ExclusiveContext *cx, ObjectGroup *replacement = nullptr); bool isPropertyNonData(jsid id); bool isPropertyNonWritable(jsid id); diff --git a/js/src/vm/TypeInference.cpp b/js/src/vm/TypeInference.cpp index e55a39a50b5a..afcf11d6543f 100644 --- a/js/src/vm/TypeInference.cpp +++ b/js/src/vm/TypeInference.cpp @@ -2765,7 +2765,7 @@ ObjectGroup::anyNewScript() } void -ObjectGroup::detachNewScript(bool writeBarrier) +ObjectGroup::detachNewScript(bool writeBarrier, ObjectGroup *replacement) { // Clear the TypeNewScript from this ObjectGroup and, if it has been // analyzed, remove it from the newObjectGroups table so that it will not be @@ -2775,8 +2775,16 @@ ObjectGroup::detachNewScript(bool writeBarrier) MOZ_ASSERT(newScript); if (newScript->analyzed()) { - newScript->function()->compartment()->objectGroups.removeDefaultNewGroup(nullptr, proto(), - newScript->function()); + ObjectGroupCompartment &objectGroups = newScript->function()->compartment()->objectGroups; + if (replacement) { + MOZ_ASSERT(replacement->newScript()->function() == newScript->function()); + objectGroups.replaceDefaultNewGroup(nullptr, proto(), newScript->function(), + replacement); + } else { + objectGroups.removeDefaultNewGroup(nullptr, proto(), newScript->function()); + } + } else { + MOZ_ASSERT(!replacement); } if (this->newScript()) @@ -2800,13 +2808,13 @@ ObjectGroup::maybeClearNewScriptOnOOM() addFlags(OBJECT_FLAG_NEW_SCRIPT_CLEARED); // This method is called during GC sweeping, so don't trigger pre barriers. - detachNewScript(/* writeBarrier = */ false); + detachNewScript(/* writeBarrier = */ false, nullptr); js_delete(newScript); } void -ObjectGroup::clearNewScript(ExclusiveContext *cx) +ObjectGroup::clearNewScript(ExclusiveContext *cx, ObjectGroup *replacement /* = nullptr*/) { TypeNewScript *newScript = anyNewScript(); if (!newScript) @@ -2814,15 +2822,17 @@ ObjectGroup::clearNewScript(ExclusiveContext *cx) AutoEnterAnalysis enter(cx); - // Invalidate any Ion code constructing objects of this type. - setFlags(cx, OBJECT_FLAG_NEW_SCRIPT_CLEARED); + if (!replacement) { + // Invalidate any Ion code constructing objects of this type. + setFlags(cx, OBJECT_FLAG_NEW_SCRIPT_CLEARED); - // Mark the constructing function as having its 'new' script cleared, so we - // will not try to construct another one later. - if (!newScript->function()->setNewScriptCleared(cx)) - cx->recoverFromOutOfMemory(); + // Mark the constructing function as having its 'new' script cleared, so we + // will not try to construct another one later. + if (!newScript->function()->setNewScriptCleared(cx)) + cx->recoverFromOutOfMemory(); + } - detachNewScript(/* writeBarrier = */ true); + detachNewScript(/* writeBarrier = */ true, replacement); if (cx->isJSContext()) { bool found = newScript->rollbackPartiallyInitializedObjects(cx->asJSContext(), this); @@ -3268,6 +3278,33 @@ TypeNewScript::make(JSContext *cx, ObjectGroup *group, JSFunction *fun) gc::TraceTypeNewScript(group); } +// Make a TypeNewScript with the same initializer list as |newScript| but with +// a new template object. +/* static */ TypeNewScript * +TypeNewScript::makeNativeVersion(JSContext *cx, TypeNewScript *newScript, + PlainObject *templateObject) +{ + MOZ_ASSERT(cx->zone()->types.activeAnalysis); + + ScopedJSDeletePtr nativeNewScript(cx->new_()); + if (!nativeNewScript) + return nullptr; + + nativeNewScript->function_ = newScript->function(); + nativeNewScript->templateObject_ = templateObject; + + Initializer *cursor = newScript->initializerList; + while (cursor->kind != Initializer::DONE) { cursor++; } + size_t initializerLength = cursor - newScript->initializerList + 1; + + nativeNewScript->initializerList = cx->zone()->pod_calloc(initializerLength); + if (!nativeNewScript->initializerList) + return nullptr; + PodCopy(nativeNewScript->initializerList, newScript->initializerList, initializerLength); + + return nativeNewScript.forget(); +} + size_t TypeNewScript::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { diff --git a/js/src/vm/TypeInference.h b/js/src/vm/TypeInference.h index 4724704fbb26..8097294a83e8 100644 --- a/js/src/vm/TypeInference.h +++ b/js/src/vm/TypeInference.h @@ -827,8 +827,6 @@ class TypeNewScript private: // Scripted function which this information was computed for. - // If instances of the associated group are created without calling - // 'new' on this function, the new script information is cleared. HeapPtrFunction function_; // Any preliminary objects with the type. The analyses are not performed @@ -902,6 +900,8 @@ class TypeNewScript bool rollbackPartiallyInitializedObjects(JSContext *cx, ObjectGroup *group); static void make(JSContext *cx, ObjectGroup *group, JSFunction *fun); + static TypeNewScript *makeNativeVersion(JSContext *cx, TypeNewScript *newScript, + PlainObject *templateObject); size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; }; diff --git a/js/src/vm/UnboxedObject.cpp b/js/src/vm/UnboxedObject.cpp index c6c634e9aad9..27e2dc2ebca2 100644 --- a/js/src/vm/UnboxedObject.cpp +++ b/js/src/vm/UnboxedObject.cpp @@ -35,6 +35,9 @@ UnboxedLayout::trace(JSTracer *trc) if (nativeShape_) MarkShape(trc, &nativeShape_, "unboxed_layout_nativeShape"); + + if (replacementNewGroup_) + MarkObjectGroup(trc, &replacementNewGroup_, "unboxed_layout_replacementNewGroup"); } size_t @@ -172,18 +175,49 @@ UnboxedPlainObject::trace(JSTracer *trc, JSObject *obj) /* static */ bool UnboxedLayout::makeNativeGroup(JSContext *cx, ObjectGroup *group) { + AutoEnterAnalysis enter(cx); + UnboxedLayout &layout = group->unboxedLayout(); + Rooted proto(cx, group->proto()); MOZ_ASSERT(!layout.nativeGroup()); - // Immediately clear any new script on the group, as - // rollbackPartiallyInitializedObjects() will be confused by the type - // changes we make later on. - group->clearNewScript(cx); + // Immediately clear any new script on the group. This is done by replacing + // the existing new script with one for a replacement default new group. + // This is done so that the size of the replacment group's objects is the + // same as that for the unboxed group, so that we do not see polymorphic + // slot accesses later on for sites that see converted objects from this + // group and objects that were allocated using the replacement new group. + RootedObjectGroup replacementNewGroup(cx); + if (layout.newScript()) { + replacementNewGroup = ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto); + if (!replacementNewGroup) + return false; - AutoEnterAnalysis enter(cx); + PlainObject *templateObject = NewObjectWithGroup(cx, replacementNewGroup, + cx->global(), layout.getAllocKind(), + MaybeSingletonObject); + if (!templateObject) + return false; - Rooted proto(cx, group->proto()); + for (size_t i = 0; i < layout.properties().length(); i++) { + const UnboxedLayout::Property &property = layout.properties()[i]; + if (!templateObject->addDataProperty(cx, NameToId(property.name), i, JSPROP_ENUMERATE)) + return false; + MOZ_ASSERT(templateObject->slotSpan() == i + 1); + MOZ_ASSERT(!templateObject->inDictionaryMode()); + } + + TypeNewScript *replacementNewScript = + TypeNewScript::makeNativeVersion(cx, layout.newScript(), templateObject); + if (!replacementNewScript) + return false; + + replacementNewGroup->setNewScript(replacementNewScript); + gc::TraceTypeNewScript(replacementNewGroup); + + group->clearNewScript(cx, replacementNewGroup); + } size_t nfixed = gc::GetGCKindSlots(layout.getAllocKind()); RootedShape shape(cx, EmptyShape::getInitialShape(cx, &PlainObject::class_, proto, @@ -224,6 +258,7 @@ UnboxedLayout::makeNativeGroup(JSContext *cx, ObjectGroup *group) layout.nativeGroup_ = nativeGroup; layout.nativeShape_ = shape; + layout.replacementNewGroup_ = replacementNewGroup; nativeGroup->setOriginalUnboxedGroup(group); diff --git a/js/src/vm/UnboxedObject.h b/js/src/vm/UnboxedObject.h index 7f6549ac6bea..291a3abee398 100644 --- a/js/src/vm/UnboxedObject.h +++ b/js/src/vm/UnboxedObject.h @@ -71,10 +71,18 @@ class UnboxedLayout : public mozilla::LinkedListElement HeapPtrObjectGroup nativeGroup_; HeapPtrShape nativeShape_; + // If nativeGroup is set and this object originally had a TypeNewScript, + // this points to the default 'new' group which replaced this one (and + // which might itself have been cleared since). This link is only needed to + // keep the replacement group from being GC'ed. If it were GC'ed and a new + // one regenerated later, that new group might have a different allocation + // kind from this group. + HeapPtrObjectGroup replacementNewGroup_; + public: UnboxedLayout(const PropertyVector &properties, size_t size) : size_(size), newScript_(nullptr), traceList_(nullptr), - nativeGroup_(nullptr), nativeShape_(nullptr) + nativeGroup_(nullptr), nativeShape_(nullptr), replacementNewGroup_(nullptr) { properties_.appendAll(properties); }