Bug 1462540 - Initialize NativeIterator objects (and any associated property name strings and HeapReceiverGuards) all within a single constructor call, without using PodZero. r=jandem
authorJeff Walden <jwalden@mit.edu>
Wed, 16 May 2018 23:55:40 -0700
changeset 418922 7491ab23247f
parent 418921 7d83c7de9404
child 418923 63d37d73f5ae
push id103419
push userjwalden@mit.edu
push date2018-05-18 19:33 +0000
treeherdermozilla-inbound@7658d2d1e0d7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1462540
milestone62.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 1462540 - Initialize NativeIterator objects (and any associated property name strings and HeapReceiverGuards) all within a single constructor call, without using PodZero. r=jandem
js/src/vm/Iteration.cpp
js/src/vm/Iteration.h
--- a/js/src/vm/Iteration.cpp
+++ b/js/src/vm/Iteration.cpp
@@ -9,16 +9,18 @@
 #include "vm/Iteration.h"
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/Unused.h"
 
+#include <new>
+
 #include "jstypes.h"
 #include "jsutil.h"
 
 #include "builtin/Array.h"
 #include "ds/Sort.h"
 #include "gc/FreeOp.h"
 #include "gc/Marking.h"
 #include "js/Proxy.h"
@@ -41,17 +43,16 @@
 
 using namespace js;
 using namespace js::gc;
 
 using mozilla::DebugOnly;
 using mozilla::Maybe;
 using mozilla::PodCopy;
 using mozilla::PodEqual;
-using mozilla::PodZero;
 
 typedef Rooted<PropertyIteratorObject*> RootedPropertyIteratorObject;
 
 static const gc::AllocKind ITERATOR_FINALIZE_KIND = gc::AllocKind::OBJECT2_BACKGROUND;
 
 void
 NativeIterator::trace(JSTracer* trc)
 {
@@ -529,17 +530,27 @@ Snapshot(JSContext* cx, HandleObject pob
 JS_FRIEND_API(bool)
 js::GetPropertyKeys(JSContext* cx, HandleObject obj, unsigned flags, AutoIdVector* props)
 {
     return Snapshot(cx, obj,
                     flags & (JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS | JSITER_SYMBOLSONLY),
                     props);
 }
 
-static inline PropertyIteratorObject*
+static inline void
+RegisterEnumerator(JSContext* cx, NativeIterator* ni)
+{
+    /* Register non-escaping native enumerators (for-in) with the current context. */
+    ni->link(cx->compartment()->enumerators);
+
+    MOZ_ASSERT(!(ni->flags & JSITER_ACTIVE));
+    ni->flags |= JSITER_ACTIVE;
+}
+
+static PropertyIteratorObject*
 NewPropertyIteratorObject(JSContext* cx)
 {
     RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &PropertyIteratorObject::class_,
                                                              TaggedProto(nullptr)));
     if (!group)
         return nullptr;
 
     const Class* clasp = &PropertyIteratorObject::class_;
@@ -558,181 +569,187 @@ NewPropertyIteratorObject(JSContext* cx)
     // CodeGenerator::visitIteratorStartO assumes the iterator object is not
     // inside the nursery when deciding whether a barrier is necessary.
     MOZ_ASSERT(!js::gc::IsInsideNursery(res));
 
     MOZ_ASSERT(res->numFixedSlots() == JSObject::ITER_CLASS_NFIXED_SLOTS);
     return res;
 }
 
