mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1133254 - Improve type information and Ion compilation when dealing with converted unboxed objects, r=jandem.
This commit is contained in:
parent
30073f0d31
commit
9e50b6836a
@ -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, \
|
||||
|
@ -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>());
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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:
|
||||
|
@ -223,6 +223,7 @@
|
||||
_(StoreElementV) \
|
||||
_(StoreElementT) \
|
||||
_(StoreUnboxedPointer) \
|
||||
_(ConvertUnboxedObjectToNative) \
|
||||
_(ArrayPopShiftV) \
|
||||
_(ArrayPopShiftT) \
|
||||
_(ArrayPushV) \
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -184,6 +184,7 @@ namespace jit {
|
||||
_(StoreElementHole) \
|
||||
_(StoreUnboxedObjectOrNull) \
|
||||
_(StoreUnboxedString) \
|
||||
_(ConvertUnboxedObjectToNative) \
|
||||
_(ArrayPopShift) \
|
||||
_(ArrayPush) \
|
||||
_(ArrayConcat) \
|
||||
|
@ -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) {
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user