diff -r 000000000000 -r 6474c204b198 js/src/vm/PIC.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/src/vm/PIC.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -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 "jsobj.h" +#include "gc/Marking.h" + +#include "vm/GlobalObject.h" +#include "vm/ObjectImpl.h" +#include "vm/SelfHosting.h" +#include "jsobjinlines.h" +#include "vm/ObjectImpl-inl.h" + +using namespace js; +using namespace js::gc; + +bool +js::ForOfPIC::Chain::initialize(JSContext *cx) +{ + JS_ASSERT(!initialized_); + + // Get the canonical Array.prototype + RootedObject arrayProto(cx, GlobalObject::getOrCreateArrayPrototype(cx, cx->global())); + if (!arrayProto) + return false; + + // Get the canonical ArrayIterator.prototype + RootedObject 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 '@@iterator' on Array.prototype, ensure it's a slotful shape. + Shape *iterShape = arrayProto->nativeLookup(cx, cx->names().std_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->nativeLookup(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, HandleObject array, bool *optimized) +{ + JS_ASSERT(array->is()); + JS_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; + } + JS_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. + JS_ASSERT(isArrayStateStillSane()); + + // Check if stub already exists. + ForOfPIC::Stub *stub = isArrayOptimized(&array->as()); + 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->nativeLookup(cx, cx->names().std_iterator)) + return true; + + // Good to optimize now, create stub to add. + RootedShape shape(cx, array->lastProperty()); + stub = cx->new_(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->lastProperty()) + return stub; + } + + return nullptr; +} + +bool +js::ForOfPIC::Chain::isOptimizableArray(JSObject *obj) +{ + JS_ASSERT(obj->is()); + + // Ensure object's prototype is the actual Array.prototype + if (!obj->getTaggedProto().isObject()) + return false; + if (obj->getTaggedProto().toObject() != arrayProto_) + return false; + + return true; +} + +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. + JS_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. + JS_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; + + gc::MarkObject(trc, &arrayProto_, "ForOfPIC Array.prototype."); + gc::MarkObject(trc, &arrayIteratorProto_, "ForOfPIC ArrayIterator.prototype."); + + gc::MarkShape(trc, &arrayProtoShape_, "ForOfPIC Array.prototype shape."); + gc::MarkShape(trc, &arrayIteratorProtoShape_, "ForOfPIC ArrayIterator.prototype shape."); + + gc::MarkValue(trc, &canonicalIteratorFunc_, "ForOfPIC ArrayValues builtin."); + gc::MarkValue(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) +{ + if (ForOfPIC::Chain *chain = ForOfPIC::fromJSObject(obj)) + chain->sweep(fop); +} + +static void +ForOfPIC_traceObject(JSTracer *trc, JSObject *obj) +{ + if (ForOfPIC::Chain *chain = ForOfPIC::fromJSObject(obj)) + chain->mark(trc); +} + +const Class ForOfPIC::jsclass = { + "ForOfPIC", JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS, + JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, ForOfPIC_finalize, + nullptr, /* call */ + nullptr, /* hasInstance */ + nullptr, /* construct */ + ForOfPIC_traceObject +}; + +/* static */ JSObject * +js::ForOfPIC::createForOfPICObject(JSContext *cx, Handle global) +{ + assertSameCompartment(cx, global); + JSObject *obj = NewObjectWithGivenProto(cx, &ForOfPIC::jsclass, nullptr, global); + if (!obj) + return nullptr; + ForOfPIC::Chain *chain = cx->new_(); + if (!chain) + return nullptr; + obj->setPrivate(chain); + return obj; +} + +/* static */ js::ForOfPIC::Chain * +js::ForOfPIC::create(JSContext *cx) +{ + JS_ASSERT(!cx->global()->getForOfPICObject()); + Rooted global(cx, cx->global()); + JSObject *obj = GlobalObject::getOrCreateForOfPICObject(cx, global); + if (!obj) + return nullptr; + return fromJSObject(obj); +}