Bug 1376572 - Add a lookup cache for Array[@@species]. r=jandem

This commit is contained in:
André Bargull 2017-12-05 04:25:54 -08:00
Родитель 378f6d9cfb
Коммит fdfc3f9c34
4 изменённых файлов: 239 добавлений и 4 удалений

Просмотреть файл

@ -1078,11 +1078,15 @@ IsArraySpecies(JSContext* cx, HandleObject origArray)
#endif
return true;
}
} else {
return false;
}
// 9.4.2.3 Step 4. Non-array objects always use the default constructor.
if (!origArray->is<ArrayObject>())
return true;
}
if (cx->compartment()->arraySpeciesLookup.tryOptimizeArray(cx, &origArray->as<ArrayObject>()))
return true;
Value ctor;
if (!GetPropertyPure(cx, origArray, NameToId(cx->names().constructor), &ctor))
@ -4081,3 +4085,151 @@ js::ArrayInfo(JSContext* cx, unsigned argc, Value* vp)
return true;
}
#endif
void
js::ArraySpeciesLookup::initialize(JSContext* cx)
{
MOZ_ASSERT(state_ == State::Uninitialized);
// Get the canonical Array.prototype.
NativeObject* arrayProto = cx->global()->maybeGetArrayPrototype();
// Leave the cache uninitialized if the Array class itself is not yet
// initialized.
if (!arrayProto)
return;
// Get the canonical Array constructor.
const Value& arrayCtorValue = cx->global()->getConstructor(JSProto_Array);
MOZ_ASSERT(arrayCtorValue.isObject(),
"The Array constructor is initialized iff Array.prototype is initialized");
JSFunction* arrayCtor = &arrayCtorValue.toObject().as<JSFunction>();
// Shortcut returns below means Array[@@species] will never be
// optimizable, set to disabled now, and clear it later when we succeed.
state_ = State::Disabled;
// Look up Array.prototype[@@iterator] and ensure it's a data property.
Shape* ctorShape = arrayProto->lookup(cx, NameToId(cx->names().constructor));
if (!ctorShape || !ctorShape->isDataProperty())
return;
// Get the referred value, and ensure it holds the canonical Array
// constructor.
JSFunction* ctorFun;
if (!IsFunctionObject(arrayProto->getSlot(ctorShape->slot()), &ctorFun))
return;
if (ctorFun != arrayCtor)
return;
// Look up the '@@species' value on Array
Shape* speciesShape = arrayCtor->lookup(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().species));
if (!speciesShape || !speciesShape->hasGetterValue())
return;
// Get the referred value, ensure it holds the canonical Array[@@species]
// function.
JSFunction* speciesFun;
if (!IsFunctionObject(speciesShape->getterValue(), &speciesFun))
return;
if (!IsSelfHostedFunctionWithName(speciesFun, cx->names().ArraySpecies))
return;
// Store raw pointers below. This is okay to do here, because all objects
// are in the tenured heap.
MOZ_ASSERT(!IsInsideNursery(arrayProto));
MOZ_ASSERT(!IsInsideNursery(arrayCtor));
MOZ_ASSERT(!IsInsideNursery(arrayCtor->lastProperty()));
MOZ_ASSERT(!IsInsideNursery(speciesShape));
MOZ_ASSERT(!IsInsideNursery(speciesFun));
MOZ_ASSERT(!IsInsideNursery(arrayProto->lastProperty()));
state_ = State::Initialized;
arrayProto_ = arrayProto;
arrayConstructor_ = arrayCtor;
arrayConstructorShape_ = arrayCtor->lastProperty();
#ifdef DEBUG
arraySpeciesShape_ = speciesShape;
canonicalSpeciesFunc_ = speciesFun;
#endif
arrayProtoShape_ = arrayProto->lastProperty();
arrayProtoConstructorSlot_ = ctorShape->slot();
}
void
js::ArraySpeciesLookup::reset()
{
state_ = State::Uninitialized;
arrayProto_ = nullptr;
arrayConstructor_ = nullptr;
arrayConstructorShape_ = nullptr;
#ifdef DEBUG
arraySpeciesShape_ = nullptr;
canonicalSpeciesFunc_ = nullptr;
#endif
arrayProtoShape_ = nullptr;
arrayProtoConstructorSlot_ = -1;
}
bool
js::ArraySpeciesLookup::isArrayStateStillSane()
{
MOZ_ASSERT(state_ == State::Initialized);
// Ensure that Array.prototype still has the expected shape.
if (arrayProto_->lastProperty() != arrayProtoShape_)
return false;
// Ensure that Array.prototype.constructor contains the canonical Array
// constructor function.
if (arrayProto_->getSlot(arrayProtoConstructorSlot_) != ObjectValue(*arrayConstructor_))
return false;
// Ensure that Array still has the expected shape.
if (arrayConstructor_->lastProperty() != arrayConstructorShape_)
return false;
// Ensure the species getter contains the canonical @@species function.
// Note: This is currently guaranteed to be always true, because modifying
// the getter property implies a new shape is generated. If this ever
// changes, convert this assertion into an if-statement.
MOZ_ASSERT(arraySpeciesShape_->getterObject() == canonicalSpeciesFunc_);
return true;
}
bool
js::ArraySpeciesLookup::tryOptimizeArray(JSContext* cx, ArrayObject* array)
{
if (state_ == State::Uninitialized) {
// If the cache is not initialized, initialize it.
initialize(cx);
} else if (state_ == State::Initialized && !isArrayStateStillSane()) {
// Otherwise, if the array state is no longer sane, reinitialize.
reset();
initialize(cx);
}
// If the cache is disabled or still uninitialized, don't bother trying to
// optimize.
if (state_ != State::Initialized)
return false;
// By the time we get here, we should have a sane array state.
MOZ_ASSERT(isArrayStateStillSane());
// Ensure |array|'s prototype is the actual Array.prototype.
if (array->staticPrototype() != arrayProto_)
return false;
// Ensure |array| doesn't define any own properties besides its
// non-deletable "length" property. This serves as a quick check to make
// sure |array| doesn't define an own "constructor" property which may
// shadow Array.prototype.constructor.
Shape* shape = array->shape();
if (shape->previous() && !shape->previous()->isEmptyShape())
return false;
MOZ_ASSERT(JSID_IS_ATOM(shape->propidRaw(), cx->names().length));
return true;
}

