mozjs_sys 0.67.1

System crate for the Mozilla SpiderMonkey JavaScript engine.
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "vm/PIC.h"

#include "gc/FreeOp.h"
#include "gc/Marking.h"
#include "vm/GlobalObject.h"
#include "vm/JSContext.h"
#include "vm/JSObject.h"
#include "vm/Realm.h"
#include "vm/SelfHosting.h"

#include "vm/JSObject-inl.h"
#include "vm/NativeObject-inl.h"

using namespace js;

bool js::ForOfPIC::Chain::initialize(JSContext* cx) {
  MOZ_ASSERT(!initialized_);

  // Get the canonical Array.prototype
  RootedNativeObject arrayProto(
      cx, GlobalObject::getOrCreateArrayPrototype(cx, cx->global()));
  if (!arrayProto) {
    return false;
  }

  // Get the canonical ArrayIterator.prototype
  RootedNativeObject arrayIteratorProto(
      cx, GlobalObject::getOrCreateArrayIteratorPrototype(cx, cx->global()));
  if (!arrayIteratorProto) {
    return false;
  }

  // From this point on, we can't fail.  Set initialized and fill the fields
  // for the canonical Array.prototype and ArrayIterator.prototype objects.
  initialized_ = true;
  arrayProto_ = arrayProto;
  arrayIteratorProto_ = arrayIteratorProto;

  // Shortcut returns below means Array for-of will never be optimizable,
  // do set disabled_ now, and clear it later when we succeed.
  disabled_ = true;

  // Look up Array.prototype[@@iterator], ensure it's a slotful shape.
  Shape* iterShape =
      arrayProto->lookup(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().iterator));
  if (!iterShape || !iterShape->isDataProperty()) {
    return true;
  }

  // Get the referred value, and ensure it holds the canonical ArrayValues
  // function.
  Value iterator = arrayProto->getSlot(iterShape->slot());
  JSFunction* iterFun;
  if (!IsFunctionObject(iterator, &iterFun)) {
    return true;
  }
  if (!IsSelfHostedFunctionWithName(iterFun, cx->names().ArrayValues)) {
    return true;
  }

  // Look up the 'next' value on ArrayIterator.prototype
  Shape* nextShape = arrayIteratorProto->lookup(cx, cx->names().next);
  if (!nextShape || !nextShape->isDataProperty()) {
    return true;
  }

  // Get the referred value, ensure it holds the canonical ArrayIteratorNext
  // function.
  Value next = arrayIteratorProto->getSlot(nextShape->slot());
  JSFunction* nextFun;
  if (!IsFunctionObject(next, &nextFun)) {
    return true;
  }
  if (!IsSelfHostedFunctionWithName(nextFun, cx->names().ArrayIteratorNext)) {
    return true;
  }

  disabled_ = false;
  arrayProtoShape_ = arrayProto->lastProperty();
  arrayProtoIteratorSlot_ = iterShape->slot();
  canonicalIteratorFunc_ = iterator;
  arrayIteratorProtoShape_ = arrayIteratorProto->lastProperty();
  arrayIteratorProtoNextSlot_ = nextShape->slot();
  canonicalNextFunc_ = next;
  return true;
}

bool js::ForOfPIC::Chain::tryOptimizeArray(JSContext* cx,
                                           HandleArrayObject array,
                                           bool* optimized) {
  MOZ_ASSERT(optimized);

  *optimized = false;

  if (!initialized_) {
    // If PIC is not initialized, initialize it.
    if (!initialize(cx)) {
      return false;
    }

  } else if (!disabled_ && !isArrayStateStillSane()) {
    // Otherwise, if array state is no longer sane, reinitialize.
    reset();

    if (!initialize(cx)) {
      return false;
    }
  }
  MOZ_ASSERT(initialized_);

  // If PIC is disabled, don't bother trying to optimize.
  if (disabled_) {
    return true;
  }

  // By the time we get here, we should have a sane array state to work with.
  MOZ_ASSERT(isArrayStateStillSane());

  // Ensure array's prototype is the actual Array.prototype
  if (array->staticPrototype() != arrayProto_) {
    return true;
  }

  // Check if stub already exists.
  if (hasMatchingStub(array)) {
    *optimized = true;
    return true;
  }

  // Ensure array doesn't define @@iterator directly.
  if (array->lookup(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().iterator))) {
    return true;
  }

  // If the number of stubs is about to exceed the limit, throw away entire
  // existing cache before adding new stubs.  We shouldn't really have heavy
  // churn on these.
  if (numStubs() >= MAX_STUBS) {
    eraseChain();
  }

  // Good to optimize now, create stub to add.
  RootedShape shape(cx, array->lastProperty());
  Stub* stub = cx->new_<Stub>(shape);
  if (!stub) {
    return false;
  }

  // Add the stub.
  addStub(stub);

  *optimized = true;
  return true;
}

bool js::ForOfPIC::Chain::tryOptimizeArrayIteratorNext(JSContext* cx,
                                                       bool* optimized) {
  MOZ_ASSERT(optimized);

  *optimized = false;

  if (!initialized_) {
    // If PIC is not initialized, initialize it.
    if (!initialize(cx)) {
      return false;
    }
  } else if (!disabled_ && !isArrayNextStillSane()) {
    // Otherwise, if array iterator state is no longer sane, reinitialize.
    reset();

    if (!initialize(cx)) {
      return false;
    }
  }
  MOZ_ASSERT(initialized_);

  // If PIC is disabled, don't bother trying to optimize.
  if (disabled_) {
    return true;
  }

  // By the time we get here, we should have a sane iterator state to work with.
  MOZ_ASSERT(isArrayNextStillSane());

  *optimized = true;
  return true;
}

