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:
Brian Hackett 2015-09-19 10:40:22 -06:00
parent d13141e676
commit 42b1701245
9 changed files with 145 additions and 103 deletions

View File

@ -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)",

View File

@ -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,

View File

@ -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

View File

@ -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);

View File

@ -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());

View File

@ -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);

View File

@ -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
{

View File

@ -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 {

View File

@ -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