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.cpp370
1 files changed, 370 insertions, 0 deletions
diff --git a/js/src/vm/PIC.cpp b/js/src/vm/PIC.cpp
new file mode 100644
index 0000000000..a35677f79c
--- /dev/null
+++ b/js/src/vm/PIC.cpp
@@ -0,0 +1,370 @@
+/* -*- 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;
+
+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
+ 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(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, 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(cx);
+ }
+
+ // 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(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_ && !isArrayNextStillSane()) {
+ // 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(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(JSContext* cx) {
+ // Should never reset a disabled_ stub.
+ MOZ_ASSERT(!disabled_);
+
+ // Erase the chain.
+ eraseChain(cx);
+
+ arrayProto_ = nullptr;
+ arrayIteratorProto_ = nullptr;
+
+ arrayProtoShape_ = nullptr;
+ arrayProtoIteratorSlot_ = -1;
+ canonicalIteratorFunc_ = UndefinedValue();
+
+ arrayIteratorProtoShape_ = nullptr;
+ arrayIteratorProtoNextSlot_ = -1;
+ canonicalNextFunc_ = UndefinedValue();
+
+ 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->defaultFreeOp());
+}
+
+// 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, &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.");
+
+ if (trc->isMarkingTracer()) {
+ // Free all the stubs in the chain.
+ freeAllStubs(trc->runtime()->defaultFreeOp());
+ }
+}
+
+static void ForOfPIC_finalize(JSFreeOp* fop, JSObject* obj) {
+ MOZ_ASSERT(fop->maybeOnHelperThread());
+ if (ForOfPIC::Chain* chain =
+ ForOfPIC::fromJSObject(&obj->as<NativeObject>())) {
+ chain->finalize(fop, obj);
+ }
+}
+
+void js::ForOfPIC::Chain::finalize(JSFreeOp* fop, JSObject* obj) {
+ freeAllStubs(fop);
+ fop->delete_(obj, this, MemoryUse::ForOfPIC);
+}
+
+void js::ForOfPIC::Chain::freeAllStubs(JSFreeOp* fop) {
+ Stub* stub = stubs_;
+ while (stub) {
+ Stub* next = stub->next();
+ fop->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, // hasInstance
+ nullptr, // construct
+ ForOfPIC_traceObject, // trace
+};
+
+const JSClass ForOfPICObject::class_ = {
+ "ForOfPIC", JSCLASS_HAS_PRIVATE | 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;
+ }
+ InitObjectPrivate(obj, chain, MemoryUse::ForOfPIC);
+ 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);
+}