зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1376572 - Add a lookup cache for Array[@@species]. r=jandem
This commit is contained in:
Родитель
378f6d9cfb
Коммит
fdfc3f9c34
|
@ -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>;
|
||||
|
|
Загрузка…
Ссылка в новой задаче