Bug 1133254 - Improve type information and Ion compilation when dealing with converted unboxed objects, r=jandem.

This commit is contained in:
Brian Hackett 2015-02-21 18:52:50 -06:00
parent 30073f0d31
commit 9e50b6836a
22 changed files with 433 additions and 76 deletions

View File

@ -110,6 +110,8 @@ namespace JS {
"not definitely a TypedObject struct") \
_(NotUnboxed, \
"not definitely an unboxed object") \
_(UnboxedConvertedToNative, \
"unboxed object may have been converted") \
_(StructNoField, \
"struct doesn't definitely have field") \
_(InconsistentFieldType, \

View File

@ -1441,6 +1441,9 @@ ScanObjectGroup(GCMarker *gcmarker, ObjectGroup *group)
if (group->maybeUnboxedLayout())
group->unboxedLayout().trace(gcmarker);
if (ObjectGroup *unboxedGroup = group->maybeOriginalUnboxedGroup())
PushMarkStack(gcmarker, unboxedGroup);
if (TypeDescr *descr = group->maybeTypeDescr())
PushMarkStack(gcmarker, descr);
@ -1469,6 +1472,11 @@ gc::MarkChildren(JSTracer *trc, ObjectGroup *group)
if (group->maybeUnboxedLayout())
group->unboxedLayout().trace(trc);
if (ObjectGroup *unboxedGroup = group->maybeOriginalUnboxedGroup()) {
MarkObjectGroupUnbarriered(trc, &unboxedGroup, "group_original_unboxed_group");
group->setOriginalUnboxedGroup(unboxedGroup);
}
if (JSObject *descr = group->maybeTypeDescr()) {
MarkObjectUnbarriered(trc, &descr, "group_type_descr");
group->setTypeDescr(&descr->as<TypeDescr>());

View File

@ -79,16 +79,31 @@ SetElemICInspector::sawTypedArrayWrite() const
return false;
}
template <typename S, typename T>
static bool
VectorAppendNoDuplicate(S &list, T value)
{
for (size_t i = 0; i < list.length(); i++) {
if (list[i] == value)
return true;
}
return list.append(value);
}
bool
BaselineInspector::maybeInfoForPropertyOp(jsbytecode *pc,
ShapeVector &nativeShapes,
ObjectGroupVector &unboxedGroups)
ObjectGroupVector &unboxedGroups,
ObjectGroupVector &convertUnboxedGroups)
{
// Return lists of native shapes and unboxed objects seen by the baseline
// IC for the current op. Empty lists indicate no shapes/types are known,
// or there was an uncacheable access.
// or there was an uncacheable access. convertUnboxedGroups is used for
// unboxed object groups which have been seen, but have had instances
// converted to native objects and should be eagerly converted by Ion.
MOZ_ASSERT(nativeShapes.empty());
MOZ_ASSERT(unboxedGroups.empty());
MOZ_ASSERT(convertUnboxedGroups.empty());
if (!hasBaselineScript())
return true;
@ -114,27 +129,18 @@ BaselineInspector::maybeInfoForPropertyOp(jsbytecode *pc,
return true;
}
// Don't add the same shape/group twice (this can happen if there are
// multiple SetProp_Native stubs with different ObjectGroups).
if (group && group->unboxedLayout().nativeGroup()) {
if (!VectorAppendNoDuplicate(convertUnboxedGroups, group))
return false;
shape = group->unboxedLayout().nativeShape();
group = nullptr;
}
if (shape) {
bool found = false;
for (size_t i = 0; i < nativeShapes.length(); i++) {
if (nativeShapes[i] == shape) {
found = true;
break;
}
}
if (!found && !nativeShapes.append(shape))
if (!VectorAppendNoDuplicate(nativeShapes, shape))
return false;
} else {
bool found = false;
for (size_t i = 0; i < unboxedGroups.length(); i++) {
if (unboxedGroups[i] == group) {
found = true;
break;
}
}
if (!found && !unboxedGroups.append(group))
if (!VectorAppendNoDuplicate(unboxedGroups, group))
return false;
}

View File

@ -96,7 +96,8 @@ class BaselineInspector
typedef Vector<ObjectGroup *, 4, JitAllocPolicy> ObjectGroupVector;
bool maybeInfoForPropertyOp(jsbytecode *pc,
ShapeVector &nativeShapes,
ObjectGroupVector &unboxedGroups);
ObjectGroupVector &unboxedGroups,
ObjectGroupVector &convertUnboxedGroups);
SetElemICInspector setElemICInspector(jsbytecode *pc) {
return makeICInspector<SetElemICInspector>(pc, ICStub::SetElem_Fallback);

View File

@ -6638,6 +6638,23 @@ CodeGenerator::visitStoreUnboxedPointer(LStoreUnboxedPointer *lir)
}
}
typedef bool (*ConvertUnboxedObjectToNativeFn)(JSContext *, JSObject *);
static const VMFunction ConvertUnboxedObjectToNativeInfo =
FunctionInfo<ConvertUnboxedObjectToNativeFn>(UnboxedPlainObject::convertToNative);
void
CodeGenerator::visitConvertUnboxedObjectToNative(LConvertUnboxedObjectToNative *lir)
{
Register object = ToRegister(lir->getOperand(0));
OutOfLineCode *ool = oolCallVM(ConvertUnboxedObjectToNativeInfo, lir,
(ArgList(), object), StoreNothing());
masm.branchPtr(Assembler::Equal, Address(object, JSObject::offsetOfGroup()),
ImmGCPtr(lir->mir()->group()), ool->entry());
masm.bind(ool->rejoin());
}
typedef bool (*ArrayPopShiftFn)(JSContext *, HandleObject, MutableHandleValue);
static const VMFunction ArrayPopDenseInfo = FunctionInfo<ArrayPopShiftFn>(jit::ArrayPopDense);
static const VMFunction ArrayShiftDenseInfo = FunctionInfo<ArrayPopShiftFn>(jit::ArrayShiftDense);

