summaryrefslogtreecommitdiffstats
path: root/js/src/vm/PIC.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/PIC.cpp')
-rw-r--r--js/src/vm/PIC.cpp453
1 files changed, 453 insertions, 0 deletions
diff --git a/js/src/vm/PIC.cpp b/js/src/vm/PIC.cpp
new file mode 100644
index 0000000000..b43a46a209
--- /dev/null
+++ b/js/src/vm/PIC.cpp
@@ -0,0 +1,453 @@
+/* -*- 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/GCContext.h"
+#include "vm/GlobalObject.h"
+#include "vm/JSContext.h"
+#include "vm/JSObject.h"
+#include "vm/Realm.h"
+#include "vm/SelfHosting.h"
+
+#include "gc/GCContext-inl.h"
+#include "vm/JSContext-inl.h"
+#include "vm/JSObject-inl.h"
+
+using namespace js;
+
+template <typename Category>
+void PICChain<Category>::addStub(JSObject* obj, CatStub* stub) {
+ MOZ_ASSERT(stub);
+ MOZ_ASSERT(!stub->next());
+
+ AddCellMemory(obj, sizeof(CatStub), MemoryUse::ForOfPICStub);
+
+ if (!stubs_) {
+ stubs_ = stub;
+ return;
+ }
+
+ CatStub* cur = stubs_;
+ while (cur->next()) {
+ cur = cur->next();
+ }
+ cur->append(stub);
+}
+
+bool js::ForOfPIC::Chain::initialize(JSContext* cx) {
+ MOZ_ASSERT(!initialized_);
+
+ // Get the canonical Array.prototype
+ Rooted<NativeObject*> arrayProto(
+ cx, GlobalObject::getOrCreateArrayPrototype(cx, cx->global()));
+ if (!arrayProto) {
+ return false;
+ }
+
+ // Get the canonical ArrayIterator.prototype
+ Rooted<NativeObject*> arrayIteratorProto(
+ cx, GlobalObject::getOrCreateArrayIteratorPrototype(cx, cx->global()));
+ if (!arrayIteratorProto) {
+ return false;
+ }
+
+ // Get the canonical Iterator.prototype
+ Rooted<NativeObject*> iteratorProto(
+ cx, MaybeNativeObject(
+ GlobalObject::getOrCreateIteratorPrototype(cx, cx->global())));
+ if (!iteratorProto) {
+ return false;
+ }
+
+ Rooted<NativeObject*> objectProto(
+ cx, MaybeNativeObject(&cx->global()->getObjectPrototype()));
+ MOZ_ASSERT(objectProto);
+
+ // 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;
+ iteratorProto_ = iteratorProto;
+ objectProto_ = objectProto;
+
+ // 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.
+ mozilla::Maybe<PropertyInfo> iterProp = arrayProto->lookup(
+ cx, PropertyKey::Symbol(cx->wellKnownSymbols().iterator));
+ if (iterProp.isNothing() || !iterProp->isDataProperty()) {
+ return true;
+ }
+
+ // Get the referred value, and ensure it holds the canonical ArrayValues
+ // function.
+ Value iterator = arrayProto->getSlot(iterProp->slot());
+ JSFunction* iterFun;
+ if (!IsFunctionObject(iterator, &iterFun)) {
+ return true;
+ }
+ if (!IsSelfHostedFunctionWithName(iterFun, cx->names().dollar_ArrayValues_)) {
+ return true;
+ }
+
+ // Look up the 'next' value on ArrayIterator.prototype
+ mozilla::Maybe<PropertyInfo> nextProp =
+ arrayIteratorProto->lookup(cx, cx->names().next);
+ if (nextProp.isNothing() || !nextProp->isDataProperty()) {
+ return true;
+ }
+
+ // Get the referred value, ensure it holds the canonical ArrayIteratorNext
+ // function.
+ Value next = arrayIteratorProto->getSlot(nextProp->slot());
+ JSFunction* nextFun;
+ if (!IsFunctionObject(next, &nextFun)) {
+ return true;
+ }
+ if (!IsSelfHostedFunctionWithName(nextFun, cx->names().ArrayIteratorNext)) {
+ return true;
+ }
+
+ // Ensure ArrayIterator.prototype doesn't define a "return" property
+ if (arrayIteratorProto->lookup(cx, cx->names().return_).isSome()) {
+ return true;
+ }
+
+ // Ensure ArrayIterator.prototype's prototype is Iterator.prototype
+ if (arrayIteratorProto->staticPrototype() != iteratorProto) {
+ return true;
+ }
+
+ // Ensure Iterator.prototype doesn't define a "return" property
+ if (iteratorProto->lookup(cx, cx->names().return_).isSome()) {
+ return true;
+ }
+
+ // Ensure Iterator.prototype's prototype is Object.prototype
+ if (iteratorProto->staticPrototype() != objectProto) {
+ return true;
+ }
+
+ // Ensure Object.prototype doesn't define a "return" property
+ if (objectProto->lookup(cx, cx->names().return_).isSome()) {
+ return true;
+ }
+
+ disabled_ = false;
+ arrayProtoShape_ = arrayProto->shape();
+ arrayProtoIteratorSlot_ = iterProp->slot();
+ canonicalIteratorFunc_ = iterator;
+ arrayIteratorProtoShape_ = arrayIteratorProto->shape();
+ arrayIteratorProtoNextSlot_ = nextProp->slot();
+ canonicalNextFunc_ = next;
+ iteratorProtoShape_ = iteratorProto->shape();
+ objectProtoShape_ = objectProto->shape();
+ return true;
+}
+
+bool js::ForOfPIC::Chain::tryOptimizeArray(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_ && !isArrayStateStillSane()) {
+ // Otherwise, if array state is no longer sane, reinitialize.
+ reset(cx);
+
+ 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());
+
+ *optimized = true;
+ return true;
+}
+
+bool js::ForOfPIC::Chain::tryOptimizeArray(JSContext* cx,
+ Handle<ArrayObject*> 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(cx);
+
+ 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, PropertyKey::Symbol(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(cx);
+ }
+
+ // Good to optimize now, create stub to add.
+ Rooted<Shape*> shape(cx, array->shape());
+ Stub* stub = cx->new_<Stub>(shape);
+ if (!stub) {
+ return false;
+ }
+
+ // Add the stub.
+ addStub(picObject_, 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_ && !isArrayIteratorStateStillSane()) {
+ // Otherwise, if array iterator state is no longer sane, reinitialize.
+ reset(cx);
+
+ 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(isArrayIteratorStateStillSane());
+
+ *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_->shape() != arrayProtoShape_) {
+ return false;
+ }
+
+ // Ensure that Array.prototype[@@iterator] contains the
+ // canonical iterator function.
+ if (arrayProto_->getSlot(arrayProtoIteratorSlot_) != canonicalIteratorFunc_) {
+ return false;
+ }
+
+ // Chain to isArrayIteratorStateStillSane.
+ return isArrayIteratorStateStillSane();
+}
+
+void js::ForOfPIC::Chain::reset(JSContext* cx) {
+ // Should never reset a disabled_ stub.
+ MOZ_ASSERT(!disabled_);
+
+ // Erase the chain.
+ eraseChain(cx);
+
+ arrayProto_ = nullptr;
+ arrayIteratorProto_ = nullptr;
+ iteratorProto_ = nullptr;
+ objectProto_ = nullptr;
+
+ arrayProtoShape_ = nullptr;
+ arrayProtoIteratorSlot_ = -1;
+ canonicalIteratorFunc_ = UndefinedValue();
+
+ arrayIteratorProtoShape_ = nullptr;
+ arrayIteratorProtoNextSlot_ = -1;
+ canonicalNextFunc_ = UndefinedValue();
+
+ iteratorProtoShape_ = nullptr;
+ objectProtoShape_ = nullptr;
+
+ initialized_ = false;
+}
+
+void js::ForOfPIC::Chain::eraseChain(JSContext* cx) {
+ // Should never need to clear the chain of a disabled stub.
+ MOZ_ASSERT(!disabled_);
+ freeAllStubs(cx->gcContext());
+}
+
+// Trace the pointers stored directly on the stub.
+void js::ForOfPIC::Chain::trace(JSTracer* trc) {
+ TraceEdge(trc, &picObject_, "ForOfPIC object");
+
+ if (!initialized_ || disabled_) {
+ return;
+ }
+
+ TraceEdge(trc, &arrayProto_, "ForOfPIC Array.prototype.");
+ TraceEdge(trc, &arrayIteratorProto_, "ForOfPIC ArrayIterator.prototype.");
+ TraceEdge(trc, &iteratorProto_, "ForOfPIC Iterator.prototype.");
+ TraceEdge(trc, &objectProto_, "ForOfPIC Object.prototype.");
+
+ TraceEdge(trc, &arrayProtoShape_, "ForOfPIC Array.prototype shape.");
+ TraceEdge(trc, &arrayIteratorProtoShape_,
+ "ForOfPIC ArrayIterator.prototype shape.");
+ TraceEdge(trc, &iteratorProtoShape_, "ForOfPIC Iterator.prototype shape.");
+ TraceEdge(trc, &objectProtoShape_, "ForOfPIC Object.prototype shape.");
+
+ TraceEdge(trc, &canonicalIteratorFunc_, "ForOfPIC ArrayValues builtin.");
+ TraceEdge(trc, &canonicalNextFunc_,
+ "ForOfPIC ArrayIterator.prototype.next builtin.");
+
+ for (Stub* stub = stubs_; stub; stub = stub->next()) {
+ stub->trace(trc);
+ }
+}
+
+void js::ForOfPIC::Stub::trace(JSTracer* trc) {
+ TraceEdge(trc, &shape_, "ForOfPIC::Stub::shape_");
+}
+
+static void ForOfPIC_finalize(JS::GCContext* gcx, JSObject* obj) {
+ if (ForOfPIC::Chain* chain =
+ ForOfPIC::fromJSObject(&obj->as<NativeObject>())) {
+ chain->finalize(gcx, obj);
+ }
+}
+
+void js::ForOfPIC::Chain::finalize(JS::GCContext* gcx, JSObject* obj) {
+ freeAllStubs(gcx);
+ gcx->delete_(obj, this, MemoryUse::ForOfPIC);
+}
+
+void js::ForOfPIC::Chain::freeAllStubs(JS::GCContext* gcx) {
+ Stub* stub = stubs_;
+ while (stub) {
+ Stub* next = stub->next();
+ gcx->delete_(picObject_, stub, MemoryUse::ForOfPICStub);
+ stub = next;
+ }
+ stubs_ = nullptr;
+}
+
+static void ForOfPIC_traceObject(JSTracer* trc, JSObject* obj) {
+ if (ForOfPIC::Chain* chain =
+ ForOfPIC::fromJSObject(&obj->as<NativeObject>())) {
+ chain->trace(trc);
+ }
+}
+
+static const JSClassOps ForOfPICClassOps = {
+ nullptr, // addProperty
+ nullptr, // delProperty
+ nullptr, // enumerate
+ nullptr, // newEnumerate
+ nullptr, // resolve
+ nullptr, // mayResolve
+ ForOfPIC_finalize, // finalize
+ nullptr, // call
+ nullptr, // construct
+ ForOfPIC_traceObject, // trace
+};
+
+const JSClass ForOfPICObject::class_ = {
+ "ForOfPIC",
+ JSCLASS_HAS_RESERVED_SLOTS(SlotCount) | JSCLASS_BACKGROUND_FINALIZE,
+ &ForOfPICClassOps};
+
+/* static */
+NativeObject* js::ForOfPIC::createForOfPICObject(JSContext* cx,
+ Handle<GlobalObject*> global) {
+ cx->check(global);
+ ForOfPICObject* obj =
+ NewTenuredObjectWithGivenProto<ForOfPICObject>(cx, nullptr);
+ if (!obj) {
+ return nullptr;
+ }
+ ForOfPIC::Chain* chain = cx->new_<ForOfPIC::Chain>(obj);
+ if (!chain) {
+ return nullptr;
+ }
+ InitReservedSlot(obj, ForOfPICObject::ChainSlot, chain, MemoryUse::ForOfPIC);
+ 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);
+}