michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "vm/PIC.h" michael@0: #include "jscntxt.h" michael@0: #include "jsobj.h" michael@0: #include "gc/Marking.h" michael@0: michael@0: #include "vm/GlobalObject.h" michael@0: #include "vm/ObjectImpl.h" michael@0: #include "vm/SelfHosting.h" michael@0: #include "jsobjinlines.h" michael@0: #include "vm/ObjectImpl-inl.h" michael@0: michael@0: using namespace js; michael@0: using namespace js::gc; michael@0: michael@0: bool michael@0: js::ForOfPIC::Chain::initialize(JSContext *cx) michael@0: { michael@0: JS_ASSERT(!initialized_); michael@0: michael@0: // Get the canonical Array.prototype michael@0: RootedObject arrayProto(cx, GlobalObject::getOrCreateArrayPrototype(cx, cx->global())); michael@0: if (!arrayProto) michael@0: return false; michael@0: michael@0: // Get the canonical ArrayIterator.prototype michael@0: RootedObject arrayIteratorProto(cx, michael@0: GlobalObject::getOrCreateArrayIteratorPrototype(cx, cx->global())); michael@0: if (!arrayIteratorProto) michael@0: return false; michael@0: michael@0: // From this point on, we can't fail. Set initialized and fill the fields michael@0: // for the canonical Array.prototype and ArrayIterator.prototype objects. michael@0: initialized_ = true; michael@0: arrayProto_ = arrayProto; michael@0: arrayIteratorProto_ = arrayIteratorProto; michael@0: michael@0: // Shortcut returns below means Array for-of will never be optimizable, michael@0: // do set disabled_ now, and clear it later when we succeed. michael@0: disabled_ = true; michael@0: michael@0: // Look up '@@iterator' on Array.prototype, ensure it's a slotful shape. michael@0: Shape *iterShape = arrayProto->nativeLookup(cx, cx->names().std_iterator); michael@0: if (!iterShape || !iterShape->hasSlot() || !iterShape->hasDefaultGetter()) michael@0: return true; michael@0: michael@0: // Get the referred value, and ensure it holds the canonical ArrayValues function. michael@0: Value iterator = arrayProto->getSlot(iterShape->slot()); michael@0: JSFunction *iterFun; michael@0: if (!IsFunctionObject(iterator, &iterFun)) michael@0: return true; michael@0: if (!IsSelfHostedFunctionWithName(iterFun, cx->names().ArrayValues)) michael@0: return true; michael@0: michael@0: // Look up the 'next' value on ArrayIterator.prototype michael@0: Shape *nextShape = arrayIteratorProto->nativeLookup(cx, cx->names().next); michael@0: if (!nextShape || !nextShape->hasSlot()) michael@0: return true; michael@0: michael@0: // Get the referred value, ensure it holds the canonical ArrayIteratorNext function. michael@0: Value next = arrayIteratorProto->getSlot(nextShape->slot()); michael@0: JSFunction *nextFun; michael@0: if (!IsFunctionObject(next, &nextFun)) michael@0: return true; michael@0: if (!IsSelfHostedFunctionWithName(nextFun, cx->names().ArrayIteratorNext)) michael@0: return true; michael@0: michael@0: disabled_ = false; michael@0: arrayProtoShape_ = arrayProto->lastProperty(); michael@0: arrayProtoIteratorSlot_ = iterShape->slot(); michael@0: canonicalIteratorFunc_ = iterator; michael@0: arrayIteratorProtoShape_ = arrayIteratorProto->lastProperty(); michael@0: arrayIteratorProtoNextSlot_ = nextShape->slot(); michael@0: canonicalNextFunc_ = next; michael@0: return true; michael@0: } michael@0: michael@0: js::ForOfPIC::Stub * michael@0: js::ForOfPIC::Chain::isArrayOptimized(ArrayObject *obj) michael@0: { michael@0: Stub *stub = getMatchingStub(obj); michael@0: if (!stub) michael@0: return nullptr; michael@0: michael@0: // Ensure that this is an otherwise optimizable array. michael@0: if (!isOptimizableArray(obj)) michael@0: return nullptr; michael@0: michael@0: // Not yet enough! Ensure that the world as we know it remains sane. michael@0: if (!isArrayStateStillSane()) michael@0: return nullptr; michael@0: michael@0: return stub; michael@0: } michael@0: michael@0: bool michael@0: js::ForOfPIC::Chain::tryOptimizeArray(JSContext *cx, HandleObject array, bool *optimized) michael@0: { michael@0: JS_ASSERT(array->is()); michael@0: JS_ASSERT(optimized); michael@0: michael@0: *optimized = false; michael@0: michael@0: if (!initialized_) { michael@0: // If PIC is not initialized, initialize it. michael@0: if (!initialize(cx)) michael@0: return false; michael@0: michael@0: } else if (!disabled_ && !isArrayStateStillSane()) { michael@0: // Otherwise, if array state is no longer sane, reinitialize. michael@0: reset(cx); michael@0: michael@0: if (!initialize(cx)) michael@0: return false; michael@0: } michael@0: JS_ASSERT(initialized_); michael@0: michael@0: // If PIC is disabled, don't bother trying to optimize. michael@0: if (disabled_) michael@0: return true; michael@0: michael@0: // By the time we get here, we should have a sane array state to work with. michael@0: JS_ASSERT(isArrayStateStillSane()); michael@0: michael@0: // Check if stub already exists. michael@0: ForOfPIC::Stub *stub = isArrayOptimized(&array->as()); michael@0: if (stub) { michael@0: *optimized = true; michael@0: return true; michael@0: } michael@0: michael@0: // If the number of stubs is about to exceed the limit, throw away entire michael@0: // existing cache before adding new stubs. We shouldn't really have heavy michael@0: // churn on these. michael@0: if (numStubs() >= MAX_STUBS) michael@0: eraseChain(); michael@0: michael@0: // Ensure array's prototype is the actual Array.prototype michael@0: if (!isOptimizableArray(array)) michael@0: return true; michael@0: michael@0: // Ensure array doesn't define '@@iterator' directly. michael@0: if (array->nativeLookup(cx, cx->names().std_iterator)) michael@0: return true; michael@0: michael@0: // Good to optimize now, create stub to add. michael@0: RootedShape shape(cx, array->lastProperty()); michael@0: stub = cx->new_(shape); michael@0: if (!stub) michael@0: return false; michael@0: michael@0: // Add the stub. michael@0: addStub(stub); michael@0: michael@0: *optimized = true; michael@0: return true; michael@0: } michael@0: michael@0: js::ForOfPIC::Stub * michael@0: js::ForOfPIC::Chain::getMatchingStub(JSObject *obj) michael@0: { michael@0: // Ensure PIC is initialized and not disabled. michael@0: if (!initialized_ || disabled_) michael@0: return nullptr; michael@0: michael@0: // Check if there is a matching stub. michael@0: for (Stub *stub = stubs(); stub != nullptr; stub = stub->next()) { michael@0: if (stub->shape() == obj->lastProperty()) michael@0: return stub; michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: bool michael@0: js::ForOfPIC::Chain::isOptimizableArray(JSObject *obj) michael@0: { michael@0: JS_ASSERT(obj->is()); michael@0: michael@0: // Ensure object's prototype is the actual Array.prototype michael@0: if (!obj->getTaggedProto().isObject()) michael@0: return false; michael@0: if (obj->getTaggedProto().toObject() != arrayProto_) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::ForOfPIC::Chain::isArrayStateStillSane() michael@0: { michael@0: // Ensure that canonical Array.prototype has matching shape. michael@0: if (arrayProto_->lastProperty() != arrayProtoShape_) michael@0: return false; michael@0: michael@0: // Ensure that Array.prototype['@@iterator'] contains the michael@0: // canonical iterator function. michael@0: if (arrayProto_->getSlot(arrayProtoIteratorSlot_) != canonicalIteratorFunc_) michael@0: return false; michael@0: michael@0: // Chain to isArrayNextStillSane. michael@0: return isArrayNextStillSane(); michael@0: } michael@0: michael@0: void michael@0: js::ForOfPIC::Chain::reset(JSContext *cx) michael@0: { michael@0: // Should never reset a disabled_ stub. michael@0: JS_ASSERT(!disabled_); michael@0: michael@0: // Erase the chain. michael@0: eraseChain(); michael@0: michael@0: arrayProto_ = nullptr; michael@0: arrayIteratorProto_ = nullptr; michael@0: michael@0: arrayProtoShape_ = nullptr; michael@0: arrayProtoIteratorSlot_ = -1; michael@0: canonicalIteratorFunc_ = UndefinedValue(); michael@0: michael@0: arrayIteratorProtoShape_ = nullptr; michael@0: arrayIteratorProtoNextSlot_ = -1; michael@0: canonicalNextFunc_ = UndefinedValue(); michael@0: michael@0: initialized_ = false; michael@0: } michael@0: michael@0: void michael@0: js::ForOfPIC::Chain::eraseChain() michael@0: { michael@0: // Should never need to clear the chain of a disabled stub. michael@0: JS_ASSERT(!disabled_); michael@0: michael@0: // Free all stubs. michael@0: Stub *stub = stubs_; michael@0: while (stub) { michael@0: Stub *next = stub->next(); michael@0: js_delete(stub); michael@0: stub = next; michael@0: } michael@0: stubs_ = nullptr; michael@0: } michael@0: michael@0: michael@0: // Trace the pointers stored directly on the stub. michael@0: void michael@0: js::ForOfPIC::Chain::mark(JSTracer *trc) michael@0: { michael@0: if (!initialized_ || disabled_) michael@0: return; michael@0: michael@0: gc::MarkObject(trc, &arrayProto_, "ForOfPIC Array.prototype."); michael@0: gc::MarkObject(trc, &arrayIteratorProto_, "ForOfPIC ArrayIterator.prototype."); michael@0: michael@0: gc::MarkShape(trc, &arrayProtoShape_, "ForOfPIC Array.prototype shape."); michael@0: gc::MarkShape(trc, &arrayIteratorProtoShape_, "ForOfPIC ArrayIterator.prototype shape."); michael@0: michael@0: gc::MarkValue(trc, &canonicalIteratorFunc_, "ForOfPIC ArrayValues builtin."); michael@0: gc::MarkValue(trc, &canonicalNextFunc_, "ForOfPIC ArrayIterator.prototype.next builtin."); michael@0: michael@0: // Free all the stubs in the chain. michael@0: while (stubs_) michael@0: removeStub(stubs_, nullptr); michael@0: } michael@0: michael@0: void michael@0: js::ForOfPIC::Chain::sweep(FreeOp *fop) michael@0: { michael@0: // Free all the stubs in the chain. michael@0: while (stubs_) { michael@0: Stub *next = stubs_->next(); michael@0: fop->delete_(stubs_); michael@0: stubs_ = next; michael@0: } michael@0: fop->delete_(this); michael@0: } michael@0: michael@0: static void michael@0: ForOfPIC_finalize(FreeOp *fop, JSObject *obj) michael@0: { michael@0: if (ForOfPIC::Chain *chain = ForOfPIC::fromJSObject(obj)) michael@0: chain->sweep(fop); michael@0: } michael@0: michael@0: static void michael@0: ForOfPIC_traceObject(JSTracer *trc, JSObject *obj) michael@0: { michael@0: if (ForOfPIC::Chain *chain = ForOfPIC::fromJSObject(obj)) michael@0: chain->mark(trc); michael@0: } michael@0: michael@0: const Class ForOfPIC::jsclass = { michael@0: "ForOfPIC", JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS, michael@0: JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, michael@0: JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, ForOfPIC_finalize, michael@0: nullptr, /* call */ michael@0: nullptr, /* hasInstance */ michael@0: nullptr, /* construct */ michael@0: ForOfPIC_traceObject michael@0: }; michael@0: michael@0: /* static */ JSObject * michael@0: js::ForOfPIC::createForOfPICObject(JSContext *cx, Handle global) michael@0: { michael@0: assertSameCompartment(cx, global); michael@0: JSObject *obj = NewObjectWithGivenProto(cx, &ForOfPIC::jsclass, nullptr, global); michael@0: if (!obj) michael@0: return nullptr; michael@0: ForOfPIC::Chain *chain = cx->new_(); michael@0: if (!chain) michael@0: return nullptr; michael@0: obj->setPrivate(chain); michael@0: return obj; michael@0: } michael@0: michael@0: /* static */ js::ForOfPIC::Chain * michael@0: js::ForOfPIC::create(JSContext *cx) michael@0: { michael@0: JS_ASSERT(!cx->global()->getForOfPICObject()); michael@0: Rooted global(cx, cx->global()); michael@0: JSObject *obj = GlobalObject::getOrCreateForOfPICObject(cx, global); michael@0: if (!obj) michael@0: return nullptr; michael@0: return fromJSObject(obj); michael@0: }