View File

@ -251,6 +251,7 @@ class CodeGenerator : public CodeGeneratorSpecific
void visitStoreElementHoleT(LStoreElementHoleT *lir);
void visitStoreElementHoleV(LStoreElementHoleV *lir);
void visitStoreUnboxedPointer(LStoreUnboxedPointer *lir);
void visitConvertUnboxedObjectToNative(LConvertUnboxedObjectToNative *lir);
void emitArrayPopShift(LInstruction *lir, const MArrayPopShift *mir, Register obj,
Register elementsTemp, Register lengthTemp, TypedOrValueRegister out);
void visitArrayPopShiftV(LArrayPopShiftV *lir);

View File

@ -2608,6 +2608,8 @@ PassthroughOperand(MDefinition *def)
return def->toConvertElementsToDoubles()->elements();
if (def->isMaybeCopyElementsForWrite())
return def->toMaybeCopyElementsForWrite()->object();
if (def->isConvertUnboxedObjectToNative())
return def->toConvertUnboxedObjectToNative()->object();
return nullptr;
}

View File

@ -9186,7 +9186,8 @@ IonBuilder::jsop_rest()
}
uint32_t
IonBuilder::getDefiniteSlot(TemporaryTypeSet *types, PropertyName *name)
IonBuilder::getDefiniteSlot(TemporaryTypeSet *types, PropertyName *name, uint32_t *pnfixed,
BaselineInspector::ObjectGroupVector &convertUnboxedGroups)
{
if (!types || types->unknownObject()) {
trackOptimizationOutcome(TrackedOutcome::NoTypeInfo);
@ -9235,6 +9236,17 @@ IonBuilder::getDefiniteSlot(TemporaryTypeSet *types, PropertyName *name)
return UINT32_MAX;
}
// If we encounter a group for an unboxed property which has a
// corresponding native group, look for a definite slot in that native
// group, and force conversion of incoming objects to the native group.
if (key->isGroup() && key->group()->maybeUnboxedLayout()) {
if (ObjectGroup *nativeGroup = key->group()->unboxedLayout().nativeGroup()) {
if (!convertUnboxedGroups.append(key->group()))
CrashAtUnhandlableOOM("IonBuilder::getDefiniteSlot");
key = TypeSet::ObjectKey::get(nativeGroup);
}
}
HeapTypeSetKey property = key->property(NameToId(name));
if (!property.maybeTypes() ||
!property.maybeTypes()->definiteProperty() ||
@ -9244,10 +9256,18 @@ IonBuilder::getDefiniteSlot(TemporaryTypeSet *types, PropertyName *name)
return UINT32_MAX;
}
// Definite slots will always be fixed slots when they are in the
// allowable range for fixed slots, except for objects which were
// converted from unboxed objects and have a smaller allocation size.
size_t nfixed = NativeObject::MAX_FIXED_SLOTS;
if (ObjectGroup *group = key->group()->maybeOriginalUnboxedGroup())
nfixed = gc::GetGCKindSlots(group->unboxedLayout().getAllocKind());
uint32_t propertySlot = property.maybeTypes()->definiteSlot();
if (slot == UINT32_MAX) {
slot = propertySlot;
} else if (slot != propertySlot) {
*pnfixed = nfixed;
} else if (slot != propertySlot || nfixed != *pnfixed) {
trackOptimizationOutcome(TrackedOutcome::InconsistentFixedSlot);
return UINT32_MAX;
}
@ -9293,6 +9313,13 @@ IonBuilder::getUnboxedOffset(TemporaryTypeSet *types, PropertyName *name, JSValu
return UINT32_MAX;
}
if (layout->nativeGroup()) {
trackOptimizationOutcome(TrackedOutcome::UnboxedConvertedToNative);
return UINT32_MAX;
}
key->watchStateChangeForUnboxedConvertedToNative(constraints());
if (offset == UINT32_MAX) {
offset = property->offset;
*punboxedType = property->type;
@ -10028,13 +10055,27 @@ IonBuilder::getPropTryComplexPropOfTypedObject(bool *emitted,
fieldPrediction, fieldTypeObj);
}
MDefinition *
IonBuilder::convertUnboxedObjects(MDefinition *obj,
const BaselineInspector::ObjectGroupVector &list)
{
for (size_t i = 0; i < list.length(); i++) {
obj = MConvertUnboxedObjectToNative::New(alloc(), obj, list[i]);
current->add(obj->toInstruction());
}
return obj;
}
bool
IonBuilder::getPropTryDefiniteSlot(bool *emitted, MDefinition *obj, PropertyName *name,
BarrierKind barrier, TemporaryTypeSet *types)
{
MOZ_ASSERT(*emitted == false);
uint32_t slot = getDefiniteSlot(obj->resultTypeSet(), name);
BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc());
uint32_t nfixed;
uint32_t slot = getDefiniteSlot(obj->resultTypeSet(), name, &nfixed, convertUnboxedGroups);
if (slot == UINT32_MAX)
return true;
@ -10044,14 +10085,16 @@ IonBuilder::getPropTryDefiniteSlot(bool *emitted, MDefinition *obj, PropertyName
obj = guard;
}
obj = convertUnboxedObjects(obj, convertUnboxedGroups);
MInstruction *load;
if (slot < NativeObject::MAX_FIXED_SLOTS) {
if (slot < nfixed) {
load = MLoadFixedSlot::New(alloc(), obj, slot);
} else {
MInstruction *slots = MSlots::New(alloc(), obj);
current->add(slots);
load = MLoadSlot::New(alloc(), slots, slot - NativeObject::MAX_FIXED_SLOTS);
load = MLoadSlot::New(alloc(), slots, slot - nfixed);
}
if (barrier == BarrierKind::NoBarrier)
@ -10373,13 +10416,15 @@ IonBuilder::getPropTryInlineAccess(bool *emitted, MDefinition *obj, PropertyName
}
BaselineInspector::ShapeVector nativeShapes(alloc());
BaselineInspector::ObjectGroupVector unboxedGroups(alloc());
if (!inspector->maybeInfoForPropertyOp(pc, nativeShapes, unboxedGroups))
BaselineInspector::ObjectGroupVector unboxedGroups(alloc()), convertUnboxedGroups(alloc());
if (!inspector->maybeInfoForPropertyOp(pc, nativeShapes, unboxedGroups, convertUnboxedGroups))
return false;
if (!canInlinePropertyOpShapes(nativeShapes, unboxedGroups))
return true;
obj = convertUnboxedObjects(obj, convertUnboxedGroups);
MIRType rvalType = types->getKnownMIRType();
if (barrier != BarrierKind::NoBarrier || IsNullOrUndefined(rvalType))
rvalType = MIRType_Value;
@ -10917,7 +10962,10 @@ IonBuilder::setPropTryDefiniteSlot(bool *emitted, MDefinition *obj,
return true;
}
uint32_t slot = getDefiniteSlot(obj->resultTypeSet(), name);
BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc());
uint32_t nfixed;
uint32_t slot = getDefiniteSlot(obj->resultTypeSet(), name, &nfixed, convertUnboxedGroups);
if (slot == UINT32_MAX)
return true;
@ -10935,8 +10983,10 @@ IonBuilder::setPropTryDefiniteSlot(bool *emitted, MDefinition *obj,
writeBarrier |= property.needsBarrier(constraints());
}
obj = convertUnboxedObjects(obj, convertUnboxedGroups);
MInstruction *store;
if (slot < NativeObject::MAX_FIXED_SLOTS) {
if (slot < nfixed) {
store = MStoreFixedSlot::New(alloc(), obj, slot, value);
if (writeBarrier)
store->toStoreFixedSlot()->setNeedsBarrier();
@ -10944,7 +10994,7 @@ IonBuilder::setPropTryDefiniteSlot(bool *emitted, MDefinition *obj,
MInstruction *slots = MSlots::New(alloc(), obj);
current->add(slots);
store = MStoreSlot::New(alloc(), slots, slot - NativeObject::MAX_FIXED_SLOTS, value);
store = MStoreSlot::New(alloc(), slots, slot - nfixed, value);
if (writeBarrier)
store->toStoreSlot()->setNeedsBarrier();
}
@ -11053,13 +11103,15 @@ IonBuilder::setPropTryInlineAccess(bool *emitted, MDefinition *obj,
}
BaselineInspector::ShapeVector nativeShapes(alloc());
BaselineInspector::ObjectGroupVector unboxedGroups(alloc());
if (!inspector->maybeInfoForPropertyOp(pc, nativeShapes, unboxedGroups))
BaselineInspector::ObjectGroupVector unboxedGroups(alloc()), convertUnboxedGroups(alloc());
if (!inspector->maybeInfoForPropertyOp(pc, nativeShapes, unboxedGroups, convertUnboxedGroups))
return false;
if (!canInlinePropertyOpShapes(nativeShapes, unboxedGroups))
return true;
obj = convertUnboxedObjects(obj, convertUnboxedGroups);
if (nativeShapes.length() == 1 && unboxedGroups.empty()) {
spew("Inlining monomorphic SETPROP");

View File

@ -889,7 +889,10 @@ class IonBuilder
JSObject *testSingletonProperty(JSObject *obj, PropertyName *name);
bool testSingletonPropertyTypes(MDefinition *obj, JSObject *singleton, PropertyName *name,
bool *testObject, bool *testString);
uint32_t getDefiniteSlot(TemporaryTypeSet *types, PropertyName *name);
uint32_t getDefiniteSlot(TemporaryTypeSet *types, PropertyName *name, uint32_t *pnfixed,
BaselineInspector::ObjectGroupVector &convertUnboxedGroups);
MDefinition *convertUnboxedObjects(MDefinition *obj,
const BaselineInspector::ObjectGroupVector &list);
uint32_t getUnboxedOffset(TemporaryTypeSet *types, PropertyName *name,
JSValueType *punboxedType);
MInstruction *loadUnboxedProperty(MDefinition *obj, size_t offset, JSValueType unboxedType,

View File

@ -4661,6 +4661,22 @@ class LStoreUnboxedPointer : public LInstructionHelper<0, 3, 0>
}
};
// If necessary, convert an unboxed object in a particular group to its native
// representation.
class LConvertUnboxedObjectToNative : public LInstructionHelper<0, 1, 0>
{
public:
LIR_HEADER(ConvertUnboxedObjectToNative)
explicit LConvertUnboxedObjectToNative(const LAllocation &object) {
setOperand(0, object);
}
MConvertUnboxedObjectToNative *mir() {
return mir_->toConvertUnboxedObjectToNative();
}
};
class LArrayPopShiftV : public LInstructionHelper<BOX_PIECES, 1, 2>
{
public:

View File

@ -223,6 +223,7 @@
_(StoreElementV) \
_(StoreElementT) \
_(StoreUnboxedPointer) \
_(ConvertUnboxedObjectToNative) \
_(ArrayPopShiftV) \
_(ArrayPopShiftT) \
_(ArrayPushV) \

View File

@ -2766,6 +2766,14 @@ LIRGenerator::visitStoreUnboxedString(MStoreUnboxedString *ins)
add(lir, ins);
}
void
LIRGenerator::visitConvertUnboxedObjectToNative(MConvertUnboxedObjectToNative *ins)
{
LInstruction *check = new(alloc()) LConvertUnboxedObjectToNative(useRegister(ins->object()));
add(check, ins);
assignSafepoint(check, ins);
}
void
LIRGenerator::visitEffectiveAddress(MEffectiveAddress *ins)
{

View File

@ -197,6 +197,7 @@ class LIRGenerator : public LIRGeneratorSpecific
void visitStoreElementHole(MStoreElementHole *ins);
void visitStoreUnboxedObjectOrNull(MStoreUnboxedObjectOrNull *ins);
void visitStoreUnboxedString(MStoreUnboxedString *ins);
void visitConvertUnboxedObjectToNative(MConvertUnboxedObjectToNative *ins);
void visitEffectiveAddress(MEffectiveAddress *ins);
void visitArrayPopShift(MArrayPopShift *ins);
void visitArrayPush(MArrayPush *ins);

View File

@ -4830,6 +4830,17 @@ jit::PropertyWriteNeedsTypeBarrier(TempAllocator &alloc, CompilerConstraintList
MOZ_ASSERT(excluded);
// If the excluded object is a group with an unboxed layout, make sure it
// does not have a corresponding native group. Objects with the native
// group might appear even though they are not in the type set.
if (excluded->isGroup()) {
if (UnboxedLayout *layout = excluded->group()->maybeUnboxedLayout()) {
if (layout->nativeGroup())
return true;
excluded->watchStateChangeForUnboxedConvertedToNative(constraints);
}
}
*pobj = AddGroupGuard(alloc, current, *pobj, excluded, /* bailOnEquality = */ true);
return false;
}

View File

@ -27,6 +27,7 @@
#include "vm/ArrayObject.h"
#include "vm/ScopeObject.h"
#include "vm/TypedArrayCommon.h"
#include "vm/UnboxedObject.h"
// Undo windows.h damage on Win64
#undef MemoryBarrier
@ -8422,6 +8423,60 @@ class MStoreUnboxedString
ALLOW_CLONE(MStoreUnboxedString)
};
// Passes through an object, after ensuring it is converted from an unboxed
// object to a native representation.
class MConvertUnboxedObjectToNative
: public MUnaryInstruction,
public SingleObjectPolicy::Data
{
AlwaysTenured<ObjectGroup *> group_;
explicit MConvertUnboxedObjectToNative(MDefinition *obj, ObjectGroup *group)
: MUnaryInstruction(obj),
group_(group)
{
setGuard();
setMovable();
setResultType(MIRType_Object);
}
public:
INSTRUCTION_HEADER(ConvertUnboxedObjectToNative)
static MConvertUnboxedObjectToNative *New(TempAllocator &alloc, MDefinition *obj,
ObjectGroup *group) {
return new(alloc) MConvertUnboxedObjectToNative(obj, group);
}
MDefinition *object() const {
return getOperand(0);
}
ObjectGroup *group() const {
return group_;
}
bool congruentTo(const MDefinition *ins) const MOZ_OVERRIDE {
if (!congruentIfOperandsEqual(ins))
return false;
return ins->toConvertUnboxedObjectToNative()->group() == group();
}
AliasSet getAliasSet() const MOZ_OVERRIDE {
// This instruction can read and write to all parts of the object, but
// is marked as non-effectful so it can be consolidated by LICM and GVN
// and avoid inhibiting other optimizations.
//
// This is valid to do because when unboxed objects might have a native
// group they can be converted to, we do not optimize accesses to the
// unboxed objects and do not guard on their group or shape (other than
// in this opcode).
//
// Later accesses can assume the object has a native representation
// and optimize accordingly. Those accesses cannot be reordered before
// this instruction, however. This is prevented by chaining this
// instruction with the object itself, in the same way as MBoundsCheck.
return AliasSet::None();
}
};
// Array.prototype.pop or Array.prototype.shift on a dense array.
class MArrayPopShift
: public MUnaryInstruction,
@ -9773,6 +9828,11 @@ class MGuardShape
setGuard();
setMovable();
setResultType(MIRType_Object);
// Disallow guarding on unboxed object shapes. The group is better to
// guard on, and guarding on the shape can interact badly with
// MConvertUnboxedObjectToNative.
MOZ_ASSERT(shape->getObjectClass() != &UnboxedPlainObject::class_);
}
public:
@ -9869,6 +9929,10 @@ class MGuardObjectGroup
setGuard();
setMovable();
setResultType(MIRType_Object);
// Unboxed groups which might be converted to natives can't be guarded
// on, due to MConvertUnboxedObjectToNative.
MOZ_ASSERT_IF(group->maybeUnboxedLayout(), !group->unboxedLayout().nativeGroup());
}
public:

View File

@ -184,6 +184,7 @@ namespace jit {
_(StoreElementHole) \
_(StoreUnboxedObjectOrNull) \
_(StoreUnboxedString) \
_(ConvertUnboxedObjectToNative) \
_(ArrayPopShift) \
_(ArrayPush) \
_(ArrayConcat) \

View File

@ -900,7 +900,7 @@ js::StandardDefineProperty(JSContext *cx, HandleObject obj, HandleId id, const P
if (IsAnyTypedArray(obj))
return DefinePropertyOnTypedArray(cx, obj, id, desc, throwError, rval);
if (obj->is<UnboxedPlainObject>() && !obj->as<UnboxedPlainObject>().convertToNative(cx))
if (obj->is<UnboxedPlainObject>() && !UnboxedPlainObject::convertToNative(cx, obj))
return false;
if (obj->getOps()->lookupProperty) {

View File

@ -219,6 +219,11 @@ class ObjectGroup : public gc::TenuredCell
// as well, if the group is also constructed using 'new').
Addendum_UnboxedLayout,
// If this group is used by objects that have been converted from an
// unboxed representation, the addendum points to the original unboxed
// group.
Addendum_OriginalUnboxedGroup,
// When used by typed objects, the addendum stores a TypeDescr.
Addendum_TypeDescr
};
@ -293,6 +298,16 @@ class ObjectGroup : public gc::TenuredCell
setAddendum(Addendum_UnboxedLayout, layout);
}
ObjectGroup *maybeOriginalUnboxedGroup() const {
if (addendumKind() == Addendum_OriginalUnboxedGroup)
return reinterpret_cast<ObjectGroup *>(addendum_);
return nullptr;
}
void setOriginalUnboxedGroup(ObjectGroup *group) {
setAddendum(Addendum_OriginalUnboxedGroup, group);
}
TypeDescr *maybeTypeDescr() {
// Note: there is no need to sweep when accessing the type descriptor
// of an object, as it is strongly held and immutable.

View File

@ -1709,6 +1709,31 @@ class ConstraintDataFreezeObjectForTypedArrayData
}
};
// Constraint which triggers recompilation if an unboxed object in some group
// is converted to a native object.
class ConstraintDataFreezeObjectForUnboxedConvertedToNative
{
public:
ConstraintDataFreezeObjectForUnboxedConvertedToNative()
{}
const char *kind() { return "freezeObjectForUnboxedConvertedToNative"; }
bool invalidateOnNewType(TypeSet::Type type) { return false; }
bool invalidateOnNewPropertyState(TypeSet *property) { return false; }
bool invalidateOnNewObjectState(ObjectGroup *group) {
return group->unboxedLayout().nativeGroup() != nullptr;
}
bool constraintHolds(JSContext *cx,
const HeapTypeSetKey &property, TemporaryTypeSet *expected)
{
return !invalidateOnNewObjectState(property.object()->maybeGroup());
}
bool shouldSweep() { return false; }
};
} /* anonymous namespace */
void
@ -1733,6 +1758,17 @@ TypeSet::ObjectKey::watchStateChangeForTypedArrayData(CompilerConstraintList *co
ConstraintDataFreezeObjectForTypedArrayData(tarray)));
}
void
TypeSet::ObjectKey::watchStateChangeForUnboxedConvertedToNative(CompilerConstraintList *constraints)
{
HeapTypeSetKey objectProperty = property(JSID_EMPTY);
LifoAlloc *alloc = constraints->alloc();
typedef CompilerConstraintInstance<ConstraintDataFreezeObjectForUnboxedConvertedToNative> T;
constraints->add(alloc->new_<T>(alloc, objectProperty,
ConstraintDataFreezeObjectForUnboxedConvertedToNative()));
}
static void
ObjectStateChange(ExclusiveContext *cxArg, ObjectGroup *group, bool markingUnknown)
{
@ -2561,16 +2597,28 @@ js::AddTypePropertyId(ExclusiveContext *cx, ObjectGroup *group, jsid id, TypeSet
TypeSet::ObjectGroupString(group), TypeIdString(id), TypeSet::TypeString(type));
types->addType(cx, type);
// If this addType caused the type set to be marked as containing any
// object, make sure that is reflected in other type sets the addType is
// propagated to below.
if (type.isObjectUnchecked() && types->unknownObject())
type = TypeSet::AnyObjectType();
// Propagate new types from partially initialized groups to fully
// initialized groups for the acquired properties analysis. Note that we
// don't need to do this for other property changes, as these will also be
// reflected via shape changes on the object that will prevent the object
// from acquiring the fully initialized group.
if (group->newScript() && group->newScript()->initializedGroup()) {
if (type.isObjectUnchecked() && types->unknownObject())
type = TypeSet::AnyObjectType();
if (group->newScript() && group->newScript()->initializedGroup())
AddTypePropertyId(cx, group->newScript()->initializedGroup(), id, type);
}
// Maintain equivalent type information for unboxed object groups and their
// corresponding native group. Since type sets might contain the unboxed
// group but not the native group, this ensures optimizations based on the
// unboxed group are valid for the native group.
if (group->maybeUnboxedLayout() && group->maybeUnboxedLayout()->nativeGroup())
AddTypePropertyId(cx, group->maybeUnboxedLayout()->nativeGroup(), id, type);
if (ObjectGroup *unboxedGroup = group->maybeOriginalUnboxedGroup())
AddTypePropertyId(cx, unboxedGroup, id, type);
}
void
@ -2666,6 +2714,12 @@ ObjectGroup::setFlags(ExclusiveContext *cx, ObjectGroupFlags flags)
// acquired properties analysis.
if (newScript() && newScript()->initializedGroup())
newScript()->initializedGroup()->setFlags(cx, flags);
// Propagate flag changes between unboxed and corresponding native groups.
if (maybeUnboxedLayout() && maybeUnboxedLayout()->nativeGroup())
maybeUnboxedLayout()->nativeGroup()->setFlags(cx, flags);
if (ObjectGroup *unboxedGroup = maybeOriginalUnboxedGroup())
unboxedGroup->setFlags(cx, flags);
}
void
@ -3566,7 +3620,7 @@ TypeNewScript::rollbackPartiallyInitializedObjects(JSContext *cx, ObjectGroup *g
}
if (thisv.toObject().is<UnboxedPlainObject>() &&
!thisv.toObject().as<UnboxedPlainObject>().convertToNative(cx))
!UnboxedPlainObject::convertToNative(cx, &thisv.toObject()))
{
CrashAtUnhandlableOOM("rollbackPartiallyInitializedObjects");
}
@ -3701,6 +3755,12 @@ ConstraintTypeSet::sweep(Zone *zone, AutoClearTypeInferenceStateOnOOM &oom)
// Object sets containing objects with unknown properties might
// not be complete. Mark the type set as unknown, which it will
// be treated as during Ion compilation.
//
// Note that we don't have to do this when the type set might
// be missing the native group corresponding to an unboxed
// object group. In this case, the native group points to the
// unboxed object group via its addendum, so as long as objects
// with either group exist, neither group will be finalized.
flags |= TYPE_FLAG_ANYOBJECT;
clearObjects();
objectCount = 0;

