зеркало из 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,12 +1078,16 @@ IsArraySpecies(JSContext* cx, HandleObject origArray)
|
||||||
#endif
|
#endif
|
||||||
return true;
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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;
|
Value ctor;
|
||||||
if (!GetPropertyPure(cx, origArray, NameToId(cx->names().constructor), &ctor))
|
if (!GetPropertyPure(cx, origArray, NameToId(cx->names().constructor), &ctor))
|
||||||
return false;
|
return false;
|
||||||
|
@ -4081,3 +4085,151 @@ js::ArrayInfo(JSContext* cx, unsigned argc, Value* vp)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
#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
|
extern bool
|
||||||
IsWrappedArrayConstructor(JSContext* cx, const Value& v, bool* result);
|
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 */
|
} /* namespace js */
|
||||||
|
|
||||||
#endif /* jsarray_h */
|
#endif /* jsarray_h */
|
||||||
|
|
|
@ -68,6 +68,7 @@ JSCompartment::JSCompartment(Zone* zone, const JS::CompartmentOptions& options =
|
||||||
allocationMetadataBuilder(nullptr),
|
allocationMetadataBuilder(nullptr),
|
||||||
lastAnimationTime(0),
|
lastAnimationTime(0),
|
||||||
regExps(),
|
regExps(),
|
||||||
|
arraySpeciesLookup(),
|
||||||
globalWriteBarriered(0),
|
globalWriteBarriered(0),
|
||||||
detachedTypedObjects(0),
|
detachedTypedObjects(0),
|
||||||
objectMetadataState(ImmediateMetadata()),
|
objectMetadataState(ImmediateMetadata()),
|
||||||
|
@ -1083,6 +1084,7 @@ JSCompartment::purge()
|
||||||
newProxyCache.purge();
|
newProxyCache.purge();
|
||||||
objectGroups.purge();
|
objectGroups.purge();
|
||||||
iteratorCache.clearAndShrink();
|
iteratorCache.clearAndShrink();
|
||||||
|
arraySpeciesLookup.purge();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
@ -726,6 +726,8 @@ struct JSCompartment
|
||||||
|
|
||||||
js::RegExpCompartment regExps;
|
js::RegExpCompartment regExps;
|
||||||
|
|
||||||
|
js::ArraySpeciesLookup arraySpeciesLookup;
|
||||||
|
|
||||||
using IteratorCache = js::HashSet<js::PropertyIteratorObject*,
|
using IteratorCache = js::HashSet<js::PropertyIteratorObject*,
|
||||||
js::IteratorHashPolicy,
|
js::IteratorHashPolicy,
|
||||||
js::SystemAllocPolicy>;
|
js::SystemAllocPolicy>;
|
||||||
|
|
Загрузка…
Ссылка в новой задаче