Просмотреть файл

@ -199,6 +199,85 @@ array_construct(JSContext* cx, unsigned argc, Value* vp);
extern bool
IsWrappedArrayConstructor(JSContext* cx, const Value& v, bool* result);
class MOZ_NON_TEMPORARY_CLASS ArraySpeciesLookup final
{
/*
* An ArraySpeciesLookup holds the following:
*
* Array.prototype (arrayProto_)
* To ensure that the incoming array has the standard proto.
*
* Array.prototype's shape (arrayProtoShape_)
* To ensure that Array.prototype has not been modified.
*
* Array (arrayConstructor_)
* Array's shape (arrayConstructorShape_)
* To ensure that Array has not been modified.
*
* Array.prototype's slot number for constructor (arrayProtoConstructorSlot_)
* To quickly retrieve and ensure that the Array constructor
* stored in the slot has not changed.
*
* Array's shape for the @@species getter. (arraySpeciesShape_)
* Array's canonical value for @@species (canonicalSpeciesFunc_)
* To quickly retrieve and ensure that the @@species getter for Array
* has not changed.
*/
// Pointer to canonical Array.prototype and Array.
NativeObject* arrayProto_;
NativeObject* arrayConstructor_;
// Shape of matching Array, and slot containing the @@species
// property, and the canonical value.
Shape* arrayConstructorShape_;
#ifdef DEBUG
Shape* arraySpeciesShape_;
JSFunction* canonicalSpeciesFunc_;
#endif
// Shape of matching Array.prototype object, and slot containing the
// constructor for it.
Shape* arrayProtoShape_;
uint32_t arrayProtoConstructorSlot_;
enum class State : uint8_t {
// Flags marking the lazy initialization of the above fields.
Uninitialized,
Initialized,
// The disabled flag is set when we don't want to try optimizing
// anymore because core objects were changed.
Disabled
};
State state_;
// Initialize the internal fields.
void initialize(JSContext* cx);
// Reset the cache.
void reset();
// Check if the global array-related objects have not been messed with
// in a way that would disable this cache.
bool isArrayStateStillSane();
public:
ArraySpeciesLookup() {
reset();
}
// Try to optimize the @@species lookup for an array.
bool tryOptimizeArray(JSContext* cx, ArrayObject* array);
// Purge the cache and all info associated with it.
void purge() {
if (state_ == State::Initialized)
reset();
}
};
} /* namespace js */
#endif /* jsarray_h */

Просмотреть файл

@ -68,6 +68,7 @@ JSCompartment::JSCompartment(Zone* zone, const JS::CompartmentOptions& options =
allocationMetadataBuilder(nullptr),
lastAnimationTime(0),
regExps(),
arraySpeciesLookup(),
globalWriteBarriered(0),
detachedTypedObjects(0),
objectMetadataState(ImmediateMetadata()),
@ -1083,6 +1084,7 @@ JSCompartment::purge()
newProxyCache.purge();
objectGroups.purge();
iteratorCache.clearAndShrink();
arraySpeciesLookup.purge();
}
void

Просмотреть файл

@ -726,6 +726,8 @@ struct JSCompartment
js::RegExpCompartment regExps;
js::ArraySpeciesLookup arraySpeciesLookup;
using IteratorCache = js::HashSet<js::PropertyIteratorObject*,
js::IteratorHashPolicy,
js::SystemAllocPolicy>;