bool js::ForOfPIC::Chain::hasMatchingStub(ArrayObject* obj) {
  // Ensure PIC is initialized and not disabled.
  MOZ_ASSERT(initialized_ && !disabled_);

  // Check if there is a matching stub.
  for (Stub* stub = stubs(); stub != nullptr; stub = stub->next()) {
    if (stub->shape() == obj->shape()) {
      return true;
    }
  }

  return false;
}

bool js::ForOfPIC::Chain::isArrayStateStillSane() {
  // Ensure that canonical Array.prototype has matching shape.
  if (arrayProto_->lastProperty() != arrayProtoShape_) {
    return false;
  }

  // Ensure that Array.prototype[@@iterator] contains the
  // canonical iterator function.
  if (arrayProto_->getSlot(arrayProtoIteratorSlot_) != canonicalIteratorFunc_) {
    return false;
  }

  // Chain to isArrayNextStillSane.
  return isArrayNextStillSane();
}

void js::ForOfPIC::Chain::reset() {
  // Should never reset a disabled_ stub.
  MOZ_ASSERT(!disabled_);

  // Erase the chain.
  eraseChain();

  arrayProto_ = nullptr;
  arrayIteratorProto_ = nullptr;

  arrayProtoShape_ = nullptr;
  arrayProtoIteratorSlot_ = -1;
  canonicalIteratorFunc_ = UndefinedValue();

  arrayIteratorProtoShape_ = nullptr;
  arrayIteratorProtoNextSlot_ = -1;
  canonicalNextFunc_ = UndefinedValue();

  initialized_ = false;
}

void js::ForOfPIC::Chain::eraseChain() {
  // Should never need to clear the chain of a disabled stub.
  MOZ_ASSERT(!disabled_);

  // Free all stubs.
  Stub* stub = stubs_;
  while (stub) {
    Stub* next = stub->next();
    js_delete(stub);
    stub = next;
  }
  stubs_ = nullptr;
}

// Trace the pointers stored directly on the stub.
void js::ForOfPIC::Chain::trace(JSTracer* trc) {
  if (!initialized_ || disabled_) {
    return;
  }

  TraceEdge(trc, &arrayProto_, "ForOfPIC Array.prototype.");
  TraceEdge(trc, &arrayIteratorProto_, "ForOfPIC ArrayIterator.prototype.");

  TraceEdge(trc, &arrayProtoShape_, "ForOfPIC Array.prototype shape.");
  TraceEdge(trc, &arrayIteratorProtoShape_,
            "ForOfPIC ArrayIterator.prototype shape.");

  TraceEdge(trc, &canonicalIteratorFunc_, "ForOfPIC ArrayValues builtin.");
  TraceEdge(trc, &canonicalNextFunc_,
            "ForOfPIC ArrayIterator.prototype.next builtin.");

  // Free all the stubs in the chain.
  while (stubs_) {
    removeStub(stubs_, nullptr);
  }
}

void js::ForOfPIC::Chain::sweep(FreeOp* fop) {
  // Free all the stubs in the chain.
  while (stubs_) {
    Stub* next = stubs_->next();
    fop->delete_(stubs_);
    stubs_ = next;
  }
  fop->delete_(this);
}

static void ForOfPIC_finalize(FreeOp* fop, JSObject* obj) {
  MOZ_ASSERT(fop->maybeOnHelperThread());
  if (ForOfPIC::Chain* chain =
          ForOfPIC::fromJSObject(&obj->as<NativeObject>())) {
    chain->sweep(fop);
  }
}

static void ForOfPIC_traceObject(JSTracer* trc, JSObject* obj) {
  if (ForOfPIC::Chain* chain =
          ForOfPIC::fromJSObject(&obj->as<NativeObject>())) {
    chain->trace(trc);
  }
}

static const ClassOps ForOfPICClassOps = {nullptr,
                                          nullptr,
                                          nullptr,
                                          nullptr,
                                          nullptr,
                                          nullptr,
                                          ForOfPIC_finalize,
                                          nullptr, /* call        */
                                          nullptr, /* hasInstance */
                                          nullptr, /* construct   */
                                          ForOfPIC_traceObject};

const Class ForOfPIC::class_ = {
    "ForOfPIC", JSCLASS_HAS_PRIVATE | JSCLASS_BACKGROUND_FINALIZE,
    &ForOfPICClassOps};

/* static */
NativeObject* js::ForOfPIC::createForOfPICObject(JSContext* cx,
                                                 Handle<GlobalObject*> global) {
  cx->check(global);
  NativeObject* obj = NewNativeObjectWithGivenProto(cx, &ForOfPIC::class_,
                                                    nullptr, TenuredObject);
  if (!obj) {
    return nullptr;
  }
  ForOfPIC::Chain* chain = cx->new_<ForOfPIC::Chain>();
  if (!chain) {
    return nullptr;
  }
  obj->setPrivate(chain);
  return obj;
}

/* static */ js::ForOfPIC::Chain* js::ForOfPIC::create(JSContext* cx) {
  MOZ_ASSERT(!cx->global()->getForOfPICObject());
  Rooted<GlobalObject*> global(cx, cx->global());
  NativeObject* obj = GlobalObject::getOrCreateForOfPICObject(cx, global);
  if (!obj) {
    return nullptr;
  }
  return fromJSObject(obj);
}