-NativeIterator*
-NativeIterator::allocateIterator(JSContext* cx, uint32_t numGuards, uint32_t plength)
+static PropertyIteratorObject*
+CreatePropertyIterator(JSContext* cx, Handle<JSObject*> objBeingIterated,
+                       const AutoIdVector& props, uint32_t numGuards, uint32_t guardKey)
 {
+    Rooted<PropertyIteratorObject*> propIter(cx, NewPropertyIteratorObject(cx));
+    if (!propIter)
+        return nullptr;
+
     static_assert(sizeof(ReceiverGuard) == 2 * sizeof(GCPtrFlatString),
                   "NativeIterators are allocated in space for 1) themselves, "
                   "2) the properties a NativeIterator iterates (as "
-                  "GCPtrFlatStrings), and 3) |numGuards| ReceiverGuard "
+                  "GCPtrFlatStrings), and 3) |numGuards| HeapReceiverGuard "
                   "objects; the additional-length calculation below assumes "
                   "this size-relationship when determining the extra space to "
                   "allocate");
 
-    size_t extraLength = plength + numGuards * 2;
-    NativeIterator* ni =
-        cx->zone()->pod_malloc_with_extra<NativeIterator, GCPtrFlatString>(extraLength);
-    if (!ni) {
+    size_t extraCount = props.length() + numGuards * 2;
+    void* mem =
+        cx->zone()->pod_malloc_with_extra<NativeIterator, GCPtrFlatString>(extraCount);
+    if (!mem) {
         ReportOutOfMemory(cx);
         return nullptr;
     }
 
-    // Zero out the NativeIterator first.
-    PodZero(ni);
+    // This also registers |ni| with |propIter|.
+    bool hadError = false;
+    NativeIterator* ni =
+        new (mem) NativeIterator(cx, propIter, objBeingIterated, props, numGuards, guardKey,
+                                 &hadError);
+    if (hadError)
+        return nullptr;
+
+    RegisterEnumerator(cx, ni);
+    return propIter;
+}
 
-    // Zero out the remaining space for GCPtrFlatStrings for properties and
-    // ReceiverGuards for guards.
-    GCPtrFlatString* extra = ni->begin();
-    PodZero(extra, extraLength);
+/**
+ * Initialize a sentinel NativeIterator whose purpose is only to act as the
+ * start/end of the circular linked list of NativeIterators in
+ * JSCompartment::enumerators.
+ */
+NativeIterator::NativeIterator()
+{
+    // Do our best to enforce that nothing in |this| except the two fields set
+    // below is ever observed.
+    JS_POISON(static_cast<void*>(this), 0xCC, sizeof(*this), MemCheckKind::MakeUndefined);
 
-    ni->props_cursor = extra;
-    ni->props_end = extra + plength;
-    return ni;
+    // These are the only two fields in sentinel NativeIterators that are
+    // examined, in JSCompartment::sweepNativeIterators.  Everything else is
+    // only examined *if* it's a NativeIterator being traced by a
+    // PropertyIteratorObject that owns it, and nothing owns this iterator.
+    prev_ = next_ = this;
 }
 
 NativeIterator*
 NativeIterator::allocateSentinel(JSContext* maybecx)
 {
-    NativeIterator* ni = js_pod_malloc<NativeIterator>();
+    NativeIterator* ni = js_new<NativeIterator>();
     if (!ni) {
         if (maybecx)
             ReportOutOfMemory(maybecx);
-        return nullptr;
     }
 
-    PodZero(ni);
-
-    ni->next_ = ni;
-    ni->prev_ = ni;
     return ni;
 }
 
-inline void
-NativeIterator::init(JSObject* obj, JSObject* iterObj, uint32_t numGuards, uint32_t key)
-{
-    this->obj.init(obj);
-    this->iterObj_ = iterObj;
-    this->flags = 0;
-    this->guard_length = numGuards;
-    this->guard_key = key;
-}
-
-bool
-NativeIterator::initProperties(JSContext* cx, Handle<PropertyIteratorObject*> obj,
-                               const AutoIdVector& props)
+/**
+ * Initialize a fresh NativeIterator.
+ *
+ * This definition is a bit tricky: some parts of initializing are fallible, so
+ * as we initialize, we must carefully keep this in GC-safe state (see
+ * NativeIterator::trace).
+ */
+NativeIterator::NativeIterator(JSContext* cx, Handle<PropertyIteratorObject*> propIter,
+                               Handle<JSObject*> objBeingIterated, const AutoIdVector& props,
+                               uint32_t numGuards, uint32_t guardKey, bool* hadError)
+  : obj(objBeingIterated),
+    iterObj_(propIter),
+    // NativeIterator initially acts as if it contains no properties.
+    props_cursor(begin()),
+    props_end(props_cursor),
+    // ...and no HeapReceiverGuards.
+    guard_length(0),
+    guard_key(guardKey),
+    flags(0)
 {
-    // The obj parameter is just so that we can ensure that this object will get
-    // traced if we GC.
-    MOZ_ASSERT(this == obj->getNativeIterator());
+    MOZ_ASSERT(!*hadError);
 
-    size_t plength = props.length();
-    MOZ_ASSERT(plength == size_t(end() - begin()));
+    // NOTE: This must be done first thing: PropertyIteratorObject::finalize
+    //       can only free |this| (and not leak it) if this has happened.
+    propIter->setNativeIterator(this);
 
-    GCPtrFlatString* propNames = begin();
-    for (size_t i = 0; i < plength; i++) {
+    for (size_t i = 0, len = props.length(); i < len; i++) {
         JSFlatString* str = IdToString(cx, props[i]);
-        if (!str)
-            return false;
-        propNames[i].init(str);
+        if (!str) {
+            *hadError = true;
+            return;
+        }
+
+        // Placement-new the next property string at the end of the currently
+        // computed property strings.
+        GCPtrFlatString* loc = props_end;
+
+        // Increase the overall property string count before initializing the
+        // property string, so this construction isn't on a location not known
+        // to the GC yet.
+        props_end++;
+
+        new (loc) GCPtrFlatString(str);
     }
 
-    return true;
-}
-
-static inline void
-RegisterEnumerator(JSContext* cx, NativeIterator* ni)
-{
-    /* Register non-escaping native enumerators (for-in) with the current context. */
-    ni->link(cx->compartment()->enumerators);
-
-    MOZ_ASSERT(!(ni->flags & JSITER_ACTIVE));
-    ni->flags |= JSITER_ACTIVE;
-}
-
-static inline PropertyIteratorObject*
-VectorToKeyIterator(JSContext* cx, HandleObject obj, AutoIdVector& keys, uint32_t numGuards)
-{
-    if (obj->isSingleton() && !JSObject::setIteratedSingleton(cx, obj))
-        return nullptr;
-    MarkObjectGroupFlags(cx, obj, OBJECT_FLAG_ITERATED);
-
-    Rooted<PropertyIteratorObject*> iterobj(cx, NewPropertyIteratorObject(cx));
-    if (!iterobj)
-        return nullptr;
-
-    NativeIterator* ni = NativeIterator::allocateIterator(cx, numGuards, keys.length());
-    if (!ni)
-        return nullptr;
-
-    iterobj->setNativeIterator(ni);
-    ni->init(obj, iterobj, numGuards, 0);
-    if (!ni->initProperties(cx, iterobj, keys))
-        return nullptr;
-
-    if (numGuards) {
-        // Fill in the guard array from scratch. Also recompute the guard key
-        // as we might have reshaped the object (see for instance the
-        // setIteratedSingleton call above) or GC might have moved shapes and
-        // groups in memory.
-        JSObject* pobj = obj;
-        size_t ind = 0;
+    if (numGuards > 0) {
+        // Construct guards into the guard array.  Also recompute the guard key,
+        // which incorporates Shape* and ObjectGroup* addresses that could have
+        // changed during a GC triggered in (among other places) |IdToString|
+        //. above.
+        JSObject* pobj = objBeingIterated;
         uint32_t key = 0;
-        HeapReceiverGuard* guards = ni->guardArray();
+        HeapReceiverGuard* guards = guardArray();
         do {
             ReceiverGuard guard(pobj);
-            guards[ind++].init(guard);
+
+            // Placement-new the next HeapReceiverGuard at the end of the
+            // currently initialized HeapReceiverGuards.
+            uint32_t index = guard_length;
+
+            // Increase the overall guard-count before initializing the
+            // HeapReceiverGuard, so this construction isn't on a location not
+            // known to the GC.
+            guard_length++;
+
+            new (&guards[index]) HeapReceiverGuard(guard);
+
             key = mozilla::AddToHash(key, guard.hash());
 
             // The one caller of this method that passes |numGuards > 0|, does
             // so only if the entire chain consists of cacheable objects (that
             // necessarily have static prototypes).
             pobj = pobj->staticPrototype();
         } while (pobj);
-        ni->guard_key = key;
-        MOZ_ASSERT(ind == numGuards);
+
+        guard_key = key;
+        MOZ_ASSERT(guard_length == numGuards);
     }
 
-    RegisterEnumerator(cx, ni);
-    return iterobj;
+    MOZ_ASSERT(!*hadError);
+}
+
+static inline PropertyIteratorObject*
+VectorToKeyIterator(JSContext* cx, HandleObject obj, AutoIdVector& props, uint32_t numGuards)
+{
+    if (obj->isSingleton() && !JSObject::setIteratedSingleton(cx, obj))
+        return nullptr;
+    MarkObjectGroupFlags(cx, obj, OBJECT_FLAG_ITERATED);
+
+    return CreatePropertyIterator(cx, obj, props, numGuards, 0);
 }
 
 
 JSObject*
 js::EnumeratedIdVectorToIterator(JSContext* cx, HandleObject obj, AutoIdVector& props)
 {
     return VectorToKeyIterator(cx, obj, props, 0);
 }
 
 // Mainly used for .. in over null/undefined
 JSObject*
 js::NewEmptyPropertyIterator(JSContext* cx)
 {
-    Rooted<PropertyIteratorObject*> iterobj(cx, NewPropertyIteratorObject(cx));
-    if (!iterobj)
-        return nullptr;
-
-    AutoIdVector keys(cx); // Empty
-    NativeIterator* ni = NativeIterator::allocateIterator(cx, 0, keys.length());
-    if (!ni)
-        return nullptr;
-
-    iterobj->setNativeIterator(ni);
-    ni->init(nullptr, iterobj, 0, 0);
-    if (!ni->initProperties(cx, iterobj, keys))
-        return nullptr;
-
-    RegisterEnumerator(cx, ni);
-    return iterobj;
+    AutoIdVector props(cx); // Empty
+    return CreatePropertyIterator(cx, nullptr, props, 0, 0);
 }
 
 /* static */ bool
 IteratorHashPolicy::match(PropertyIteratorObject* obj, const Lookup& lookup)
 {
     NativeIterator* ni = obj->getNativeIterator();
     if (ni->guard_key != lookup.key || ni->guard_length != lookup.numGuards)
         return false;
--- a/js/src/vm/Iteration.h
+++ b/js/src/vm/Iteration.h
@@ -26,39 +26,61 @@
 #define JSITER_UNREUSABLE   0x2000
 
 namespace js {
 
 class PropertyIteratorObject;
 
 struct NativeIterator
 {
-    GCPtrObject obj;    // Object being iterated.
-    JSObject* iterObj_; // Internal iterator object.
-    GCPtrFlatString* props_cursor;
-    GCPtrFlatString* props_end; // also the start of HeapReceiverGuards
-    uint32_t guard_length;
-    uint32_t guard_key;
-    uint32_t flags;
+    // Object being iterated.
+    GCPtrObject obj = {};
+
+    // Internal iterator object.
+    JSObject* iterObj_ = nullptr;
+
+    // The next property, pointing into an array of strings directly after this
+    // NativeIterator as part of the overall allocation containing |*this|.
+    GCPtrFlatString* props_cursor; // initialized by constructor
+
+    // The limit/end of properties to iterate.  (This is also, after casting,
+    // the start of an array of HeapReceiverGuards included in the overall
+    // allocation that stores |*this| and the iterated strings.)
+    GCPtrFlatString* props_end; // initialized by constructor
+
+    uint32_t guard_length = 0;
+    uint32_t guard_key = 0;
+    uint32_t flags = 0;
 
   private:
     /* While in compartment->enumerators, these form a doubly linked list. */
-    NativeIterator* next_;
-    NativeIterator* prev_;
+    NativeIterator* next_ = nullptr;
+    NativeIterator* prev_ = nullptr;
 
     // No further fields appear after here *in NativeIterator*, but this class
-    // is always allocated with space tacked on immediately after |this| (see
-    // below) to store 1) a dynamically known number of iterated property names
-    // and 2) a dynamically known number of HeapReceiverGuards.  The limit of
-    // all such property names, and the start of HeapReceiverGuards, is
-    // |props_end|.  The next property name to iterate is |props_cursor| and
-    // equals |props_end| when this iterator is exhausted but not ready yet for
-    // possible reuse.
+    // is always allocated with space tacked on immediately after |this| to
+    // store iterated property names up to |props_end| and |guard_length|
+    // HeapReceiverGuards after that.
 
   public:
+    /**
+     * Initialize a NativeIterator properly allocated for |props.length()|
+     * properties and |numGuards| guards.
+     *
+     * Despite being a constructor, THIS FUNCTION CAN REPORT ERRORS.  Users
+     * MUST set |*hadError = false| on entry and consider |*hadError| on return
+     * to mean this function failed.
+     */
+    NativeIterator(JSContext* cx, Handle<PropertyIteratorObject*> propIter,
+                   Handle<JSObject*> objBeingIterated, const AutoIdVector& props,
+                   uint32_t numGuards, uint32_t guardKey, bool* hadError);
+
+    /** Initialize a |JSCompartment::enumerators| sentinel. */
+    NativeIterator();
+
     GCPtrFlatString* begin() const {
         static_assert(alignof(NativeIterator) >= alignof(GCPtrFlatString),
                       "GCPtrFlatStrings for properties must be able to appear "
                       "directly after NativeIterator, with no padding space "
                       "required for correct alignment");
 
         // Note that JIT code inlines this computation to reset |props_cursor|
         // when an iterator ends: see |CodeGenerator::visitIteratorEnd|.
@@ -118,26 +140,18 @@ struct NativeIterator
     void unlink() {
         next_->prev_ = prev_;
         prev_->next_ = next_;
         next_ = nullptr;
         prev_ = nullptr;
     }
 
     static NativeIterator* allocateSentinel(JSContext* maybecx);
-    static NativeIterator* allocateIterator(JSContext* cx, uint32_t slength, uint32_t plength);
-    void init(JSObject* obj, JSObject* iterObj, uint32_t slength, uint32_t key);
-    bool initProperties(JSContext* cx, Handle<PropertyIteratorObject*> obj,
-                        const js::AutoIdVector& props);
 
     void trace(JSTracer* trc);
-
-    static void destroy(NativeIterator* iter) {
-        js_free(iter);
-    }
 };
 
 class PropertyIteratorObject : public NativeObject
 {
     static const ClassOps classOps_;
 
   public:
     static const Class class_;