View File

@ -206,11 +206,19 @@ class TemporaryTypeSet;
* that compilation.
*
* The contents of a type set completely describe the values that a particular
* lvalue might have, except in cases where an object's type is mutated. In
* such cases, type sets which had the object's old type might not have the
* object's new type. Type mutation occurs only in specific circumstances ---
* when an object's prototype changes, and when it is swapped with another
* object --- and will cause the object's properties to be marked as unknown.
* lvalue might have, except for the following cases:
*
* - If an object's prototype or class is dynamically mutated, its group will
* change. Type sets containing the old group will not necessarily contain
* the new group. When this occurs, the properties of the old and new group
* will both be marked as unknown, which will prevent Ion from optimizing
* based on the object's type information.
*
* - If an unboxed object is converted to a native object, its group will also
* change and type sets containing the old group will not necessarily contain
* the new group. Unlike the above case, this will not degrade property type
* information, but Ion will no longer optimize unboxed objects with the old
* group.
*/
class TypeSet
{
@ -247,6 +255,7 @@ class TypeSet
bool hasStableClassAndProto(CompilerConstraintList *constraints);
void watchStateChangeForInlinedCall(CompilerConstraintList *constraints);
void watchStateChangeForTypedArrayData(CompilerConstraintList *constraints);
void watchStateChangeForUnboxedConvertedToNative(CompilerConstraintList *constraints);
HeapTypeSetKey property(jsid id);
void ensureTrackedProperty(JSContext *cx, jsid id);

View File

@ -29,6 +29,12 @@ UnboxedLayout::trace(JSTracer *trc)
if (newScript())
newScript()->trace(trc);
if (nativeGroup_)
MarkObjectGroup(trc, &nativeGroup_, "unboxed_layout_nativeGroup");
if (nativeShape_)
MarkShape(trc, &nativeShape_, "unboxed_layout_nativeShape");
}
size_t
@ -155,35 +161,30 @@ UnboxedPlainObject::trace(JSTracer *trc, JSObject *obj)
MOZ_ASSERT(*(list + 1) == -1);
}
bool
UnboxedPlainObject::convertToNative(JSContext *cx)
/* static */ bool
UnboxedLayout::makeNativeGroup(JSContext *cx, ObjectGroup *group)
{
// Immediately clear any new script on this object's group,
// as rollbackPartiallyInitializedObjects() will be confused by the type
// changes we make in this function.
group()->clearNewScript(cx);
UnboxedLayout &layout = group->unboxedLayout();
// clearNewScript() can reentrantly invoke this method.
if (!is<UnboxedPlainObject>())
return true;
MOZ_ASSERT(!layout.nativeGroup());
Rooted<UnboxedPlainObject *> obj(cx, this);
Rooted<TaggedProto> proto(cx, getTaggedProto());
// Immediately clear any new script on the group, as
// rollbackPartiallyInitializedObjects() will be confused by the type
// changes we make later on.
group->clearNewScript(cx);
size_t nfixed = gc::GetGCKindSlots(obj->layout().getAllocKind());
AutoEnterAnalysis enter(cx);
AutoValueVector values(cx);
Rooted<TaggedProto> proto(cx, group->proto());
size_t nfixed = gc::GetGCKindSlots(layout.getAllocKind());
RootedShape shape(cx, EmptyShape::getInitialShape(cx, &PlainObject::class_, proto,
getParent(), getMetadata(), nfixed,
lastProperty()->getObjectFlags()));
cx->global(), nullptr, nfixed, 0));
if (!shape)
return false;
for (size_t i = 0; i < obj->layout().properties().length(); i++) {
const UnboxedLayout::Property &property = obj->layout().properties()[i];
if (!values.append(obj->getValue(property)))
return false;
for (size_t i = 0; i < layout.properties().length(); i++) {
const UnboxedLayout::Property &property = layout.properties()[i];
StackShape unrootedChild(shape->base()->unowned(), NameToId(property.name), i,
JSPROP_ENUMERATE, 0);
@ -193,14 +194,76 @@ UnboxedPlainObject::convertToNative(JSContext *cx)
return false;
}
if (!SetClassAndProto(cx, obj, &PlainObject::class_, proto))
ObjectGroup *nativeGroup =
ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto,
group->flags() & OBJECT_FLAG_DYNAMIC_MASK);
if (!nativeGroup)
return false;
RootedNativeObject nobj(cx, &obj->as<PlainObject>());
nobj->setLastPropertyMakeNative(cx, shape);
// Propagate all property types from the old group to the new group.
for (size_t i = 0; i < group->getPropertyCount(); i++) {
if (ObjectGroup::Property *property = group->getProperty(i)) {
TypeSet::TypeList types;
if (!property->types.enumerateTypes(&types))
return false;
for (size_t i = 0; i < types.length(); i++)
AddTypePropertyId(cx, nativeGroup, property->id, types[i]);
HeapTypeSet *nativeProperty = nativeGroup->maybeGetProperty(property->id);
if (nativeProperty->canSetDefinite(i))
nativeProperty->setDefinite(i);
}
}
layout.nativeGroup_ = nativeGroup;
layout.nativeShape_ = shape;
nativeGroup->setOriginalUnboxedGroup(group);
return true;
}
/* static */ bool
UnboxedPlainObject::convertToNative(JSContext *cx, JSObject *obj)
{
const UnboxedLayout &layout = obj->as<UnboxedPlainObject>().layout();
if (!layout.nativeGroup()) {
if (!UnboxedLayout::makeNativeGroup(cx, obj->group()))
return false;
// makeNativeGroup can reentrantly invoke this method.
if (obj->is<PlainObject>())
return true;
}
AutoValueVector values(cx);
for (size_t i = 0; i < layout.properties().length(); i++) {
if (!values.append(obj->as<UnboxedPlainObject>().getValue(layout.properties()[i])))
return false;
}
uint32_t objectFlags = obj->lastProperty()->getObjectFlags();
RootedObject metadata(cx, obj->getMetadata());
obj->setGroup(layout.nativeGroup());
obj->as<PlainObject>().setLastPropertyMakeNative(cx, layout.nativeShape());
for (size_t i = 0; i < values.length(); i++)
nobj->initSlotUnchecked(i, values[i]);
obj->as<PlainObject>().initSlotUnchecked(i, values[i]);
if (objectFlags) {
RootedObject objRoot(cx, obj);
if (!obj->setFlags(cx, objectFlags))
return false;
obj = objRoot;
}
if (metadata) {
RootedObject objRoot(cx, obj);
RootedObject metadataRoot(cx, metadata);
if (!setMetadata(cx, objRoot, metadataRoot))
return false;
}
return true;
}
@ -265,7 +328,7 @@ UnboxedPlainObject::obj_lookupProperty(JSContext *cx, HandleObject obj,
UnboxedPlainObject::obj_defineProperty(JSContext *cx, HandleObject obj, HandleId id, HandleValue v,
PropertyOp getter, StrictPropertyOp setter, unsigned attrs)
{
if (!obj->as<UnboxedPlainObject>().convertToNative(cx))
if (!convertToNative(cx, obj))
return false;
return DefineProperty(cx, obj, id, v, getter, setter, attrs);
@ -319,7 +382,7 @@ UnboxedPlainObject::obj_setProperty(JSContext *cx, HandleObject obj, HandleObjec
if (obj->as<UnboxedPlainObject>().setValue(cx, *property, vp))
return true;
if (!obj->as<UnboxedPlainObject>().convertToNative(cx))
if (!convertToNative(cx, obj))
return false;
return SetProperty(cx, obj, receiver, id, vp, strict);
}
@ -351,7 +414,7 @@ UnboxedPlainObject::obj_getOwnPropertyDescriptor(JSContext *cx, HandleObject obj
UnboxedPlainObject::obj_deleteProperty(JSContext *cx, HandleObject obj, HandleId id,
bool *succeeded)
{
if (!obj->as<UnboxedPlainObject>().convertToNative(cx))
if (!convertToNative(cx, obj))
return false;
return DeleteProperty(cx, obj, id, succeeded);
}
@ -359,7 +422,7 @@ UnboxedPlainObject::obj_deleteProperty(JSContext *cx, HandleObject obj, HandleId
/* static */ bool
UnboxedPlainObject::obj_watch(JSContext *cx, HandleObject obj, HandleId id, HandleObject callable)
{
if (!obj->as<UnboxedPlainObject>().convertToNative(cx))
if (!convertToNative(cx, obj))
return false;
return WatchProperty(cx, obj, id, callable);
}

View File

@ -65,9 +65,16 @@ class UnboxedLayout : public mozilla::LinkedListElement<UnboxedLayout>
// structure as the trace list on a TypeDescr.
int32_t *traceList_;
// If objects in this group have ever been converted to native objects,
// these store the corresponding native group and initial shape for such
// objects. Type information for this object is reflected in nativeGroup.
HeapPtrObjectGroup nativeGroup_;
HeapPtrShape nativeShape_;
public:
UnboxedLayout(const PropertyVector &properties, size_t size)
: size_(size), newScript_(nullptr), traceList_(nullptr)
: size_(size), newScript_(nullptr), traceList_(nullptr),
nativeGroup_(nullptr), nativeShape_(nullptr)
{
properties_.appendAll(properties);
}
@ -113,11 +120,21 @@ class UnboxedLayout : public mozilla::LinkedListElement<UnboxedLayout>
return size_;
}
ObjectGroup *nativeGroup() const {
return nativeGroup_;
}
Shape *nativeShape() const {
return nativeShape_;
}
inline gc::AllocKind getAllocKind() const;
void trace(JSTracer *trc);
size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
static bool makeNativeGroup(JSContext *cx, ObjectGroup *group);
};
// Class for a plain object using an unboxed representation. The physical
@ -166,8 +183,7 @@ class UnboxedPlainObject : public JSObject
bool setValue(JSContext *cx, const UnboxedLayout::Property &property, const Value &v);
Value getValue(const UnboxedLayout::Property &property);
bool convertToNative(JSContext *cx);
static bool convertToNative(JSContext *cx, JSObject *obj);
static UnboxedPlainObject *create(JSContext *cx, HandleObjectGroup group, NewObjectKind newKind);
static void trace(JSTracer *trc, JSObject *object);