diff options
Diffstat (limited to 'js/src/vm/PIC.cpp')
-rw-r--r-- | js/src/vm/PIC.cpp | 330 |
1 files changed, 330 insertions, 0 deletions
diff --git a/js/src/vm/PIC.cpp b/js/src/vm/PIC.cpp new file mode 100644 index 000000000..46843b759 --- /dev/null +++ b/js/src/vm/PIC.cpp @@ -0,0 +1,330 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 "jscntxt.h" +#include "jscompartment.h" +#include "jsobj.h" +#include "gc/Marking.h" + +#include "vm/GlobalObject.h" +#include "vm/SelfHosting.h" + +#include "jsobjinlines.h" +#include "vm/NativeObject-inl.h" + +using namespace js; +using namespace js::gc; + +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->hasSlot() || !iterShape->hasDefaultGetter()) + 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->hasSlot()) + 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; +} + +js::ForOfPIC::Stub* +js::ForOfPIC::Chain::isArrayOptimized(ArrayObject* obj) +{ + Stub* stub = getMatchingStub(obj); + if (!stub) + return nullptr; + + // Ensure that this is an otherwise optimizable array. + if (!isOptimizableArray(obj)) + return nullptr; + + // Not yet enough! Ensure that the world as we know it remains sane. + if (!isArrayStateStillSane()) + return nullptr; + + return stub; +} + +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()); + + // Check if stub already exists. + ForOfPIC::Stub* stub = isArrayOptimized(&array->as<ArrayObject>()); + if (stub) { + *optimized = true; + 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(); + + // Ensure array's prototype is the actual Array.prototype + if (!isOptimizableArray(array)) + return true; + + // Ensure array doesn't define @@iterator directly. + if (array->lookup(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().iterator))) + return true; + + // Good to optimize now, create stub to add. + RootedShape shape(cx, array->lastProperty()); + stub = cx->new_<Stub>(shape); + if (!stub) + return false; + + // Add the stub. + addStub(stub); + + *optimized = true; + return true; +} + +js::ForOfPIC::Stub* +js::ForOfPIC::Chain::getMatchingStub(JSObject* obj) +{ + // Ensure PIC is initialized and not disabled. + if (!initialized_ || disabled_) + return nullptr; + + // Check if there is a matching stub. + for (Stub* stub = stubs(); stub != nullptr; stub = stub->next()) { + if (stub->shape() == obj->maybeShape()) + return stub; + } + + return nullptr; +} + +bool +js::ForOfPIC::Chain::isOptimizableArray(JSObject* obj) +{ + MOZ_ASSERT(obj->is<ArrayObject>()); + return obj->staticPrototype() == arrayProto_; +} + +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(); + + 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::mark(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->maybeOffMainThread()); + 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->mark(trc); +} + +static const ClassOps ForOfPICClassOps = { + nullptr, 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) +{ + assertSameCompartment(cx, global); + NativeObject* obj = NewNativeObjectWithGivenProto(cx, &ForOfPIC::class_, nullptr); + 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); +} |