mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-29 07:42:04 +00:00
Bug 1198861 - Improve type barrier logic to avoid unnecessary tests on primitive types, and fix redundant baseline stubs for SETELEM property adds, r=jandem.
This commit is contained in:
parent
d13141e676
commit
42b1701245
@ -3517,7 +3517,7 @@ SetElemAddHasSameShapes(ICSetElem_DenseOrUnboxedArrayAdd* stub, JSObject* obj)
|
||||
return false;
|
||||
if (proto->as<NativeObject>().lastProperty() != nstub->shape(i + 1))
|
||||
return false;
|
||||
proto = obj->getProto();
|
||||
proto = proto->getProto();
|
||||
if (!proto) {
|
||||
if (i != stub->protoChainDepth() - 1)
|
||||
return false;
|
||||
@ -3535,22 +3535,53 @@ DenseOrUnboxedArraySetElemStubExists(JSContext* cx, ICStub::Kind kind,
|
||||
MOZ_ASSERT(kind == ICStub::SetElem_DenseOrUnboxedArray ||
|
||||
kind == ICStub::SetElem_DenseOrUnboxedArrayAdd);
|
||||
|
||||
if (obj->isSingleton())
|
||||
return false;
|
||||
|
||||
for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) {
|
||||
if (kind == ICStub::SetElem_DenseOrUnboxedArray && iter->isSetElem_DenseOrUnboxedArray()) {
|
||||
ICSetElem_DenseOrUnboxedArray* nstub = iter->toSetElem_DenseOrUnboxedArray();
|
||||
if (obj->maybeShape() == nstub->shape() && obj->getGroup(cx) == nstub->group())
|
||||
if (obj->maybeShape() == nstub->shape() && obj->group() == nstub->group())
|
||||
return true;
|
||||
}
|
||||
|
||||
if (kind == ICStub::SetElem_DenseOrUnboxedArrayAdd && iter->isSetElem_DenseOrUnboxedArrayAdd()) {
|
||||
ICSetElem_DenseOrUnboxedArrayAdd* nstub = iter->toSetElem_DenseOrUnboxedArrayAdd();
|
||||
if (obj->getGroup(cx) == nstub->group() && SetElemAddHasSameShapes(nstub, obj))
|
||||
if (obj->group() == nstub->group() && SetElemAddHasSameShapes(nstub, obj))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
RemoveMatchingDenseOrUnboxedArraySetElemAddStub(JSContext* cx,
|
||||
ICSetElem_Fallback* stub, HandleObject obj)
|
||||
{
|
||||
if (obj->isSingleton())
|
||||
return;
|
||||
|
||||
// Before attaching a new stub to add elements to a dense or unboxed array,
|
||||
// remove any other stub with the same group/shape but different prototype
|
||||
// shapes.
|
||||
for (ICStubIterator iter = stub->beginChain(); !iter.atEnd(); iter++) {
|
||||
if (!iter->isSetElem_DenseOrUnboxedArrayAdd())
|
||||
continue;
|
||||
|
||||
ICSetElem_DenseOrUnboxedArrayAdd* nstub = iter->toSetElem_DenseOrUnboxedArrayAdd();
|
||||
if (obj->group() != nstub->group())
|
||||
continue;
|
||||
|
||||
static const size_t MAX_DEPTH = ICSetElem_DenseOrUnboxedArrayAdd::MAX_PROTO_CHAIN_DEPTH;
|
||||
ICSetElem_DenseOrUnboxedArrayAddImpl<MAX_DEPTH>* nostub = nstub->toImplUnchecked<MAX_DEPTH>();
|
||||
|
||||
if (obj->maybeShape() == nostub->shape(0)) {
|
||||
iter.unlink(cx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
TypedArraySetElemStubExists(ICSetElem_Fallback* stub, HandleObject obj, bool expectOOB)
|
||||
{
|
||||
@ -3732,6 +3763,8 @@ DoSetElemFallback(JSContext* cx, BaselineFrame* frame, ICSetElem_Fallback* stub_
|
||||
!DenseOrUnboxedArraySetElemStubExists(cx, ICStub::SetElem_DenseOrUnboxedArrayAdd,
|
||||
stub, obj))
|
||||
{
|
||||
RemoveMatchingDenseOrUnboxedArraySetElemAddStub(cx, stub, obj);
|
||||
|
||||
JitSpew(JitSpew_BaselineIC,
|
||||
" Generating SetElem_DenseOrUnboxedArrayAdd stub "
|
||||
"(shape=%p, group=%p, protoDepth=%u)",
|
||||
|
@ -8459,6 +8459,12 @@ IonBuilder::pushScalarLoadFromTypedObject(MDefinition* obj,
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
BarrierMustTestTypeTag(BarrierKind kind)
|
||||
{
|
||||
return kind == BarrierKind::TypeSet || kind == BarrierKind::TypeTagOnly;
|
||||
}
|
||||
|
||||
bool
|
||||
IonBuilder::pushReferenceLoadFromTypedObject(MDefinition* typedObj,
|
||||
const LinearSum& byteOffset,
|
||||
@ -8494,7 +8500,7 @@ IonBuilder::pushReferenceLoadFromTypedObject(MDefinition* typedObj,
|
||||
// MLoadUnboxedObjectOrNull, which avoids the need to box the result
|
||||
// for a type barrier instruction.
|
||||
MLoadUnboxedObjectOrNull::NullBehavior nullBehavior;
|
||||
if (barrier == BarrierKind::NoBarrier && !observedTypes->hasType(TypeSet::NullType()))
|
||||
if (!observedTypes->hasType(TypeSet::NullType()) && !BarrierMustTestTypeTag(barrier))
|
||||
nullBehavior = MLoadUnboxedObjectOrNull::BailOnNull;
|
||||
else
|
||||
nullBehavior = MLoadUnboxedObjectOrNull::HandleNull;
|
||||
@ -11182,7 +11188,7 @@ IonBuilder::loadUnboxedValue(MDefinition* elements, size_t elementsOffset,
|
||||
|
||||
case JSVAL_TYPE_OBJECT: {
|
||||
MLoadUnboxedObjectOrNull::NullBehavior nullBehavior;
|
||||
if (types->hasType(TypeSet::NullType()) || barrier != BarrierKind::NoBarrier)
|
||||
if (types->hasType(TypeSet::NullType()) || BarrierMustTestTypeTag(barrier))
|
||||
nullBehavior = MLoadUnboxedObjectOrNull::HandleNull;
|
||||
else
|
||||
nullBehavior = MLoadUnboxedObjectOrNull::NullNotPossible;
|
||||
@ -11579,10 +11585,7 @@ IonBuilder::getPropTryCache(bool* emitted, MDefinition* obj, PropertyName* name,
|
||||
if (barrier != BarrierKind::TypeSet) {
|
||||
BarrierKind protoBarrier =
|
||||
PropertyReadOnPrototypeNeedsTypeBarrier(this, obj, name, types);
|
||||
if (protoBarrier != BarrierKind::NoBarrier) {
|
||||
MOZ_ASSERT(barrier <= protoBarrier);
|
||||
barrier = protoBarrier;
|
||||
}
|
||||
barrier = CombineBarrierKinds(barrier, protoBarrier);
|
||||
}
|
||||
|
||||
MGetPropertyCache* load = MGetPropertyCache::New(alloc(), obj, name,
|
||||
|
@ -745,11 +745,34 @@ enum class BarrierKind : uint32_t {
|
||||
// Specific object types don't have to be checked.
|
||||
TypeTagOnly,
|
||||
|
||||
// The barrier only has to check that object values are in the type set.
|
||||
// Non-object types don't have to be checked.
|
||||
ObjectTypesOnly,
|
||||
|
||||
// Check if the value is in the TypeSet, including the object type if it's
|
||||
// an object.
|
||||
TypeSet
|
||||
};
|
||||
|
||||
static inline BarrierKind
|
||||
CombineBarrierKinds(BarrierKind first, BarrierKind second)
|
||||
{
|
||||
// Barrier kinds form the following lattice:
|
||||
//
|
||||
// TypeSet
|
||||
// | |
|
||||
// TypeTagOnly ObjectTypesOnly
|
||||
// | |
|
||||
// NoBarrier
|
||||
//
|
||||
// This function computes the least upper bound of two barrier kinds.
|
||||
if (first == BarrierKind::NoBarrier || first == second)
|
||||
return second;
|
||||
if (second == BarrierKind::NoBarrier)
|
||||
return first;
|
||||
return BarrierKind::TypeSet;
|
||||
}
|
||||
|
||||
} // namespace jit
|
||||
} // namespace js
|
||||
|
||||
|
@ -4985,6 +4985,12 @@ PropertyReadNeedsTypeBarrier(CompilerConstraintList* constraints,
|
||||
property.freeze(constraints);
|
||||
return BarrierKind::TypeTagOnly;
|
||||
}
|
||||
// If all possible primitives have been observed, we don't have to
|
||||
// guard on those primitives.
|
||||
if (property.maybeTypes()->primitivesAreSubset(observed)) {
|
||||
property.freeze(constraints);
|
||||
return BarrierKind::ObjectTypesOnly;
|
||||
}
|
||||
return BarrierKind::TypeSet;
|
||||
}
|
||||
}
|
||||
@ -5006,34 +5012,6 @@ PropertyReadNeedsTypeBarrier(CompilerConstraintList* constraints,
|
||||
return BarrierKind::NoBarrier;
|
||||
}
|
||||
|
||||
static bool
|
||||
ObjectSubsumes(TypeSet::ObjectKey* first, TypeSet::ObjectKey* second)
|
||||
{
|
||||
if (first->isSingleton() ||
|
||||
second->isSingleton() ||
|
||||
first->clasp() != second->clasp() ||
|
||||
first->unknownProperties() ||
|
||||
second->unknownProperties())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (first->clasp() == &ArrayObject::class_) {
|
||||
HeapTypeSetKey firstElements = first->property(JSID_VOID);
|
||||
HeapTypeSetKey secondElements = second->property(JSID_VOID);
|
||||
|
||||
return firstElements.maybeTypes() && secondElements.maybeTypes() &&
|
||||
firstElements.maybeTypes()->equals(secondElements.maybeTypes());
|
||||
}
|
||||
|
||||
if (first->clasp() == &UnboxedArrayObject::class_) {
|
||||
return first->group()->unboxedLayout().elementType() ==
|
||||
second->group()->unboxedLayout().elementType();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
BarrierKind
|
||||
jit::PropertyReadNeedsTypeBarrier(JSContext* propertycx,
|
||||
CompilerConstraintList* constraints,
|
||||
@ -5078,30 +5056,6 @@ jit::PropertyReadNeedsTypeBarrier(JSContext* propertycx,
|
||||
}
|
||||
}
|
||||
|
||||
// If any objects which could be observed are similar to ones that have
|
||||
// already been observed, add them to the observed type set.
|
||||
if (!key->unknownProperties()) {
|
||||
HeapTypeSetKey property = key->property(name ? NameToId(name) : JSID_VOID);
|
||||
|
||||
if (property.maybeTypes() && !property.maybeTypes()->unknownObject()) {
|
||||
for (size_t i = 0; i < property.maybeTypes()->getObjectCount(); i++) {
|
||||
TypeSet::ObjectKey* key = property.maybeTypes()->getObject(i);
|
||||
if (!key || observed->unknownObject())
|
||||
continue;
|
||||
|
||||
for (size_t j = 0; j < observed->getObjectCount(); j++) {
|
||||
TypeSet::ObjectKey* observedKey = observed->getObject(j);
|
||||
if (observedKey && ObjectSubsumes(observedKey, key)) {
|
||||
// Note: the return value here is ignored.
|
||||
observed->addType(TypeSet::ObjectType(key),
|
||||
GetJitContext()->temp->lifoAlloc());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PropertyReadNeedsTypeBarrier(constraints, key, name, observed);
|
||||
}
|
||||
|
||||
@ -5125,15 +5079,9 @@ jit::PropertyReadNeedsTypeBarrier(JSContext* propertycx,
|
||||
if (TypeSet::ObjectKey* key = types->getObject(i)) {
|
||||
BarrierKind kind = PropertyReadNeedsTypeBarrier(propertycx, constraints, key, name,
|
||||
observed, updateObserved);
|
||||
if (kind == BarrierKind::TypeSet)
|
||||
res = CombineBarrierKinds(res, kind);
|
||||
if (res == BarrierKind::TypeSet)
|
||||
return BarrierKind::TypeSet;
|
||||
|
||||
if (kind == BarrierKind::TypeTagOnly) {
|
||||
MOZ_ASSERT(res == BarrierKind::NoBarrier || res == BarrierKind::TypeTagOnly);
|
||||
res = BarrierKind::TypeTagOnly;
|
||||
} else {
|
||||
MOZ_ASSERT(kind == BarrierKind::NoBarrier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5167,15 +5115,9 @@ jit::PropertyReadOnPrototypeNeedsTypeBarrier(IonBuilder* builder,
|
||||
key = TypeSet::ObjectKey::get(proto);
|
||||
BarrierKind kind = PropertyReadNeedsTypeBarrier(builder->constraints(),
|
||||
key, name, observed);
|
||||
if (kind == BarrierKind::TypeSet)
|
||||
res = CombineBarrierKinds(res, kind);
|
||||
if (res == BarrierKind::TypeSet)
|
||||
return BarrierKind::TypeSet;
|
||||
|
||||
if (kind == BarrierKind::TypeTagOnly) {
|
||||
MOZ_ASSERT(res == BarrierKind::NoBarrier || res == BarrierKind::TypeTagOnly);
|
||||
res = BarrierKind::TypeTagOnly;
|
||||
} else {
|
||||
MOZ_ASSERT(kind == BarrierKind::NoBarrier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5400,8 +5342,12 @@ TryAddTypeBarrierForWrite(TempAllocator& alloc, CompilerConstraintList* constrai
|
||||
// If all possible objects can be stored without a barrier, we don't have to
|
||||
// guard on the specific object types.
|
||||
BarrierKind kind = BarrierKind::TypeSet;
|
||||
if ((*pvalue)->resultTypeSet() && (*pvalue)->resultTypeSet()->objectsAreSubset(types))
|
||||
kind = BarrierKind::TypeTagOnly;
|
||||
if ((*pvalue)->resultTypeSet()) {
|
||||
if ((*pvalue)->resultTypeSet()->objectsAreSubset(types))
|
||||
kind = BarrierKind::TypeTagOnly;
|
||||
else if ((*pvalue)->resultTypeSet()->primitivesAreSubset(types))
|
||||
kind = BarrierKind::ObjectTypesOnly;
|
||||
}
|
||||
|
||||
MInstruction* ins = MMonitorTypes::New(alloc, *pvalue, types, kind);
|
||||
current->add(ins);
|
||||
|
@ -12387,7 +12387,7 @@ class MTypeBarrier
|
||||
: MUnaryInstruction(def),
|
||||
barrierKind_(kind)
|
||||
{
|
||||
MOZ_ASSERT(kind == BarrierKind::TypeTagOnly || kind == BarrierKind::TypeSet);
|
||||
MOZ_ASSERT(kind != BarrierKind::NoBarrier);
|
||||
|
||||
MOZ_ASSERT(!types->unknown());
|
||||
setResultType(types->getKnownMIRType());
|
||||
@ -12447,7 +12447,7 @@ class MMonitorTypes
|
||||
typeSet_(types),
|
||||
barrierKind_(kind)
|
||||
{
|
||||
MOZ_ASSERT(kind == BarrierKind::TypeTagOnly || kind == BarrierKind::TypeSet);
|
||||
MOZ_ASSERT(kind != BarrierKind::NoBarrier);
|
||||
|
||||
setGuard();
|
||||
MOZ_ASSERT(!types->unknown());
|
||||
|
@ -35,7 +35,7 @@ template <typename Source> void
|
||||
MacroAssembler::guardTypeSet(const Source& address, const TypeSet* types, BarrierKind kind,
|
||||
Register scratch, Label* miss)
|
||||
{
|
||||
MOZ_ASSERT(kind == BarrierKind::TypeTagOnly || kind == BarrierKind::TypeSet);
|
||||
MOZ_ASSERT(kind != BarrierKind::NoBarrier);
|
||||
MOZ_ASSERT(!types->unknown());
|
||||
|
||||
Label matched;
|
||||
@ -50,24 +50,49 @@ MacroAssembler::guardTypeSet(const Source& address, const TypeSet* types, Barrie
|
||||
TypeSet::AnyObjectType()
|
||||
};
|
||||
|
||||
// The double type also implies Int32.
|
||||
// So replace the int32 test with the double one.
|
||||
if (types->hasType(TypeSet::DoubleType())) {
|
||||
MOZ_ASSERT(types->hasType(TypeSet::Int32Type()));
|
||||
tests[0] = TypeSet::DoubleType();
|
||||
}
|
||||
|
||||
Register tag = extractTag(address, scratch);
|
||||
|
||||
// Emit all typed tests.
|
||||
BranchType lastBranch;
|
||||
for (size_t i = 0; i < mozilla::ArrayLength(tests); i++) {
|
||||
if (!types->hasType(tests[i]))
|
||||
continue;
|
||||
|
||||
if (lastBranch.isInitialized())
|
||||
lastBranch.emit(*this);
|
||||
lastBranch = BranchType(Equal, tag, tests[i], &matched);
|
||||
if (kind != BarrierKind::ObjectTypesOnly) {
|
||||
// The double type also implies Int32.
|
||||
// So replace the int32 test with the double one.
|
||||
if (types->hasType(TypeSet::DoubleType())) {
|
||||
MOZ_ASSERT(types->hasType(TypeSet::Int32Type()));
|
||||
tests[0] = TypeSet::DoubleType();
|
||||
}
|
||||
|
||||
// Emit all typed tests.
|
||||
for (size_t i = 0; i < mozilla::ArrayLength(tests); i++) {
|
||||
if (!types->hasType(tests[i]))
|
||||
continue;
|
||||
|
||||
if (lastBranch.isInitialized())
|
||||
lastBranch.emit(*this);
|
||||
lastBranch = BranchType(Equal, tag, tests[i], &matched);
|
||||
}
|
||||
} else {
|
||||
#ifdef DEBUG
|
||||
// Any non-object will be considered to match the type set. Make sure
|
||||
// such values encountered are actually in the type set.
|
||||
|
||||
if (types->hasType(TypeSet::DoubleType())) {
|
||||
MOZ_ASSERT(types->hasType(TypeSet::Int32Type()));
|
||||
tests[0] = TypeSet::DoubleType();
|
||||
}
|
||||
|
||||
Label matchedPrimitive;
|
||||
for (size_t i = 0; i < mozilla::ArrayLength(tests); i++) {
|
||||
if (!types->hasType(tests[i]))
|
||||
continue;
|
||||
BranchType branch(Equal, tag, tests[i], &matchedPrimitive);
|
||||
branch.emit(*this);
|
||||
}
|
||||
branchTestObject(Equal, tag, &matchedPrimitive);
|
||||
|
||||
assumeUnreachable("Unexpected primitive type");
|
||||
|
||||
bind(&matchedPrimitive);
|
||||
#endif
|
||||
}
|
||||
|
||||
// If this is the last check, invert the last branch.
|
||||
@ -90,7 +115,7 @@ MacroAssembler::guardTypeSet(const Source& address, const TypeSet* types, Barrie
|
||||
|
||||
// Test specific objects.
|
||||
MOZ_ASSERT(scratch != InvalidReg);
|
||||
branchTestObject(NotEqual, tag, miss);
|
||||
branchTestObject(NotEqual, tag, kind == BarrierKind::ObjectTypesOnly ? &matched : miss);
|
||||
if (kind != BarrierKind::TypeTagOnly) {
|
||||
Register obj = extractObject(address, scratch);
|
||||
guardObjectType(obj, types, scratch, miss);
|
||||
|
@ -398,6 +398,13 @@ TypeSet::objectsAreSubset(TypeSet* other)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TypeSet::primitivesAreSubset(TypeSet* other)
|
||||
{
|
||||
uint32_t primitiveFlags = baseFlags() & TYPE_FLAG_PRIMITIVE;
|
||||
return (primitiveFlags & other->baseFlags()) == primitiveFlags;
|
||||
}
|
||||
|
||||
bool
|
||||
TypeSet::isSubset(const TypeSet* other) const
|
||||
{
|
||||
|
@ -66,7 +66,7 @@ enum : uint32_t {
|
||||
/* Mask containing all primitives */
|
||||
TYPE_FLAG_PRIMITIVE = TYPE_FLAG_UNDEFINED | TYPE_FLAG_NULL | TYPE_FLAG_BOOLEAN |
|
||||
TYPE_FLAG_INT32 | TYPE_FLAG_DOUBLE | TYPE_FLAG_STRING |
|
||||
TYPE_FLAG_SYMBOL,
|
||||
TYPE_FLAG_SYMBOL | TYPE_FLAG_LAZYARGS,
|
||||
|
||||
/* Mask/shift for the number of objects in objectSet */
|
||||
TYPE_FLAG_OBJECT_COUNT_MASK = 0x3e00,
|
||||
@ -483,11 +483,10 @@ class TypeSet
|
||||
*/
|
||||
bool isSubset(const TypeSet* other) const;
|
||||
|
||||
/*
|
||||
* Get whether the objects in this TypeSet are a subset of the objects
|
||||
* in other.
|
||||
*/
|
||||
// Return whether this is a subset of other, ignoring primitive or object
|
||||
// types respectively.
|
||||
bool objectsAreSubset(TypeSet* other);
|
||||
bool primitivesAreSubset(TypeSet* other);
|
||||
|
||||
/* Whether this TypeSet contains exactly the same types as other. */
|
||||
bool equals(const TypeSet* other) const {
|
||||
|
@ -1974,6 +1974,12 @@ js::TryConvertToUnboxedLayout(ExclusiveContext* cx, Shape* templateShape,
|
||||
// element type for the objects.
|
||||
if (UnboxedTypeSize(elementType) == 0)
|
||||
return true;
|
||||
|
||||
// Don't use an unboxed representation if objects in the group have
|
||||
// ever had holes in the past. Even if they have been filled in, future
|
||||
// objects that are created might be given holes as well.
|
||||
if (group->flags() & OBJECT_FLAG_NON_PACKED)
|
||||
return true;
|
||||
} else {
|
||||
if (objectCount <= 1) {
|
||||
// If only one of the objects has been created, it is more likely
|
||||
|
Loading…
Reference in New Issue
Block a user