Commit 66114545 authored by Jan de Mooij's avatar Jan de Mooij
Browse files

Bug 1733075 part 2 - Optimize enumeration with no enumerable properties on the...

Bug 1733075 part 2 - Optimize enumeration with no enumerable properties on the proto chain better. r=iain

Add a fast path to better optimize the common case where the proto chain has no
enumerable properties. The advantages of this are:

* We no longer have to iterate over all the properties on the proto chain.
* We don't need to check for duplicates (this involves a relatively slow HashSet and includes non-enumerable properties too).

ProtoMayHaveEnumerableProperties returns false (meaning the fast path works) in
97-100% of cases on typical web workloads.

Differential Revision: https://phabricator.services.mozilla.com/D127055
parent 5c84d76a
Loading
Loading
Loading
Loading
+59 −5
Original line number Diff line number Diff line
@@ -457,11 +457,69 @@ struct SortComparatorIds {

#endif /* DEBUG */

static void AssertNoEnumerableProperties(NativeObject* obj) {
#ifdef DEBUG
  // Verify the object has no enumerable properties if the HasEnumerable
  // ObjectFlag is not set.

  MOZ_ASSERT(!obj->hasEnumerableProperty());

  static constexpr size_t MaxPropsToCheck = 5;

  size_t count = 0;
  for (ShapePropertyIter<NoGC> iter(obj->shape()); !iter.done(); iter++) {
    MOZ_ASSERT(!iter->enumerable());
    if (++count > MaxPropsToCheck) {
      break;
    }
  }
#endif  // DEBUG
}

// Typed arrays and classes with an enumerate hook can have extra properties not
// included in the shape's property map or the object's dense elements.
static bool ClassCanHaveExtraEnumeratedProperties(const JSClass* clasp) {
  return IsTypedArrayClass(clasp) || clasp->getNewEnumerate() ||
         clasp->getEnumerate();
}

static bool ProtoMayHaveEnumerableProperties(JSObject* obj) {
  if (!obj->is<NativeObject>()) {
    return true;
  }

  JSObject* proto = obj->as<NativeObject>().staticPrototype();
  while (proto) {
    if (!proto->is<NativeObject>()) {
      return true;
    }
    NativeObject* nproto = &proto->as<NativeObject>();
    if (nproto->hasEnumerableProperty() ||
        nproto->getDenseInitializedLength() > 0 ||
        ClassCanHaveExtraEnumeratedProperties(nproto->getClass())) {
      return true;
    }
    AssertNoEnumerableProperties(nproto);
    proto = nproto->staticPrototype();
  }

  return false;
}

static bool Snapshot(JSContext* cx, HandleObject pobj_, unsigned flags,
                     MutableHandleIdVector props) {
  Rooted<PropertyKeySet> visited(cx, PropertyKeySet(cx));
  RootedObject pobj(cx, pobj_);

  // If we're only interested in enumerable properties and the proto chain has
  // no enumerable properties (the common case), we can optimize this to ignore
  // the proto chain. This also lets us take advantage of the no-duplicate-check
  // optimization below.
  if (!(flags & JSITER_HIDDEN) && !(flags & JSITER_OWNONLY) &&
      !ProtoMayHaveEnumerableProperties(pobj)) {
    flags |= JSITER_OWNONLY;
  }

  // Don't check for duplicates if we're only interested in own properties.
  // This does the right thing for most objects: native objects don't have
  // duplicate property ids and we allow the [[OwnPropertyKeys]] proxy trap to
@@ -847,11 +905,7 @@ static bool CanStoreInIteratorCache(JSObject* obj) {

    // Typed arrays have indexed properties not captured by the Shape guard.
    // Enumerate hooks may add extra properties.
    const JSClass* clasp = obj->getClass();
    if (MOZ_UNLIKELY(IsTypedArrayClass(clasp))) {
      return false;
    }
    if (MOZ_UNLIKELY(clasp->getNewEnumerate() || clasp->getEnumerate())) {
    if (MOZ_UNLIKELY(ClassCanHaveExtraEnumeratedProperties(obj->getClass()))) {
      return false;
    }