blob: 92572aab6f0b869f35f26bc29240b6842f850e20 [file] [log] [blame]
/* -*- 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>());
// 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.
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)
{
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);
}
const Class ForOfPIC::jsclass = {
"ForOfPIC", JSCLASS_HAS_PRIVATE,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, ForOfPIC_finalize,
nullptr, /* call */
nullptr, /* hasInstance */
nullptr, /* construct */
ForOfPIC_traceObject
};
/* static */ NativeObject*
js::ForOfPIC::createForOfPICObject(JSContext* cx, Handle<GlobalObject*> global)
{
assertSameCompartment(cx, global);
NativeObject* obj = NewNativeObjectWithGivenProto(cx, &ForOfPIC::jsclass, 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);
}