summaryrefslogtreecommitdiffstats
path: root/js/src/vm
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm')
-rw-r--r--js/src/vm/ErrorObject.cpp8
-rw-r--r--js/src/vm/Interpreter-inl.h15
-rw-r--r--js/src/vm/Interpreter.cpp28
-rw-r--r--js/src/vm/JSONParser.cpp4
-rw-r--r--js/src/vm/NativeObject-inl.h32
-rw-r--r--js/src/vm/NativeObject.cpp44
-rw-r--r--js/src/vm/NativeObject.h28
-rw-r--r--js/src/vm/ObjectGroup-inl.h14
-rw-r--r--js/src/vm/ObjectGroup.cpp158
-rw-r--r--js/src/vm/ObjectGroup.h72
-rw-r--r--js/src/vm/Opcodes.h12
-rw-r--r--js/src/vm/ReceiverGuard.cpp19
-rw-r--r--js/src/vm/ReceiverGuard.h5
-rw-r--r--js/src/vm/RegExpObject.cpp3
-rw-r--r--js/src/vm/Stack.cpp2
-rw-r--r--js/src/vm/Stack.h2
-rw-r--r--js/src/vm/TypeInference-inl.h5
-rw-r--r--js/src/vm/TypeInference.cpp176
-rw-r--r--js/src/vm/TypeInference.h5
-rw-r--r--js/src/vm/UnboxedObject-inl.h840
-rw-r--r--js/src/vm/UnboxedObject.cpp2132
-rw-r--r--js/src/vm/UnboxedObject.h531
22 files changed, 4023 insertions, 112 deletions
diff --git a/js/src/vm/ErrorObject.cpp b/js/src/vm/ErrorObject.cpp
index 271132801..d8d29830b 100644
--- a/js/src/vm/ErrorObject.cpp
+++ b/js/src/vm/ErrorObject.cpp
@@ -29,11 +29,11 @@ js::ErrorObject::assignInitialShape(ExclusiveContext* cx, Handle<ErrorObject*> o
{
MOZ_ASSERT(obj->empty());
- if (!NativeObject::addDataProperty(cx, obj, cx->names().fileName, FILENAME_SLOT, 0))
+ if (!obj->addDataProperty(cx, cx->names().fileName, FILENAME_SLOT, 0))
return nullptr;
- if (!NativeObject::addDataProperty(cx, obj, cx->names().lineNumber, LINENUMBER_SLOT, 0))
+ if (!obj->addDataProperty(cx, cx->names().lineNumber, LINENUMBER_SLOT, 0))
return nullptr;
- return NativeObject::addDataProperty(cx, obj, cx->names().columnNumber, COLUMNNUMBER_SLOT, 0);
+ return obj->addDataProperty(cx, cx->names().columnNumber, COLUMNNUMBER_SLOT, 0);
}
/* static */ bool
@@ -57,7 +57,7 @@ js::ErrorObject::init(JSContext* cx, Handle<ErrorObject*> obj, JSExnType type,
// |new Error()|.
RootedShape messageShape(cx);
if (message) {
- messageShape = NativeObject::addDataProperty(cx, obj, cx->names().message, MESSAGE_SLOT, 0);
+ messageShape = obj->addDataProperty(cx, cx->names().message, MESSAGE_SLOT, 0);
if (!messageShape)
return false;
MOZ_ASSERT(messageShape->slot() == MESSAGE_SLOT);
diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h
index acfa8f74b..0e81dfef4 100644
--- a/js/src/vm/Interpreter-inl.h
+++ b/js/src/vm/Interpreter-inl.h
@@ -22,6 +22,7 @@
#include "vm/EnvironmentObject-inl.h"
#include "vm/Stack-inl.h"
#include "vm/String-inl.h"
+#include "vm/UnboxedObject-inl.h"
namespace js {
@@ -336,10 +337,14 @@ InitGlobalLexicalOperation(JSContext* cx, LexicalEnvironmentObject* lexicalEnvAr
inline bool
InitPropertyOperation(JSContext* cx, JSOp op, HandleObject obj, HandleId id, HandleValue rhs)
{
- MOZ_ASSERT(obj->is<PlainObject>() || obj->is<JSFunction>());
- unsigned propAttrs = GetInitDataPropAttrs(op);
- return NativeDefineProperty(cx, obj.as<NativeObject>(), id, rhs,
- nullptr, nullptr, propAttrs);
+ if (obj->is<PlainObject>() || obj->is<JSFunction>()) {
+ unsigned propAttrs = GetInitDataPropAttrs(op);
+ return NativeDefineProperty(cx, obj.as<NativeObject>(), id, rhs, nullptr, nullptr,
+ propAttrs);
+ }
+
+ MOZ_ASSERT(obj->as<UnboxedPlainObject>().layout().lookup(id));
+ return PutProperty(cx, obj, id, rhs, false);
}
inline bool
@@ -593,7 +598,7 @@ InitArrayElemOperation(JSContext* cx, jsbytecode* pc, HandleObject obj, uint32_t
JSOp op = JSOp(*pc);
MOZ_ASSERT(op == JSOP_INITELEM_ARRAY || op == JSOP_INITELEM_INC);
- MOZ_ASSERT(obj->is<ArrayObject>());
+ MOZ_ASSERT(obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>());
if (op == JSOP_INITELEM_INC && index == INT32_MAX) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SPREAD_TOO_LARGE);
diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp
index 3cf9b57f6..274392335 100644
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1939,7 +1939,6 @@ CASE(EnableInterruptsPseudoOpcode)
/* Various 1-byte no-ops. */
CASE(JSOP_NOP)
CASE(JSOP_NOP_DESTRUCTURING)
-CASE(JSOP_UNUSED126)
CASE(JSOP_UNUSED211)
CASE(JSOP_TRY_DESTRUCTURING_ITERCLOSE)
CASE(JSOP_UNUSED221)
@@ -3682,6 +3681,7 @@ CASE(JSOP_NEWINIT)
END_CASE(JSOP_NEWINIT)
CASE(JSOP_NEWARRAY)
+CASE(JSOP_SPREADCALLARRAY)
{
uint32_t length = GET_UINT32(REGS.pc);
JSObject* obj = NewArrayOperation(cx, script, REGS.pc, length);
@@ -4157,7 +4157,7 @@ CASE(JSOP_INITHOMEOBJECT)
/* Load the home object */
ReservedRooted<JSObject*> obj(&rootObject0);
obj = &REGS.sp[int(-2 - skipOver)].toObject();
- MOZ_ASSERT(obj->is<PlainObject>() || obj->is<JSFunction>());
+ MOZ_ASSERT(obj->is<PlainObject>() || obj->is<UnboxedPlainObject>() || obj->is<JSFunction>());
func->setExtendedSlot(FunctionExtended::METHOD_HOMEOBJECT_SLOT, ObjectValue(*obj));
}
@@ -4973,13 +4973,18 @@ js::NewObjectOperation(JSContext* cx, HandleScript script, jsbytecode* pc,
return nullptr;
if (group->maybePreliminaryObjects()) {
group->maybePreliminaryObjects()->maybeAnalyze(cx, group);
+ if (group->maybeUnboxedLayout())
+ group->maybeUnboxedLayout()->setAllocationSite(script, pc);
}
if (group->shouldPreTenure() || group->maybePreliminaryObjects())
newKind = TenuredObject;
+
+ if (group->maybeUnboxedLayout())
+ return UnboxedPlainObject::create(cx, group, newKind);
}
- RootedPlainObject obj(cx);
+ RootedObject obj(cx);
if (*pc == JSOP_NEWOBJECT) {
RootedPlainObject baseObject(cx, &script->getObject(pc)->as<PlainObject>());
@@ -5016,6 +5021,11 @@ js::NewObjectOperationWithTemplate(JSContext* cx, HandleObject templateObject)
NewObjectKind newKind = templateObject->group()->shouldPreTenure() ? TenuredObject : GenericObject;
+ if (templateObject->group()->maybeUnboxedLayout()) {
+ RootedObjectGroup group(cx, templateObject->group());
+ return UnboxedPlainObject::create(cx, group, newKind);
+ }
+
JSObject* obj = CopyInitializerObject(cx, templateObject.as<PlainObject>(), newKind);
if (!obj)
return nullptr;
@@ -5042,6 +5052,9 @@ js::NewArrayOperation(JSContext* cx, HandleScript script, jsbytecode* pc, uint32
if (group->shouldPreTenure() || group->maybePreliminaryObjects())
newKind = TenuredObject;
+
+ if (group->maybeUnboxedLayout())
+ return UnboxedArrayObject::create(cx, group, length, newKind);
}
ArrayObject* obj = NewDenseFullyAllocatedArray(cx, length, nullptr, newKind);
@@ -5052,6 +5065,9 @@ js::NewArrayOperation(JSContext* cx, HandleScript script, jsbytecode* pc, uint32
MOZ_ASSERT(obj->isSingleton());
} else {
obj->setGroup(group);
+
+ if (PreliminaryObjectArray* preliminaryObjects = group->maybePreliminaryObjects())
+ preliminaryObjects->registerNewObject(obj);
}
return obj;
@@ -5064,6 +5080,12 @@ js::NewArrayOperationWithTemplate(JSContext* cx, HandleObject templateObject)
NewObjectKind newKind = templateObject->group()->shouldPreTenure() ? TenuredObject : GenericObject;
+ if (templateObject->is<UnboxedArrayObject>()) {
+ uint32_t length = templateObject->as<UnboxedArrayObject>().length();
+ RootedObjectGroup group(cx, templateObject->group());
+ return UnboxedArrayObject::create(cx, group, length, newKind);
+ }
+
ArrayObject* obj = NewDenseFullyAllocatedArray(cx, templateObject->as<ArrayObject>().length(),
nullptr, newKind);
if (!obj)
diff --git a/js/src/vm/JSONParser.cpp b/js/src/vm/JSONParser.cpp
index e50da3bc4..01883bb15 100644
--- a/js/src/vm/JSONParser.cpp
+++ b/js/src/vm/JSONParser.cpp
@@ -606,8 +606,8 @@ JSONParserBase::finishArray(MutableHandleValue vp, ElementVector& elements)
{
MOZ_ASSERT(&elements == &stack.back().elements());
- ArrayObject* obj = ObjectGroup::newArrayObject(cx, elements.begin(), elements.length(),
- GenericObject);
+ JSObject* obj = ObjectGroup::newArrayObject(cx, elements.begin(), elements.length(),
+ GenericObject);
if (!obj)
return false;
diff --git a/js/src/vm/NativeObject-inl.h b/js/src/vm/NativeObject-inl.h
index 030d92c12..205d99aa2 100644
--- a/js/src/vm/NativeObject-inl.h
+++ b/js/src/vm/NativeObject-inl.h
@@ -235,38 +235,6 @@ NativeObject::ensureDenseElements(ExclusiveContext* cx, uint32_t index, uint32_t
return DenseElementResult::Success;
}
-inline DenseElementResult
-NativeObject::setOrExtendDenseElements(ExclusiveContext* cx, uint32_t start, const Value* vp,
- uint32_t count,
- ShouldUpdateTypes updateTypes)
-{
- if (denseElementsAreFrozen())
- return DenseElementResult::Incomplete;
-
- if (is<ArrayObject>() &&
- !as<ArrayObject>().lengthIsWritable() &&
- start + count >= as<ArrayObject>().length())
- {
- return DenseElementResult::Incomplete;
- }
-
- DenseElementResult result = ensureDenseElements(cx, start, count);
- if (result != DenseElementResult::Success)
- return result;
-
- if (is<ArrayObject>() && start + count >= as<ArrayObject>().length())
- as<ArrayObject>().setLengthInt32(start + count);
-
- if (updateTypes == ShouldUpdateTypes::DontUpdate && !shouldConvertDoubleElements()) {
- copyDenseElements(start, vp, count);
- } else {
- for (size_t i = 0; i < count; i++)
- setDenseElementWithType(cx, start + i, vp[i]);
- }
-
- return DenseElementResult::Success;
-}
-
inline Value
NativeObject::getDenseOrTypedArrayElement(uint32_t idx)
{
diff --git a/js/src/vm/NativeObject.cpp b/js/src/vm/NativeObject.cpp
index d801fad06..8b7543d12 100644
--- a/js/src/vm/NativeObject.cpp
+++ b/js/src/vm/NativeObject.cpp
@@ -388,6 +388,33 @@ NativeObject::setLastPropertyMakeNonNative(Shape* shape)
shape_ = shape;
}
+void
+NativeObject::setLastPropertyMakeNative(ExclusiveContext* cx, Shape* shape)
+{
+ MOZ_ASSERT(getClass()->isNative());
+ MOZ_ASSERT(shape->getObjectClass()->isNative());
+ MOZ_ASSERT(!shape->inDictionary());
+
+ // This method is used to convert unboxed objects into native objects. In
+ // this case, the shape_ field was previously used to store other data and
+ // this should be treated as an initialization.
+ shape_.init(shape);
+
+ slots_ = nullptr;
+ elements_ = emptyObjectElements;
+
+ size_t oldSpan = shape->numFixedSlots();
+ size_t newSpan = shape->slotSpan();
+
+ initializeSlotRange(0, oldSpan);
+
+ // A failure at this point will leave the object as a mutant, and we
+ // can't recover.
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ if (oldSpan != newSpan && !updateSlotsForSpan(cx, oldSpan, newSpan))
+ oomUnsafe.crash("NativeObject::setLastPropertyMakeNative");
+}
+
bool
NativeObject::setSlotSpan(ExclusiveContext* cx, uint32_t span)
{
@@ -994,22 +1021,23 @@ NativeObject::freeSlot(ExclusiveContext* cx, uint32_t slot)
setSlot(slot, UndefinedValue());
}
-/* static */ Shape*
-NativeObject::addDataProperty(ExclusiveContext* cx, HandleNativeObject obj,
- jsid idArg, uint32_t slot, unsigned attrs)
+Shape*
+NativeObject::addDataProperty(ExclusiveContext* cx, jsid idArg, uint32_t slot, unsigned attrs)
{
MOZ_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER)));
+ RootedNativeObject self(cx, this);
RootedId id(cx, idArg);
- return addProperty(cx, obj, id, nullptr, nullptr, slot, attrs, 0);
+ return addProperty(cx, self, id, nullptr, nullptr, slot, attrs, 0);
}
-/* static */ Shape*
-NativeObject::addDataProperty(ExclusiveContext* cx, HandleNativeObject obj,
- HandlePropertyName name, uint32_t slot, unsigned attrs)
+Shape*
+NativeObject::addDataProperty(ExclusiveContext* cx, HandlePropertyName name,
+ uint32_t slot, unsigned attrs)
{
MOZ_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER)));
+ RootedNativeObject self(cx, this);
RootedId id(cx, NameToId(name));
- return addProperty(cx, obj, id, nullptr, nullptr, slot, attrs, 0);
+ return addProperty(cx, self, id, nullptr, nullptr, slot, attrs, 0);
}
template <AllowGC allowGC>
diff --git a/js/src/vm/NativeObject.h b/js/src/vm/NativeObject.h
index 9cc6d5436..3a3e50244 100644
--- a/js/src/vm/NativeObject.h
+++ b/js/src/vm/NativeObject.h
@@ -339,19 +339,16 @@ IsObjectValueInCompartment(const Value& v, JSCompartment* comp);
#endif
// Operations which change an object's dense elements can either succeed, fail,
-// or be unable to complete. The latter is used when the object's elements must
-// become sparse instead. The enum below is used for such operations.
+// or be unable to complete. For native objects, the latter is used when the
+// object's elements must become sparse instead. The enum below is used for
+// such operations, and for similar operations on unboxed arrays and methods
+// that work on both kinds of objects.
enum class DenseElementResult {
Failure,
Success,
Incomplete
};
-enum class ShouldUpdateTypes {
- Update,
- DontUpdate
-};
-
/*
* NativeObject specifies the internal implementation of a native object.
*
@@ -470,6 +467,11 @@ class NativeObject : public ShapedObject
// that are (temporarily) inconsistent.
void setLastPropertyMakeNonNative(Shape* shape);
+ // As for setLastProperty(), but changes the class associated with the
+ // object to a native one. The object's type has already been changed, and
+ // this brings the shape into sync with it.
+ void setLastPropertyMakeNative(ExclusiveContext* cx, Shape* shape);
+
// Newly-created TypedArrays that map a SharedArrayBuffer are
// marked as shared by giving them an ObjectElements that has the
// ObjectElements::SHARED_MEMORY flag set.
@@ -744,10 +746,10 @@ class NativeObject : public ShapedObject
bool allowDictionary = true);
/* Add a data property whose id is not yet in this scope. */
- static Shape* addDataProperty(ExclusiveContext* cx, HandleNativeObject obj,
- jsid id_, uint32_t slot, unsigned attrs);
- static Shape* addDataProperty(ExclusiveContext* cx, HandleNativeObject obj,
- HandlePropertyName name, uint32_t slot, unsigned attrs);
+ Shape* addDataProperty(ExclusiveContext* cx,
+ jsid id_, uint32_t slot, unsigned attrs);
+ Shape* addDataProperty(ExclusiveContext* cx, HandlePropertyName name,
+ uint32_t slot, unsigned attrs);
/* Add or overwrite a property for id in this scope. */
static Shape*
@@ -1149,10 +1151,6 @@ class NativeObject : public ShapedObject
elementsRangeWriteBarrierPost(dstStart, count);
}
- inline DenseElementResult
- setOrExtendDenseElements(ExclusiveContext* cx, uint32_t start, const Value* vp, uint32_t count,
- ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update);
-
bool shouldConvertDoubleElements() {
return getElementsHeader()->shouldConvertDoubleElements();
}
diff --git a/js/src/vm/ObjectGroup-inl.h b/js/src/vm/ObjectGroup-inl.h
index d41343be6..9074f4d97 100644
--- a/js/src/vm/ObjectGroup-inl.h
+++ b/js/src/vm/ObjectGroup-inl.h
@@ -108,6 +108,20 @@ ObjectGroup::maybePreliminaryObjects()
return maybePreliminaryObjectsDontCheckGeneration();
}
+inline UnboxedLayout*
+ObjectGroup::maybeUnboxedLayout()
+{
+ maybeSweep(nullptr);
+ return maybeUnboxedLayoutDontCheckGeneration();
+}
+
+inline UnboxedLayout&
+ObjectGroup::unboxedLayout()
+{
+ maybeSweep(nullptr);
+ return unboxedLayoutDontCheckGeneration();
+}
+
} // namespace js
#endif /* vm_ObjectGroup_inl_h */
diff --git a/js/src/vm/ObjectGroup.cpp b/js/src/vm/ObjectGroup.cpp
index 91070b3f6..d05a48646 100644
--- a/js/src/vm/ObjectGroup.cpp
+++ b/js/src/vm/ObjectGroup.cpp
@@ -18,10 +18,11 @@
#include "vm/ArrayObject.h"
#include "vm/Shape.h"
#include "vm/TaggedProto.h"
+#include "vm/UnboxedObject.h"
#include "jsobjinlines.h"
-#include "vm/NativeObject-inl.h"
+#include "vm/UnboxedObject-inl.h"
using namespace js;
@@ -55,6 +56,7 @@ ObjectGroup::finalize(FreeOp* fop)
if (newScriptDontCheckGeneration())
newScriptDontCheckGeneration()->clear();
fop->delete_(newScriptDontCheckGeneration());
+ fop->delete_(maybeUnboxedLayoutDontCheckGeneration());
if (maybePreliminaryObjectsDontCheckGeneration())
maybePreliminaryObjectsDontCheckGeneration()->clear();
fop->delete_(maybePreliminaryObjectsDontCheckGeneration());
@@ -81,6 +83,8 @@ ObjectGroup::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
size_t n = 0;
if (TypeNewScript* newScript = newScriptDontCheckGeneration())
n += newScript->sizeOfIncludingThis(mallocSizeOf);
+ if (UnboxedLayout* layout = maybeUnboxedLayoutDontCheckGeneration())
+ n += layout->sizeOfIncludingThis(mallocSizeOf);
return n;
}
@@ -529,7 +533,8 @@ ObjectGroup::defaultNewGroup(ExclusiveContext* cx, const Class* clasp,
if (p) {
ObjectGroup* group = p->group;
MOZ_ASSERT_IF(clasp, group->clasp() == clasp);
- MOZ_ASSERT_IF(!clasp, group->clasp() == &PlainObject::class_);
+ MOZ_ASSERT_IF(!clasp, group->clasp() == &PlainObject::class_ ||
+ group->clasp() == &UnboxedPlainObject::class_);
MOZ_ASSERT(group->proto() == proto);
return group;
}
@@ -772,7 +777,7 @@ GetValueTypeForTable(const Value& v)
return type;
}
-/* static */ ArrayObject*
+/* static */ JSObject*
ObjectGroup::newArrayObject(ExclusiveContext* cx,
const Value* vp, size_t length,
NewObjectKind newKind, NewArrayKind arrayKind)
@@ -836,13 +841,56 @@ ObjectGroup::newArrayObject(ExclusiveContext* cx,
AddTypePropertyId(cx, group, nullptr, JSID_VOID, elementType);
+ if (elementType != TypeSet::UnknownType()) {
+ // Keep track of the initial objects we create with this type.
+ // If the initial ones have a consistent shape and property types, we
+ // will try to use an unboxed layout for the group.
+ PreliminaryObjectArrayWithTemplate* preliminaryObjects =
+ cx->new_<PreliminaryObjectArrayWithTemplate>(nullptr);
+ if (!preliminaryObjects)
+ return nullptr;
+ group->setPreliminaryObjects(preliminaryObjects);
+ }
+
if (!p.add(cx, *table, ObjectGroupCompartment::ArrayObjectKey(elementType), group))
return nullptr;
}
// The type of the elements being added will already be reflected in type
- // information.
+ // information, but make sure when creating an unboxed array that the
+ // common element type is suitable for the unboxed representation.
ShouldUpdateTypes updateTypes = ShouldUpdateTypes::DontUpdate;
+ if (!MaybeAnalyzeBeforeCreatingLargeArray(cx, group, vp, length))
+ return nullptr;
+ if (group->maybePreliminaryObjects())
+ group->maybePreliminaryObjects()->maybeAnalyze(cx, group);
+ if (group->maybeUnboxedLayout()) {
+ switch (group->unboxedLayout().elementType()) {
+ case JSVAL_TYPE_BOOLEAN:
+ if (elementType != TypeSet::BooleanType())
+ updateTypes = ShouldUpdateTypes::Update;
+ break;
+ case JSVAL_TYPE_INT32:
+ if (elementType != TypeSet::Int32Type())
+ updateTypes = ShouldUpdateTypes::Update;
+ break;
+ case JSVAL_TYPE_DOUBLE:
+ if (elementType != TypeSet::Int32Type() && elementType != TypeSet::DoubleType())
+ updateTypes = ShouldUpdateTypes::Update;
+ break;
+ case JSVAL_TYPE_STRING:
+ if (elementType != TypeSet::StringType())
+ updateTypes = ShouldUpdateTypes::Update;
+ break;
+ case JSVAL_TYPE_OBJECT:
+ if (elementType != TypeSet::NullType() && !elementType.get().isObjectUnchecked())
+ updateTypes = ShouldUpdateTypes::Update;
+ break;
+ default:
+ MOZ_CRASH();
+ }
+ }
+
return NewCopiedArrayTryUseGroup(cx, group, vp, length, newKind, updateTypes);
}
@@ -852,15 +900,49 @@ GiveObjectGroup(ExclusiveContext* cx, JSObject* source, JSObject* target)
{
MOZ_ASSERT(source->group() != target->group());
- if (!target->is<ArrayObject>() || !source->is<ArrayObject>()) {
+ if (!target->is<ArrayObject>() && !target->is<UnboxedArrayObject>())
+ return true;
+
+ if (target->group()->maybePreliminaryObjects()) {
+ bool force = IsInsideNursery(source);
+ target->group()->maybePreliminaryObjects()->maybeAnalyze(cx, target->group(), force);
+ }
+
+ if (target->is<ArrayObject>()) {
+ ObjectGroup* sourceGroup = source->group();
+
+ if (source->is<UnboxedArrayObject>()) {
+ Shape* shape = target->as<ArrayObject>().lastProperty();
+ if (!UnboxedArrayObject::convertToNativeWithGroup(cx, source, target->group(), shape))
+ return false;
+ } else if (source->is<ArrayObject>()) {
+ source->setGroup(target->group());
+ } else {
+ return true;
+ }
+
+ if (sourceGroup->maybePreliminaryObjects())
+ sourceGroup->maybePreliminaryObjects()->unregisterObject(source);
+ if (target->group()->maybePreliminaryObjects())
+ target->group()->maybePreliminaryObjects()->registerNewObject(source);
+
+ for (size_t i = 0; i < source->as<ArrayObject>().getDenseInitializedLength(); i++) {
+ Value v = source->as<ArrayObject>().getDenseElement(i);
+ AddTypePropertyId(cx, source->group(), source, JSID_VOID, v);
+ }
+
return true;
}
- source->setGroup(target->group());
+ if (target->is<UnboxedArrayObject>()) {
+ if (!source->is<UnboxedArrayObject>())
+ return true;
+ if (source->as<UnboxedArrayObject>().elementType() != JSVAL_TYPE_INT32)
+ return true;
+ if (target->as<UnboxedArrayObject>().elementType() != JSVAL_TYPE_DOUBLE)
+ return true;
- for (size_t i = 0; i < source->as<ArrayObject>().getDenseInitializedLength(); i++) {
- Value v = source->as<ArrayObject>().getDenseElement(i);
- AddTypePropertyId(cx, source->group(), source, JSID_VOID, v);
+ return source->as<UnboxedArrayObject>().convertInt32ToDouble(cx, target->group());
}
return true;
@@ -969,6 +1051,46 @@ js::CombinePlainObjectPropertyTypes(ExclusiveContext* cx, JSObject* newObj,
}
}
}
+ } else if (newObj->is<UnboxedPlainObject>()) {
+ const UnboxedLayout& layout = newObj->as<UnboxedPlainObject>().layout();
+ const int32_t* traceList = layout.traceList();
+ if (!traceList)
+ return true;
+
+ uint8_t* newData = newObj->as<UnboxedPlainObject>().data();
+ uint8_t* oldData = oldObj->as<UnboxedPlainObject>().data();
+
+ for (; *traceList != -1; traceList++) {}
+ traceList++;
+ for (; *traceList != -1; traceList++) {
+ JSObject* newInnerObj = *reinterpret_cast<JSObject**>(newData + *traceList);
+ JSObject* oldInnerObj = *reinterpret_cast<JSObject**>(oldData + *traceList);
+
+ if (!newInnerObj || !oldInnerObj || SameGroup(oldInnerObj, newInnerObj))
+ continue;
+
+ if (!GiveObjectGroup(cx, newInnerObj, oldInnerObj))
+ return false;
+
+ if (SameGroup(oldInnerObj, newInnerObj))
+ continue;
+
+ if (!GiveObjectGroup(cx, oldInnerObj, newInnerObj))
+ return false;
+
+ if (SameGroup(oldInnerObj, newInnerObj)) {
+ for (size_t i = 1; i < ncompare; i++) {
+ if (compare[i].isObject() && SameGroup(&compare[i].toObject(), newObj)) {
+ uint8_t* otherData = compare[i].toObject().as<UnboxedPlainObject>().data();
+ JSObject* otherInnerObj = *reinterpret_cast<JSObject**>(otherData + *traceList);
+ if (otherInnerObj && !SameGroup(otherInnerObj, newInnerObj)) {
+ if (!GiveObjectGroup(cx, otherInnerObj, newInnerObj))
+ return false;
+ }
+ }
+ }
+ }
+ }
}
return true;
@@ -1192,6 +1314,12 @@ ObjectGroup::newPlainObject(ExclusiveContext* cx, IdValuePair* properties, size_
RootedObjectGroup group(cx, p->value().group);
+ // Watch for existing groups which now use an unboxed layout.
+ if (group->maybeUnboxedLayout()) {
+ MOZ_ASSERT(group->unboxedLayout().properties().length() == nproperties);
+ return UnboxedPlainObject::createWithProperties(cx, group, newKind, properties);
+ }
+
// Update property types according to the properties we are about to add.
// Do this before we do anything which can GC, which might move or remove
// this table entry.
@@ -1378,6 +1506,18 @@ ObjectGroup::allocationSiteGroup(JSContext* cx, JSScript* scriptArg, jsbytecode*
}
}
+ if (kind == JSProto_Array &&
+ (JSOp(*pc) == JSOP_NEWARRAY || IsCallPC(pc)) &&
+ cx->options().unboxedArrays())
+ {
+ PreliminaryObjectArrayWithTemplate* preliminaryObjects =
+ cx->new_<PreliminaryObjectArrayWithTemplate>(nullptr);
+ if (preliminaryObjects)
+ res->setPreliminaryObjects(preliminaryObjects);
+ else
+ cx->recoverFromOutOfMemory();
+ }
+
if (!table->add(p, key, res)) {
ReportOutOfMemory(cx);
return nullptr;
diff --git a/js/src/vm/ObjectGroup.h b/js/src/vm/ObjectGroup.h
index 0b6eaee51..4e24de9f1 100644
--- a/js/src/vm/ObjectGroup.h
+++ b/js/src/vm/ObjectGroup.h
@@ -20,6 +20,7 @@
namespace js {
class TypeDescr;
+class UnboxedLayout;
class PreliminaryObjectArrayWithTemplate;
class TypeNewScript;
@@ -153,6 +154,16 @@ class ObjectGroup : public gc::TenuredCell
// For some plain objects, the addendum stores a PreliminaryObjectArrayWithTemplate.
Addendum_PreliminaryObjects,
+ // When objects in this group have an unboxed representation, the
+ // addendum stores an UnboxedLayout (which might have a TypeNewScript
+ // 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 and/or have the same allocation kind as such
+ // objects, the addendum points to that unboxed group.
+ Addendum_OriginalUnboxedGroup,
+
// When used by typed objects, the addendum stores a TypeDescr.
Addendum_TypeDescr
};
@@ -174,6 +185,7 @@ class ObjectGroup : public gc::TenuredCell
return nullptr;
}
+ TypeNewScript* anyNewScript();
void detachNewScript(bool writeBarrier, ObjectGroup* replacement);
ObjectGroupFlags flagsDontCheckGeneration() const {
@@ -213,6 +225,34 @@ class ObjectGroup : public gc::TenuredCell
maybePreliminaryObjectsDontCheckGeneration();
}
+ inline UnboxedLayout* maybeUnboxedLayout();
+ inline UnboxedLayout& unboxedLayout();
+
+ UnboxedLayout* maybeUnboxedLayoutDontCheckGeneration() const {
+ if (addendumKind() == Addendum_UnboxedLayout)
+ return reinterpret_cast<UnboxedLayout*>(addendum_);
+ return nullptr;
+ }
+
+ UnboxedLayout& unboxedLayoutDontCheckGeneration() const {
+ MOZ_ASSERT(addendumKind() == Addendum_UnboxedLayout);
+ return *maybeUnboxedLayoutDontCheckGeneration();
+ }
+
+ void setUnboxedLayout(UnboxedLayout* layout) {
+ 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.
@@ -273,8 +313,9 @@ class ObjectGroup : public gc::TenuredCell
* that can be read out of that property in actual JS objects. In native
* objects, property types account for plain data properties (those with a
* slot and no getter or setter hook) and dense elements. In typed objects
- * property types account for object and value properties and elements in
- * the object.
+ * and unboxed objects, property types account for object and value
+ * properties and elements in the object, and expando properties in unboxed
+ * objects.
*
* For accesses on these properties, the correspondence is as follows:
*
@@ -297,9 +338,10 @@ class ObjectGroup : public gc::TenuredCell
* 2. Array lengths are special cased by the compiler and VM and are not
* reflected in property types.
*
- * 3. In typed objects, the initial values of properties (null pointers and
- * undefined values) are not reflected in the property types. These
- * values are always possible when reading the property.
+ * 3. In typed objects (but not unboxed objects), the initial values of
+ * properties (null pointers and undefined values) are not reflected in
+ * the property types. These values are always possible when reading the
+ * property.
*
* We establish these by using write barriers on calls to setProperty and
* defineProperty which are on native properties, and on any jitcode which
@@ -413,6 +455,12 @@ class ObjectGroup : public gc::TenuredCell
return &flags_;
}
+ // Get the bit pattern stored in an object's addendum when it has an
+ // original unboxed group.
+ static inline int32_t addendumOriginalUnboxedGroupValue() {
+ return Addendum_OriginalUnboxedGroup << OBJECT_FLAG_ADDENDUM_SHIFT;
+ }
+
inline uint32_t basePropertyCount();
private:
@@ -457,14 +505,14 @@ class ObjectGroup : public gc::TenuredCell
UnknownIndex // Make an array with an unknown element type.
};
- // Create an ArrayObject with the specified elements and a group specialized
- // for the elements.
- static ArrayObject* newArrayObject(ExclusiveContext* cx, const Value* vp, size_t length,
- NewObjectKind newKind,
- NewArrayKind arrayKind = NewArrayKind::Normal);
+ // Create an ArrayObject or UnboxedArrayObject with the specified elements
+ // and a group specialized for the elements.
+ static JSObject* newArrayObject(ExclusiveContext* cx, const Value* vp, size_t length,
+ NewObjectKind newKind,
+ NewArrayKind arrayKind = NewArrayKind::Normal);
- // Create a PlainObject with the specified properties and a group specialized
- // for those properties.
+ // Create a PlainObject or UnboxedPlainObject with the specified properties
+ // and a group specialized for those properties.
static JSObject* newPlainObject(ExclusiveContext* cx,
IdValuePair* properties, size_t nproperties,
NewObjectKind newKind);
diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h
index e13cd6221..b26336815 100644
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -1281,7 +1281,17 @@
* Stack: receiver, obj, propval => obj[propval]
*/ \
macro(JSOP_GETELEM_SUPER, 125, "getelem-super", NULL, 1, 3, 1, JOF_BYTE |JOF_ELEM|JOF_LEFTASSOC) \
- macro(JSOP_UNUSED126, 126, "unused126", NULL, 5, 0, 1, JOF_UINT32) \
+ /*
+ * Pushes newly created array for a spread call onto the stack. This has
+ * the same semantics as JSOP_NEWARRAY, but is distinguished to avoid
+ * using unboxed arrays in spread calls, which would make compiling spread
+ * calls in baseline more complex.
+ * Category: Literals
+ * Type: Array
+ * Operands: uint32_t length
+ * Stack: => obj
+ */ \
+ macro(JSOP_SPREADCALLARRAY, 126, "spreadcallarray", NULL, 5, 0, 1, JOF_UINT32) \
\
/*
* Defines the given function on the current scope.
diff --git a/js/src/vm/ReceiverGuard.cpp b/js/src/vm/ReceiverGuard.cpp
index e95e8a208..97df908c3 100644
--- a/js/src/vm/ReceiverGuard.cpp
+++ b/js/src/vm/ReceiverGuard.cpp
@@ -7,6 +7,7 @@
#include "vm/ReceiverGuard.h"
#include "builtin/TypedObject.h"
+#include "vm/UnboxedObject.h"
#include "jsobjinlines.h"
using namespace js;
@@ -15,7 +16,11 @@ ReceiverGuard::ReceiverGuard(JSObject* obj)
: group(nullptr), shape(nullptr)
{
if (obj) {
- if (obj->is<TypedObject>()) {
+ if (obj->is<UnboxedPlainObject>()) {
+ group = obj->group();
+ if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando())
+ shape = expando->lastProperty();
+ } else if (obj->is<UnboxedArrayObject>() || obj->is<TypedObject>()) {
group = obj->group();
} else {
shape = obj->maybeShape();
@@ -28,7 +33,9 @@ ReceiverGuard::ReceiverGuard(ObjectGroup* group, Shape* shape)
{
if (group) {
const Class* clasp = group->clasp();
- if (IsTypedObjectClass(clasp)) {
+ if (clasp == &UnboxedPlainObject::class_) {
+ // Keep both group and shape.
+ } else if (clasp == &UnboxedArrayObject::class_ || IsTypedObjectClass(clasp)) {
this->shape = nullptr;
} else {
this->group = nullptr;
@@ -39,8 +46,12 @@ ReceiverGuard::ReceiverGuard(ObjectGroup* group, Shape* shape)
/* static */ int32_t
HeapReceiverGuard::keyBits(JSObject* obj)
{
- if (obj->is<TypedObject>()) {
- // Only the group needs to be guarded for typed objects.
+ if (obj->is<UnboxedPlainObject>()) {
+ // Both the group and shape need to be guarded for unboxed plain objects.
+ return obj->as<UnboxedPlainObject>().maybeExpando() ? 0 : 1;
+ }
+ if (obj->is<UnboxedArrayObject>() || obj->is<TypedObject>()) {
+ // Only the group needs to be guarded for unboxed arrays and typed objects.
return 2;
}
// Other objects only need the shape to be guarded.
diff --git a/js/src/vm/ReceiverGuard.h b/js/src/vm/ReceiverGuard.h
index c14f0d83b..459cc0012 100644
--- a/js/src/vm/ReceiverGuard.h
+++ b/js/src/vm/ReceiverGuard.h
@@ -28,6 +28,11 @@ namespace js {
// TypedObject: The structure of a typed object is determined by its group.
// All typed objects with the same group have the same class, prototype, and
// own properties.
+//
+// UnboxedPlainObject: The structure of an unboxed plain object is determined
+// by its group and its expando object's shape, if there is one. All unboxed
+// plain objects with the same group and expando shape have the same
+// properties except those stored in the expando's dense elements.
class HeapReceiverGuard;
class RootedReceiverGuard;
diff --git a/js/src/vm/RegExpObject.cpp b/js/src/vm/RegExpObject.cpp
index cd0b54c9d..6223fc10d 100644
--- a/js/src/vm/RegExpObject.cpp
+++ b/js/src/vm/RegExpObject.cpp
@@ -299,8 +299,7 @@ RegExpObject::assignInitialShape(ExclusiveContext* cx, Handle<RegExpObject*> sel
JS_STATIC_ASSERT(LAST_INDEX_SLOT == 0);
/* The lastIndex property alone is writable but non-configurable. */
- return NativeObject::addDataProperty(cx, self, cx->names().lastIndex, LAST_INDEX_SLOT,
- JSPROP_PERMANENT);
+ return self->addDataProperty(cx, cx->names().lastIndex, LAST_INDEX_SLOT, JSPROP_PERMANENT);
}
void
diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp
index 95940eeaf..7eb8f4ab8 100644
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -82,7 +82,7 @@ InterpreterFrame::isNonGlobalEvalFrame() const
return isEvalFrame() && script()->bodyScope()->as<EvalScope>().isNonGlobal();
}
-ArrayObject*
+JSObject*
InterpreterFrame::createRestParameter(JSContext* cx)
{
MOZ_ASSERT(script()->hasRest());
diff --git a/js/src/vm/Stack.h b/js/src/vm/Stack.h
index fe04a00f2..d08544213 100644
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -523,7 +523,7 @@ class InterpreterFrame
ArgumentsObject& argsObj() const;
void initArgsObj(ArgumentsObject& argsobj);
- ArrayObject* createRestParameter(JSContext* cx);
+ JSObject* createRestParameter(JSContext* cx);
/*
* Environment chain
diff --git a/js/src/vm/TypeInference-inl.h b/js/src/vm/TypeInference-inl.h
index 2af252cea..da47fa898 100644
--- a/js/src/vm/TypeInference-inl.h
+++ b/js/src/vm/TypeInference-inl.h
@@ -23,6 +23,7 @@
#include "vm/SharedArrayObject.h"
#include "vm/StringObject.h"
#include "vm/TypedArrayObject.h"
+#include "vm/UnboxedObject.h"
#include "jscntxtinlines.h"
@@ -284,6 +285,10 @@ TypeIdString(jsid id)
*/
struct AutoEnterAnalysis
{
+ // For use when initializing an UnboxedLayout. The UniquePtr's destructor
+ // must run when GC is not suppressed.
+ UniquePtr<UnboxedLayout> unboxedLayoutToCleanUp;
+
// Prevent GC activity in the middle of analysis.
gc::AutoSuppressGC suppressGC;
diff --git a/js/src/vm/TypeInference.cpp b/js/src/vm/TypeInference.cpp
index 2b1fa0e3b..438188a36 100644
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -35,6 +35,7 @@
#include "vm/Opcodes.h"
#include "vm/Shape.h"
#include "vm/Time.h"
+#include "vm/UnboxedObject.h"
#include "jsatominlines.h"
#include "jsscriptinlines.h"
@@ -296,6 +297,9 @@ js::ObjectGroupHasProperty(JSContext* cx, ObjectGroup* group, jsid id, const Val
return true;
}
}
+ JSObject* obj = &value.toObject();
+ if (!obj->hasLazyGroup() && obj->group()->maybeOriginalUnboxedGroup())
+ return true;
}
if (!types->hasType(type)) {
@@ -1944,6 +1948,33 @@ 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; }
+
+ JSCompartment* maybeCompartment() { return nullptr; }
+};
+
} /* anonymous namespace */
void
@@ -1968,6 +1999,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)
{
@@ -2478,6 +2520,8 @@ TemporaryTypeSet::propertyNeedsBarrier(CompilerConstraintList* constraints, jsid
bool
js::ClassCanHaveExtraProperties(const Class* clasp)
{
+ if (clasp == &UnboxedPlainObject::class_ || clasp == &UnboxedArrayObject::class_)
+ return false;
return clasp->getResolve()
|| clasp->getOpsLookupProperty()
|| clasp->getOpsGetProperty()
@@ -2768,6 +2812,15 @@ js::AddTypePropertyId(ExclusiveContext* cx, ObjectGroup* group, JSObject* obj, j
// from acquiring the fully initialized group.
if (group->newScript() && group->newScript()->initializedGroup())
AddTypePropertyId(cx, group->newScript()->initializedGroup(), nullptr, 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(), nullptr, id, type);
+ if (ObjectGroup* unboxedGroup = group->maybeOriginalUnboxedGroup())
+ AddTypePropertyId(cx, unboxedGroup, nullptr, id, type);
}
void
@@ -2839,6 +2892,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
@@ -2871,6 +2930,23 @@ ObjectGroup::markUnknown(ExclusiveContext* cx)
prop->types.setNonDataProperty(cx);
}
}
+
+ if (ObjectGroup* unboxedGroup = maybeOriginalUnboxedGroup())
+ MarkObjectGroupUnknownProperties(cx, unboxedGroup);
+ if (maybeUnboxedLayout() && maybeUnboxedLayout()->nativeGroup())
+ MarkObjectGroupUnknownProperties(cx, maybeUnboxedLayout()->nativeGroup());
+ if (ObjectGroup* unboxedGroup = maybeOriginalUnboxedGroup())
+ MarkObjectGroupUnknownProperties(cx, unboxedGroup);
+}
+
+TypeNewScript*
+ObjectGroup::anyNewScript()
+{
+ if (newScript())
+ return newScript();
+ if (maybeUnboxedLayout())
+ return unboxedLayout().newScript();
+ return nullptr;
}
void
@@ -2880,7 +2956,7 @@ ObjectGroup::detachNewScript(bool writeBarrier, ObjectGroup* replacement)
// analyzed, remove it from the newObjectGroups table so that it will not be
// produced by calling 'new' on the associated function anymore.
// The TypeNewScript is not actually destroyed.
- TypeNewScript* newScript = this->newScript();
+ TypeNewScript* newScript = anyNewScript();
MOZ_ASSERT(newScript);
if (newScript->analyzed()) {
@@ -2899,7 +2975,10 @@ ObjectGroup::detachNewScript(bool writeBarrier, ObjectGroup* replacement)
MOZ_ASSERT(!replacement);
}
- setAddendum(Addendum_None, nullptr, writeBarrier);
+ if (this->newScript())
+ setAddendum(Addendum_None, nullptr, writeBarrier);
+ else
+ unboxedLayout().setNewScript(nullptr, writeBarrier);
}
void
@@ -2910,7 +2989,7 @@ ObjectGroup::maybeClearNewScriptOnOOM()
if (!isMarked())
return;
- TypeNewScript* newScript = this->newScript();
+ TypeNewScript* newScript = anyNewScript();
if (!newScript)
return;
@@ -2925,7 +3004,7 @@ ObjectGroup::maybeClearNewScriptOnOOM()
void
ObjectGroup::clearNewScript(ExclusiveContext* cx, ObjectGroup* replacement /* = nullptr*/)
{
- TypeNewScript* newScript = this->newScript();
+ TypeNewScript* newScript = anyNewScript();
if (!newScript)
return;
@@ -3332,7 +3411,7 @@ JSFunction::setTypeForScriptedFunction(ExclusiveContext* cx, HandleFunction fun,
/////////////////////////////////////////////////////////////////////
void
-PreliminaryObjectArray::registerNewObject(PlainObject* res)
+PreliminaryObjectArray::registerNewObject(JSObject* res)
{
// The preliminary object pointers are weak, and won't be swept properly
// during nursery collections, so the preliminary objects need to be
@@ -3350,7 +3429,7 @@ PreliminaryObjectArray::registerNewObject(PlainObject* res)
}
void
-PreliminaryObjectArray::unregisterObject(PlainObject* obj)
+PreliminaryObjectArray::unregisterObject(JSObject* obj)
{
for (size_t i = 0; i < COUNT; i++) {
if (objects[i] == obj) {
@@ -3390,6 +3469,22 @@ PreliminaryObjectArray::sweep()
for (size_t i = 0; i < COUNT; i++) {
JSObject** ptr = &objects[i];
if (*ptr && IsAboutToBeFinalizedUnbarriered(ptr)) {
+ // Before we clear this reference, change the object's group to the
+ // Object.prototype group. This is done to ensure JSObject::finalize
+ // sees a NativeObject Class even if we change the current group's
+ // Class to one of the unboxed object classes in the meantime. If
+ // the compartment's global is dead, we don't do anything as the
+ // group's Class is not going to change in that case.
+ JSObject* obj = *ptr;
+ GlobalObject* global = obj->compartment()->unsafeUnbarrieredMaybeGlobal();
+ if (global && !obj->isSingleton()) {
+ JSObject* objectProto = GetBuiltinPrototypePure(global, JSProto_Object);
+ obj->setGroup(objectProto->groupRaw());
+ MOZ_ASSERT(obj->is<NativeObject>());
+ MOZ_ASSERT(obj->getClass() == objectProto->getClass());
+ MOZ_ASSERT(!obj->getClass()->hasFinalize());
+ }
+
*ptr = nullptr;
}
}
@@ -3489,11 +3584,17 @@ PreliminaryObjectArrayWithTemplate::maybeAnalyze(ExclusiveContext* cx, ObjectGro
}
}
- // Since the preliminary objects still reflect the template object's
- // properties, and all objects in the future will be created with those
- // properties, the properties can be marked as definitive for objects in
- // the group.
- group->addDefiniteProperties(cx, shape());
+ TryConvertToUnboxedLayout(cx, enter, shape(), group, preliminaryObjects);
+ if (group->maybeUnboxedLayout())
+ return;
+
+ if (shape()) {
+ // We weren't able to use an unboxed layout, but since the preliminary
+ // objects still reflect the template object's properties, and all
+ // objects in the future will be created with those properties, the
+ // properties can be marked as definite for objects in the group.
+ group->addDefiniteProperties(cx, shape());
+ }
}
/////////////////////////////////////////////////////////////////////
@@ -3507,6 +3608,7 @@ TypeNewScript::make(JSContext* cx, ObjectGroup* group, JSFunction* fun)
{
MOZ_ASSERT(cx->zone()->types.activeAnalysis);
MOZ_ASSERT(!group->newScript());
+ MOZ_ASSERT(!group->maybeUnboxedLayout());
// rollbackPartiallyInitializedObjects expects function_ to be
// canonicalized.
@@ -3811,9 +3913,34 @@ TypeNewScript::maybeAnalyze(JSContext* cx, ObjectGroup* group, bool* regenerate,
PodCopy(initializerList, initializerVector.begin(), initializerVector.length());
}
+ // Try to use an unboxed representation for the group.
+ if (!TryConvertToUnboxedLayout(cx, enter, templateObject()->lastProperty(), group, preliminaryObjects))
+ return false;
+
js_delete(preliminaryObjects);
preliminaryObjects = nullptr;
+ if (group->maybeUnboxedLayout()) {
+ // An unboxed layout was constructed for the group, and this has already
+ // been hooked into it.
+ MOZ_ASSERT(group->unboxedLayout().newScript() == this);
+ destroyNewScript.group = nullptr;
+
+ // Clear out the template object, which is not used for TypeNewScripts
+ // with an unboxed layout. Currently it is a mutant object with a
+ // non-native group and native shape, so make it safe for GC by changing
+ // its group to the default for its prototype.
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ ObjectGroup* plainGroup = ObjectGroup::defaultNewGroup(cx, &PlainObject::class_,
+ group->proto());
+ if (!plainGroup)
+ oomUnsafe.crash("TypeNewScript::maybeAnalyze");
+ templateObject_->setGroup(plainGroup);
+ templateObject_ = nullptr;
+
+ return true;
+ }
+
if (prefixShape->slotSpan() == templateObject()->slotSpan()) {
// The definite properties analysis found exactly the properties that
// are held in common by the preliminary objects. No further analysis
@@ -3927,6 +4054,12 @@ TypeNewScript::rollbackPartiallyInitializedObjects(JSContext* cx, ObjectGroup* g
continue;
}
+ if (thisv.toObject().is<UnboxedPlainObject>()) {
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ if (!UnboxedPlainObject::convertToNative(cx, &thisv.toObject()))
+ oomUnsafe.crash("rollbackPartiallyInitializedObjects");
+ }
+
// Found a matching frame.
RootedPlainObject obj(cx, &thisv.toObject().as<PlainObject>());
@@ -4120,6 +4253,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;
@@ -4203,6 +4342,21 @@ ObjectGroup::sweep(AutoClearTypeInferenceStateOnOOM* oom)
Maybe<AutoClearTypeInferenceStateOnOOM> fallbackOOM;
EnsureHasAutoClearTypeInferenceStateOnOOM(oom, zone(), fallbackOOM);
+ if (maybeUnboxedLayout()) {
+ // Remove unboxed layouts that are about to be finalized from the
+ // compartment wide list while we are still on the main thread.
+ ObjectGroup* group = this;
+ if (IsAboutToBeFinalizedUnbarriered(&group))
+ unboxedLayout().detachFromCompartment();
+
+ if (unboxedLayout().newScript())
+ unboxedLayout().newScript()->sweep();
+
+ // Discard constructor code to avoid holding onto ExecutablePools.
+ if (zone()->isGCCompacting())
+ unboxedLayout().setConstructorCode(nullptr);
+ }
+
if (maybePreliminaryObjects())
maybePreliminaryObjects()->sweep();
diff --git a/js/src/vm/TypeInference.h b/js/src/vm/TypeInference.h
index fd021fc96..9ec1bf927 100644
--- a/js/src/vm/TypeInference.h
+++ b/js/src/vm/TypeInference.h
@@ -262,6 +262,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);
@@ -871,8 +872,8 @@ class PreliminaryObjectArray
public:
PreliminaryObjectArray() = default;
- void registerNewObject(PlainObject* res);
- void unregisterObject(PlainObject* obj);
+ void registerNewObject(JSObject* res);
+ void unregisterObject(JSObject* obj);
JSObject* get(size_t i) const {
MOZ_ASSERT(i < COUNT);
diff --git a/js/src/vm/UnboxedObject-inl.h b/js/src/vm/UnboxedObject-inl.h
new file mode 100644
index 000000000..93ad7bf28
--- /dev/null
+++ b/js/src/vm/UnboxedObject-inl.h
@@ -0,0 +1,840 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef vm_UnboxedObject_inl_h
+#define vm_UnboxedObject_inl_h
+
+#include "vm/UnboxedObject.h"
+
+#include "gc/StoreBuffer-inl.h"
+#include "vm/ArrayObject-inl.h"
+#include "vm/NativeObject-inl.h"
+
+namespace js {
+
+static inline Value
+GetUnboxedValue(uint8_t* p, JSValueType type, bool maybeUninitialized)
+{
+ switch (type) {
+ case JSVAL_TYPE_BOOLEAN:
+ return BooleanValue(*p != 0);
+
+ case JSVAL_TYPE_INT32:
+ return Int32Value(*reinterpret_cast<int32_t*>(p));
+
+ case JSVAL_TYPE_DOUBLE: {
+ // During unboxed plain object creation, non-GC thing properties are
+ // left uninitialized. This is normally fine, since the properties will
+ // be filled in shortly, but if they are read before that happens we
+ // need to make sure that doubles are canonical.
+ double d = *reinterpret_cast<double*>(p);
+ if (maybeUninitialized)
+ return DoubleValue(JS::CanonicalizeNaN(d));
+ return DoubleValue(d);
+ }
+
+ case JSVAL_TYPE_STRING:
+ return StringValue(*reinterpret_cast<JSString**>(p));
+
+ case JSVAL_TYPE_OBJECT:
+ return ObjectOrNullValue(*reinterpret_cast<JSObject**>(p));
+
+ default:
+ MOZ_CRASH("Invalid type for unboxed value");
+ }
+}
+
+static inline void
+SetUnboxedValueNoTypeChange(JSObject* unboxedObject,
+ uint8_t* p, JSValueType type, const Value& v,
+ bool preBarrier)
+{
+ switch (type) {
+ case JSVAL_TYPE_BOOLEAN:
+ *p = v.toBoolean();
+ return;
+
+ case JSVAL_TYPE_INT32:
+ *reinterpret_cast<int32_t*>(p) = v.toInt32();
+ return;
+
+ case JSVAL_TYPE_DOUBLE:
+ *reinterpret_cast<double*>(p) = v.toNumber();
+ return;
+
+ case JSVAL_TYPE_STRING: {
+ MOZ_ASSERT(!IsInsideNursery(v.toString()));
+ JSString** np = reinterpret_cast<JSString**>(p);
+ if (preBarrier)
+ JSString::writeBarrierPre(*np);
+ *np = v.toString();
+ return;
+ }
+
+ case JSVAL_TYPE_OBJECT: {
+ JSObject** np = reinterpret_cast<JSObject**>(p);
+
+ // Manually trigger post barriers on the whole object. If we treat
+ // the pointer as a HeapPtrObject we will get confused later if the
+ // object is converted to its native representation.
+ JSObject* obj = v.toObjectOrNull();
+ if (IsInsideNursery(obj) && !IsInsideNursery(unboxedObject)) {
+ JSRuntime* rt = unboxedObject->runtimeFromMainThread();
+ rt->gc.storeBuffer.putWholeCell(unboxedObject);
+ }
+
+ if (preBarrier)
+ JSObject::writeBarrierPre(*np);
+ *np = obj;
+ return;
+ }
+
+ default:
+ MOZ_CRASH("Invalid type for unboxed value");
+ }
+}
+
+static inline bool
+SetUnboxedValue(ExclusiveContext* cx, JSObject* unboxedObject, jsid id,
+ uint8_t* p, JSValueType type, const Value& v, bool preBarrier)
+{
+ switch (type) {
+ case JSVAL_TYPE_BOOLEAN:
+ if (v.isBoolean()) {
+ *p = v.toBoolean();
+ return true;
+ }
+ return false;
+
+ case JSVAL_TYPE_INT32:
+ if (v.isInt32()) {
+ *reinterpret_cast<int32_t*>(p) = v.toInt32();
+ return true;
+ }
+ return false;
+
+ case JSVAL_TYPE_DOUBLE:
+ if (v.isNumber()) {
+ *reinterpret_cast<double*>(p) = v.toNumber();
+ return true;
+ }
+ return false;
+
+ case JSVAL_TYPE_STRING:
+ if (v.isString()) {
+ MOZ_ASSERT(!IsInsideNursery(v.toString()));
+ JSString** np = reinterpret_cast<JSString**>(p);
+ if (preBarrier)
+ JSString::writeBarrierPre(*np);
+ *np = v.toString();
+ return true;
+ }
+ return false;
+
+ case JSVAL_TYPE_OBJECT:
+ if (v.isObjectOrNull()) {
+ JSObject** np = reinterpret_cast<JSObject**>(p);
+
+ // Update property types when writing object properties. Types for
+ // other properties were captured when the unboxed layout was
+ // created.
+ AddTypePropertyId(cx, unboxedObject, id, v);
+
+ // As above, trigger post barriers on the whole object.
+ JSObject* obj = v.toObjectOrNull();
+ if (IsInsideNursery(v.toObjectOrNull()) && !IsInsideNursery(unboxedObject)) {
+ JSRuntime* rt = unboxedObject->runtimeFromMainThread();
+ rt->gc.storeBuffer.putWholeCell(unboxedObject);
+ }
+
+ if (preBarrier)
+ JSObject::writeBarrierPre(*np);
+ *np = obj;
+ return true;
+ }
+ return false;
+
+ default:
+ MOZ_CRASH("Invalid type for unboxed value");
+ }
+}
+
+/////////////////////////////////////////////////////////////////////
+// UnboxedPlainObject
+/////////////////////////////////////////////////////////////////////
+
+inline const UnboxedLayout&
+UnboxedPlainObject::layout() const
+{
+ return group()->unboxedLayout();
+}
+
+/////////////////////////////////////////////////////////////////////
+// UnboxedArrayObject
+/////////////////////////////////////////////////////////////////////
+
+inline const UnboxedLayout&
+UnboxedArrayObject::layout() const
+{
+ return group()->unboxedLayout();
+}
+
+inline void
+UnboxedArrayObject::setLength(ExclusiveContext* cx, uint32_t length)
+{
+ if (length > INT32_MAX) {
+ // Track objects with overflowing lengths in type information.
+ MarkObjectGroupFlags(cx, this, OBJECT_FLAG_LENGTH_OVERFLOW);
+ }
+
+ length_ = length;
+}
+
+inline void
+UnboxedArrayObject::setInitializedLength(uint32_t initlen)
+{
+ if (initlen < initializedLength()) {
+ switch (elementType()) {
+ case JSVAL_TYPE_STRING:
+ for (size_t i = initlen; i < initializedLength(); i++)
+ triggerPreBarrier<JSVAL_TYPE_STRING>(i);
+ break;
+ case JSVAL_TYPE_OBJECT:
+ for (size_t i = initlen; i < initializedLength(); i++)
+ triggerPreBarrier<JSVAL_TYPE_OBJECT>(i);
+ break;
+ default:
+ MOZ_ASSERT(!UnboxedTypeNeedsPreBarrier(elementType()));
+ }
+ }
+ setInitializedLengthNoBarrier(initlen);
+}
+
+template <JSValueType Type>
+inline bool
+UnboxedArrayObject::setElementSpecific(ExclusiveContext* cx, size_t index, const Value& v)
+{
+ MOZ_ASSERT(index < initializedLength());
+ MOZ_ASSERT(Type == elementType());
+ uint8_t* p = elements() + index * UnboxedTypeSize(Type);
+ return SetUnboxedValue(cx, this, JSID_VOID, p, elementType(), v, /* preBarrier = */ true);
+}
+
+template <JSValueType Type>
+inline void
+UnboxedArrayObject::setElementNoTypeChangeSpecific(size_t index, const Value& v)
+{
+ MOZ_ASSERT(index < initializedLength());
+ MOZ_ASSERT(Type == elementType());
+ uint8_t* p = elements() + index * UnboxedTypeSize(Type);
+ return SetUnboxedValueNoTypeChange(this, p, elementType(), v, /* preBarrier = */ true);
+}
+
+template <JSValueType Type>
+inline bool
+UnboxedArrayObject::initElementSpecific(ExclusiveContext* cx, size_t index, const Value& v)
+{
+ MOZ_ASSERT(index < initializedLength());
+ MOZ_ASSERT(Type == elementType());
+ uint8_t* p = elements() + index * UnboxedTypeSize(Type);
+ return SetUnboxedValue(cx, this, JSID_VOID, p, elementType(), v, /* preBarrier = */ false);
+}
+
+template <JSValueType Type>
+inline void
+UnboxedArrayObject::initElementNoTypeChangeSpecific(size_t index, const Value& v)
+{
+ MOZ_ASSERT(index < initializedLength());
+ MOZ_ASSERT(Type == elementType());
+ uint8_t* p = elements() + index * UnboxedTypeSize(Type);
+ return SetUnboxedValueNoTypeChange(this, p, elementType(), v, /* preBarrier = */ false);
+}
+
+template <JSValueType Type>
+inline Value
+UnboxedArrayObject::getElementSpecific(size_t index)
+{
+ MOZ_ASSERT(index < initializedLength());
+ MOZ_ASSERT(Type == elementType());
+ uint8_t* p = elements() + index * UnboxedTypeSize(Type);
+ return GetUnboxedValue(p, Type, /* maybeUninitialized = */ false);
+}
+
+template <JSValueType Type>
+inline void
+UnboxedArrayObject::triggerPreBarrier(size_t index)
+{
+ MOZ_ASSERT(UnboxedTypeNeedsPreBarrier(Type));
+
+ uint8_t* p = elements() + index * UnboxedTypeSize(Type);
+
+ switch (Type) {
+ case JSVAL_TYPE_STRING: {
+ JSString** np = reinterpret_cast<JSString**>(p);
+ JSString::writeBarrierPre(*np);
+ break;
+ }
+
+ case JSVAL_TYPE_OBJECT: {
+ JSObject** np = reinterpret_cast<JSObject**>(p);
+ JSObject::writeBarrierPre(*np);
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Bad type");
+ }
+}
+
+/////////////////////////////////////////////////////////////////////
+// Combined methods for NativeObject and UnboxedArrayObject accesses.
+/////////////////////////////////////////////////////////////////////
+
+static inline bool
+HasAnyBoxedOrUnboxedDenseElements(JSObject* obj)
+{
+ return obj->isNative() || obj->is<UnboxedArrayObject>();
+}
+
+static inline size_t
+GetAnyBoxedOrUnboxedInitializedLength(JSObject* obj)
+{
+ if (obj->isNative())
+ return obj->as<NativeObject>().getDenseInitializedLength();
+ if (obj->is<UnboxedArrayObject>())
+ return obj->as<UnboxedArrayObject>().initializedLength();
+ return 0;
+}
+
+static inline size_t
+GetAnyBoxedOrUnboxedCapacity(JSObject* obj)
+{
+ if (obj->isNative())
+ return obj->as<NativeObject>().getDenseCapacity();
+ if (obj->is<UnboxedArrayObject>())
+ return obj->as<UnboxedArrayObject>().capacity();
+ return 0;
+}
+
+static inline Value
+GetAnyBoxedOrUnboxedDenseElement(JSObject* obj, size_t index)
+{
+ if (obj->isNative())
+ return obj->as<NativeObject>().getDenseElement(index);
+ return obj->as<UnboxedArrayObject>().getElement(index);
+}
+
+static inline size_t
+GetAnyBoxedOrUnboxedArrayLength(JSObject* obj)
+{
+ if (obj->is<ArrayObject>())
+ return obj->as<ArrayObject>().length();
+ return obj->as<UnboxedArrayObject>().length();
+}
+
+static inline void
+SetAnyBoxedOrUnboxedArrayLength(JSContext* cx, JSObject* obj, size_t length)
+{
+ if (obj->is<ArrayObject>()) {
+ MOZ_ASSERT(length >= obj->as<ArrayObject>().length());
+ obj->as<ArrayObject>().setLength(cx, length);
+ } else {
+ MOZ_ASSERT(length >= obj->as<UnboxedArrayObject>().length());
+ obj->as<UnboxedArrayObject>().setLength(cx, length);
+ }
+}
+
+static inline bool
+SetAnyBoxedOrUnboxedDenseElement(JSContext* cx, JSObject* obj, size_t index, const Value& value)
+{
+ if (obj->isNative()) {
+ obj->as<NativeObject>().setDenseElementWithType(cx, index, value);
+ return true;
+ }
+ return obj->as<UnboxedArrayObject>().setElement(cx, index, value);
+}
+
+static inline bool
+InitAnyBoxedOrUnboxedDenseElement(JSContext* cx, JSObject* obj, size_t index, const Value& value)
+{
+ if (obj->isNative()) {
+ obj->as<NativeObject>().initDenseElementWithType(cx, index, value);
+ return true;
+ }
+ return obj->as<UnboxedArrayObject>().initElement(cx, index, value);
+}
+
+/////////////////////////////////////////////////////////////////////
+// Template methods for NativeObject and UnboxedArrayObject accesses.
+/////////////////////////////////////////////////////////////////////
+
+static inline JSValueType
+GetBoxedOrUnboxedType(JSObject* obj)
+{
+ if (obj->isNative())
+ return JSVAL_TYPE_MAGIC;
+ return obj->as<UnboxedArrayObject>().elementType();
+}
+
+template <JSValueType Type>
+static inline bool
+HasBoxedOrUnboxedDenseElements(JSObject* obj)
+{
+ if (Type == JSVAL_TYPE_MAGIC)
+ return obj->isNative();
+ return obj->is<UnboxedArrayObject>() && obj->as<UnboxedArrayObject>().elementType() == Type;
+}
+
+template <JSValueType Type>
+static inline size_t
+GetBoxedOrUnboxedInitializedLength(JSObject* obj)
+{
+ if (Type == JSVAL_TYPE_MAGIC)
+ return obj->as<NativeObject>().getDenseInitializedLength();
+ return obj->as<UnboxedArrayObject>().initializedLength();
+}
+
+template <JSValueType Type>
+static inline DenseElementResult
+SetBoxedOrUnboxedInitializedLength(JSContext* cx, JSObject* obj, size_t initlen)
+{
+ size_t oldInitlen = GetBoxedOrUnboxedInitializedLength<Type>(obj);
+ if (Type == JSVAL_TYPE_MAGIC) {
+ obj->as<NativeObject>().setDenseInitializedLength(initlen);
+ if (initlen < oldInitlen)
+ obj->as<NativeObject>().shrinkElements(cx, initlen);
+ } else {
+ obj->as<UnboxedArrayObject>().setInitializedLength(initlen);
+ if (initlen < oldInitlen)
+ obj->as<UnboxedArrayObject>().shrinkElements(cx, initlen);
+ }
+ return DenseElementResult::Success;
+}
+
+template <JSValueType Type>
+static inline size_t
+GetBoxedOrUnboxedCapacity(JSObject* obj)
+{
+ if (Type == JSVAL_TYPE_MAGIC)
+ return obj->as<NativeObject>().getDenseCapacity();
+ return obj->as<UnboxedArrayObject>().capacity();
+}
+
+template <JSValueType Type>
+static inline Value
+GetBoxedOrUnboxedDenseElement(JSObject* obj, size_t index)
+{
+ if (Type == JSVAL_TYPE_MAGIC)
+ return obj->as<NativeObject>().getDenseElement(index);
+ return obj->as<UnboxedArrayObject>().getElementSpecific<Type>(index);
+}
+
+template <JSValueType Type>
+static inline void
+SetBoxedOrUnboxedDenseElementNoTypeChange(JSObject* obj, size_t index, const Value& value)
+{
+ if (Type == JSVAL_TYPE_MAGIC)
+ obj->as<NativeObject>().setDenseElement(index, value);
+ else
+ obj->as<UnboxedArrayObject>().setElementNoTypeChangeSpecific<Type>(index, value);
+}
+
+template <JSValueType Type>
+static inline bool
+SetBoxedOrUnboxedDenseElement(JSContext* cx, JSObject* obj, size_t index, const Value& value)
+{
+ if (Type == JSVAL_TYPE_MAGIC) {
+ obj->as<NativeObject>().setDenseElementWithType(cx, index, value);
+ return true;
+ }
+ return obj->as<UnboxedArrayObject>().setElementSpecific<Type>(cx, index, value);
+}
+
+template <JSValueType Type>
+static inline DenseElementResult
+EnsureBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj, size_t count)
+{
+ if (Type == JSVAL_TYPE_MAGIC) {
+ if (!obj->as<ArrayObject>().ensureElements(cx, count))
+ return DenseElementResult::Failure;
+ } else {
+ if (obj->as<UnboxedArrayObject>().capacity() < count) {
+ if (!obj->as<UnboxedArrayObject>().growElements(cx, count))
+ return DenseElementResult::Failure;
+ }
+ }
+ return DenseElementResult::Success;
+}
+
+template <JSValueType Type>
+static inline DenseElementResult
+SetOrExtendBoxedOrUnboxedDenseElements(ExclusiveContext* cx, JSObject* obj,
+ uint32_t start, const Value* vp, uint32_t count,
+ ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update)
+{
+ if (Type == JSVAL_TYPE_MAGIC) {
+ NativeObject* nobj = &obj->as<NativeObject>();
+
+ if (nobj->denseElementsAreFrozen())
+ return DenseElementResult::Incomplete;
+
+ if (obj->is<ArrayObject>() &&
+ !obj->as<ArrayObject>().lengthIsWritable() &&
+ start + count >= obj->as<ArrayObject>().length())
+ {
+ return DenseElementResult::Incomplete;
+ }
+
+ DenseElementResult result = nobj->ensureDenseElements(cx, start, count);
+ if (result != DenseElementResult::Success)
+ return result;
+
+ if (obj->is<ArrayObject>() && start + count >= obj->as<ArrayObject>().length())
+ obj->as<ArrayObject>().setLengthInt32(start + count);
+
+ if (updateTypes == ShouldUpdateTypes::DontUpdate && !nobj->shouldConvertDoubleElements()) {
+ nobj->copyDenseElements(start, vp, count);
+ } else {
+ for (size_t i = 0; i < count; i++)
+ nobj->setDenseElementWithType(cx, start + i, vp[i]);
+ }
+
+ return DenseElementResult::Success;
+ }
+
+ UnboxedArrayObject* nobj = &obj->as<UnboxedArrayObject>();
+
+ if (start > nobj->initializedLength())
+ return DenseElementResult::Incomplete;
+
+ if (start + count >= UnboxedArrayObject::MaximumCapacity)
+ return DenseElementResult::Incomplete;
+
+ if (start + count > nobj->capacity() && !nobj->growElements(cx, start + count))
+ return DenseElementResult::Failure;
+
+ size_t oldInitlen = nobj->initializedLength();
+
+ // Overwrite any existing elements covered by the new range. If we fail
+ // after this point due to some incompatible type being written to the
+ // object's elements, afterwards the contents will be different from when
+ // we started. The caller must retry the operation using a generic path,
+ // which will overwrite the already-modified elements as well as the ones
+ // that were left alone.
+ size_t i = 0;
+ if (updateTypes == ShouldUpdateTypes::DontUpdate) {
+ for (size_t j = start; i < count && j < oldInitlen; i++, j++)
+ nobj->setElementNoTypeChangeSpecific<Type>(j, vp[i]);
+ } else {
+ for (size_t j = start; i < count && j < oldInitlen; i++, j++) {
+ if (!nobj->setElementSpecific<Type>(cx, j, vp[i]))
+ return DenseElementResult::Incomplete;
+ }
+ }
+
+ if (i != count) {
+ obj->as<UnboxedArrayObject>().setInitializedLength(start + count);
+ if (updateTypes == ShouldUpdateTypes::DontUpdate) {
+ for (; i < count; i++)
+ nobj->initElementNoTypeChangeSpecific<Type>(start + i, vp[i]);
+ } else {
+ for (; i < count; i++) {
+ if (!nobj->initElementSpecific<Type>(cx, start + i, vp[i])) {
+ nobj->setInitializedLengthNoBarrier(oldInitlen);
+ return DenseElementResult::Incomplete;
+ }
+ }
+ }
+ }
+
+ if (start + count >= nobj->length())
+ nobj->setLength(cx, start + count);
+
+ return DenseElementResult::Success;
+}
+
+template <JSValueType Type>
+static inline DenseElementResult
+MoveBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj, uint32_t dstStart, uint32_t srcStart,
+ uint32_t length)
+{
+ MOZ_ASSERT(HasBoxedOrUnboxedDenseElements<Type>(obj));
+
+ if (Type == JSVAL_TYPE_MAGIC) {
+ if (obj->as<NativeObject>().denseElementsAreFrozen())
+ return DenseElementResult::Incomplete;
+
+ if (!obj->as<NativeObject>().maybeCopyElementsForWrite(cx))
+ return DenseElementResult::Failure;
+ obj->as<NativeObject>().moveDenseElements(dstStart, srcStart, length);
+ } else {
+ uint8_t* data = obj->as<UnboxedArrayObject>().elements();
+ size_t elementSize = UnboxedTypeSize(Type);
+
+ if (UnboxedTypeNeedsPreBarrier(Type) &&
+ JS::shadow::Zone::asShadowZone(obj->zone())->needsIncrementalBarrier())
+ {
+ // Trigger pre barriers on any elements we are overwriting. See
+ // NativeObject::moveDenseElements. No post barrier is needed as
+ // only whole cell post barriers are used with unboxed objects.
+ for (size_t i = 0; i < length; i++)
+ obj->as<UnboxedArrayObject>().triggerPreBarrier<Type>(dstStart + i);
+ }
+
+ memmove(data + dstStart * elementSize,
+ data + srcStart * elementSize,
+ length * elementSize);
+ }
+
+ return DenseElementResult::Success;
+}
+
+template <JSValueType DstType, JSValueType SrcType>
+static inline DenseElementResult
+CopyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* dst, JSObject* src,
+ uint32_t dstStart, uint32_t srcStart, uint32_t length)
+{
+ MOZ_ASSERT(HasBoxedOrUnboxedDenseElements<SrcType>(src));
+ MOZ_ASSERT(HasBoxedOrUnboxedDenseElements<DstType>(dst));
+ MOZ_ASSERT(GetBoxedOrUnboxedInitializedLength<DstType>(dst) == dstStart);
+ MOZ_ASSERT(GetBoxedOrUnboxedInitializedLength<SrcType>(src) >= srcStart + length);
+ MOZ_ASSERT(GetBoxedOrUnboxedCapacity<DstType>(dst) >= dstStart + length);
+
+ SetBoxedOrUnboxedInitializedLength<DstType>(cx, dst, dstStart + length);
+
+ if (DstType == JSVAL_TYPE_MAGIC) {
+ if (SrcType == JSVAL_TYPE_MAGIC) {
+ const Value* vp = src->as<NativeObject>().getDenseElements() + srcStart;
+ dst->as<NativeObject>().initDenseElements(dstStart, vp, length);
+ } else {
+ for (size_t i = 0; i < length; i++) {
+ Value v = GetBoxedOrUnboxedDenseElement<SrcType>(src, srcStart + i);
+ dst->as<NativeObject>().initDenseElement(dstStart + i, v);
+ }
+ }
+ } else if (DstType == SrcType) {
+ uint8_t* dstData = dst->as<UnboxedArrayObject>().elements();
+ uint8_t* srcData = src->as<UnboxedArrayObject>().elements();
+ size_t elementSize = UnboxedTypeSize(DstType);
+
+ memcpy(dstData + dstStart * elementSize,
+ srcData + srcStart * elementSize,
+ length * elementSize);
+
+ // Add a store buffer entry if we might have copied a nursery pointer to dst.
+ if (UnboxedTypeNeedsPostBarrier(DstType) && !IsInsideNursery(dst))
+ dst->runtimeFromMainThread()->gc.storeBuffer.putWholeCell(dst);
+ } else if (DstType == JSVAL_TYPE_DOUBLE && SrcType == JSVAL_TYPE_INT32) {
+ uint8_t* dstData = dst->as<UnboxedArrayObject>().elements();
+ uint8_t* srcData = src->as<UnboxedArrayObject>().elements();
+
+ for (size_t i = 0; i < length; i++) {
+ int32_t v = *reinterpret_cast<int32_t*>(srcData + (srcStart + i) * sizeof(int32_t));
+ *reinterpret_cast<double*>(dstData + (dstStart + i) * sizeof(double)) = v;
+ }
+ } else {
+ for (size_t i = 0; i < length; i++) {
+ Value v = GetBoxedOrUnboxedDenseElement<SrcType>(src, srcStart + i);
+ dst->as<UnboxedArrayObject>().initElementNoTypeChangeSpecific<DstType>(dstStart + i, v);
+ }
+ }
+
+ return DenseElementResult::Success;
+}
+
+/////////////////////////////////////////////////////////////////////
+// Dispatch to specialized methods based on the type of an object.
+/////////////////////////////////////////////////////////////////////
+
+// Goop to fix MSVC. See DispatchTraceKindTyped in TraceKind.h.
+// The clang-cl front end defines _MSC_VER, but still requires the explicit
+// template declaration, so we must test for __clang__ here as well.
+#if defined(_MSC_VER) && !defined(__clang__)
+# define DEPENDENT_TEMPLATE_HINT
+#else
+# define DEPENDENT_TEMPLATE_HINT template
+#endif
+
+// Function to dispatch a method specialized to whatever boxed or unboxed dense
+// elements which an input object has.
+template <typename F>
+DenseElementResult
+CallBoxedOrUnboxedSpecialization(F f, JSObject* obj)
+{
+ if (!HasAnyBoxedOrUnboxedDenseElements(obj))
+ return DenseElementResult::Incomplete;
+ switch (GetBoxedOrUnboxedType(obj)) {
+ case JSVAL_TYPE_MAGIC:
+ return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_MAGIC>();
+ case JSVAL_TYPE_BOOLEAN:
+ return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_BOOLEAN>();
+ case JSVAL_TYPE_INT32:
+ return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_INT32>();
+ case JSVAL_TYPE_DOUBLE:
+ return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_DOUBLE>();
+ case JSVAL_TYPE_STRING:
+ return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_STRING>();
+ case JSVAL_TYPE_OBJECT:
+ return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_OBJECT>();
+ default:
+ MOZ_CRASH();
+ }
+}
+
+// As above, except the specialization can reflect the unboxed type of two objects.
+template <typename F>
+DenseElementResult
+CallBoxedOrUnboxedSpecialization(F f, JSObject* obj1, JSObject* obj2)
+{
+ if (!HasAnyBoxedOrUnboxedDenseElements(obj1) || !HasAnyBoxedOrUnboxedDenseElements(obj2))
+ return DenseElementResult::Incomplete;
+
+#define SPECIALIZE_OBJ2(TYPE) \
+ switch (GetBoxedOrUnboxedType(obj2)) { \
+ case JSVAL_TYPE_MAGIC: \
+ return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_MAGIC>(); \
+ case JSVAL_TYPE_BOOLEAN: \
+ return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_BOOLEAN>(); \
+ case JSVAL_TYPE_INT32: \
+ return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_INT32>(); \
+ case JSVAL_TYPE_DOUBLE: \
+ return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_DOUBLE>(); \
+ case JSVAL_TYPE_STRING: \
+ return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_STRING>(); \
+ case JSVAL_TYPE_OBJECT: \
+ return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_OBJECT>(); \
+ default: \
+ MOZ_CRASH(); \
+ }
+
+ switch (GetBoxedOrUnboxedType(obj1)) {
+ case JSVAL_TYPE_MAGIC:
+ SPECIALIZE_OBJ2(JSVAL_TYPE_MAGIC)
+ case JSVAL_TYPE_BOOLEAN:
+ SPECIALIZE_OBJ2(JSVAL_TYPE_BOOLEAN)
+ case JSVAL_TYPE_INT32:
+ SPECIALIZE_OBJ2(JSVAL_TYPE_INT32)
+ case JSVAL_TYPE_DOUBLE:
+ SPECIALIZE_OBJ2(JSVAL_TYPE_DOUBLE)
+ case JSVAL_TYPE_STRING:
+ SPECIALIZE_OBJ2(JSVAL_TYPE_STRING)
+ case JSVAL_TYPE_OBJECT:
+ SPECIALIZE_OBJ2(JSVAL_TYPE_OBJECT)
+ default:
+ MOZ_CRASH();
+ }
+
+#undef SPECIALIZE_OBJ2
+}
+
+#undef DEPENDENT_TEMPLATE_HINT
+
+#define DefineBoxedOrUnboxedFunctor1(Signature, A) \
+struct Signature ## Functor { \
+ A a; \
+ explicit Signature ## Functor(A a) \
+ : a(a) \
+ {} \
+ template <JSValueType Type> \
+ DenseElementResult operator()() { \
+ return Signature<Type>(a); \
+ } \
+}
+
+#define DefineBoxedOrUnboxedFunctor3(Signature, A, B, C) \
+struct Signature ## Functor { \
+ A a; B b; C c; \
+ Signature ## Functor(A a, B b, C c) \
+ : a(a), b(b), c(c) \
+ {} \
+ template <JSValueType Type> \
+ DenseElementResult operator()() { \
+ return Signature<Type>(a, b, c); \
+ } \
+}
+
+#define DefineBoxedOrUnboxedFunctor4(Signature, A, B, C, D) \
+struct Signature ## Functor { \
+ A a; B b; C c; D d; \
+ Signature ## Functor(A a, B b, C c, D d) \
+ : a(a), b(b), c(c), d(d) \
+ {} \
+ template <JSValueType Type> \
+ DenseElementResult operator()() { \
+ return Signature<Type>(a, b, c, d); \
+ } \
+}
+
+#define DefineBoxedOrUnboxedFunctorPair4(Signature, A, B, C, D) \
+struct Signature ## Functor { \
+ A a; B b; C c; D d; \
+ Signature ## Functor(A a, B b, C c, D d) \
+ : a(a), b(b), c(c), d(d) \
+ {} \
+ template <JSValueType TypeOne, JSValueType TypeTwo> \
+ DenseElementResult operator()() { \
+ return Signature<TypeOne, TypeTwo>(a, b, c, d); \
+ } \
+}
+
+#define DefineBoxedOrUnboxedFunctor5(Signature, A, B, C, D, E) \
+struct Signature ## Functor { \
+ A a; B b; C c; D d; E e; \
+ Signature ## Functor(A a, B b, C c, D d, E e) \
+ : a(a), b(b), c(c), d(d), e(e) \
+ {} \
+ template <JSValueType Type> \
+ DenseElementResult operator()() { \
+ return Signature<Type>(a, b, c, d, e); \
+ } \
+}
+
+#define DefineBoxedOrUnboxedFunctor6(Signature, A, B, C, D, E, F) \
+struct Signature ## Functor { \
+ A a; B b; C c; D d; E e; F f; \
+ Signature ## Functor(A a, B b, C c, D d, E e, F f) \
+ : a(a), b(b), c(c), d(d), e(e), f(f) \
+ {} \
+ template <JSValueType Type> \
+ DenseElementResult operator()() { \
+ return Signature<Type>(a, b, c, d, e, f); \
+ } \
+}
+
+#define DefineBoxedOrUnboxedFunctorPair6(Signature, A, B, C, D, E, F) \
+struct Signature ## Functor { \
+ A a; B b; C c; D d; E e; F f; \
+ Signature ## Functor(A a, B b, C c, D d, E e, F f) \
+ : a(a), b(b), c(c), d(d), e(e), f(f) \
+ {} \
+ template <JSValueType TypeOne, JSValueType TypeTwo> \
+ DenseElementResult operator()() { \
+ return Signature<TypeOne, TypeTwo>(a, b, c, d, e, f); \
+ } \
+}
+
+DenseElementResult
+SetOrExtendAnyBoxedOrUnboxedDenseElements(ExclusiveContext* cx, JSObject* obj,
+ uint32_t start, const Value* vp, uint32_t count,
+ ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update);
+
+DenseElementResult
+MoveAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj,
+ uint32_t dstStart, uint32_t srcStart, uint32_t length);
+
+DenseElementResult
+CopyAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* dst, JSObject* src,
+ uint32_t dstStart, uint32_t srcStart, uint32_t length);
+
+void
+SetAnyBoxedOrUnboxedInitializedLength(JSContext* cx, JSObject* obj, size_t initlen);
+
+DenseElementResult
+EnsureAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj, size_t count);
+
+} // namespace js
+
+#endif // vm_UnboxedObject_inl_h
diff --git a/js/src/vm/UnboxedObject.cpp b/js/src/vm/UnboxedObject.cpp
new file mode 100644
index 000000000..d8c9c774a
--- /dev/null
+++ b/js/src/vm/UnboxedObject.cpp
@@ -0,0 +1,2132 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "vm/UnboxedObject-inl.h"
+
+#include "jit/BaselineIC.h"
+#include "jit/ExecutableAllocator.h"
+#include "jit/JitCommon.h"
+#include "jit/Linker.h"
+
+#include "jsobjinlines.h"
+
+#include "gc/Nursery-inl.h"
+#include "jit/MacroAssembler-inl.h"
+#include "vm/Shape-inl.h"
+
+using mozilla::ArrayLength;
+using mozilla::DebugOnly;
+using mozilla::PodCopy;
+
+using namespace js;
+
+/////////////////////////////////////////////////////////////////////
+// UnboxedLayout
+/////////////////////////////////////////////////////////////////////
+
+void
+UnboxedLayout::trace(JSTracer* trc)
+{
+ for (size_t i = 0; i < properties_.length(); i++)
+ TraceManuallyBarrieredEdge(trc, &properties_[i].name, "unboxed_layout_name");
+
+ if (newScript())
+ newScript()->trace(trc);
+
+ TraceNullableEdge(trc, &nativeGroup_, "unboxed_layout_nativeGroup");
+ TraceNullableEdge(trc, &nativeShape_, "unboxed_layout_nativeShape");
+ TraceNullableEdge(trc, &allocationScript_, "unboxed_layout_allocationScript");
+ TraceNullableEdge(trc, &replacementGroup_, "unboxed_layout_replacementGroup");
+ TraceNullableEdge(trc, &constructorCode_, "unboxed_layout_constructorCode");
+}
+
+size_t
+UnboxedLayout::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
+{
+ return mallocSizeOf(this)
+ + properties_.sizeOfExcludingThis(mallocSizeOf)
+ + (newScript() ? newScript()->sizeOfIncludingThis(mallocSizeOf) : 0)
+ + mallocSizeOf(traceList());
+}
+
+void
+UnboxedLayout::setNewScript(TypeNewScript* newScript, bool writeBarrier /* = true */)
+{
+ if (newScript_ && writeBarrier)
+ TypeNewScript::writeBarrierPre(newScript_);
+ newScript_ = newScript;
+}
+
+// Constructor code returns a 0x1 value to indicate the constructor code should
+// be cleared.
+static const uintptr_t CLEAR_CONSTRUCTOR_CODE_TOKEN = 0x1;
+
+/* static */ bool
+UnboxedLayout::makeConstructorCode(JSContext* cx, HandleObjectGroup group)
+{
+ gc::AutoSuppressGC suppress(cx);
+
+ using namespace jit;
+
+ if (!cx->compartment()->ensureJitCompartmentExists(cx))
+ return false;
+
+ UnboxedLayout& layout = group->unboxedLayout();
+ MOZ_ASSERT(!layout.constructorCode());
+
+ UnboxedPlainObject* templateObject = UnboxedPlainObject::create(cx, group, TenuredObject);
+ if (!templateObject)
+ return false;
+
+ JitContext jitContext(cx, nullptr);
+
+ MacroAssembler masm;
+
+ Register propertiesReg, newKindReg;
+#ifdef JS_CODEGEN_X86
+ propertiesReg = eax;
+ newKindReg = ecx;
+ masm.loadPtr(Address(masm.getStackPointer(), sizeof(void*)), propertiesReg);
+ masm.loadPtr(Address(masm.getStackPointer(), 2 * sizeof(void*)), newKindReg);
+#else
+ propertiesReg = IntArgReg0;
+ newKindReg = IntArgReg1;
+#endif
+
+#ifdef JS_CODEGEN_ARM64
+ // ARM64 communicates stack address via sp, but uses a pseudo-sp for addressing.
+ masm.initStackPtr();
+#endif
+
+ MOZ_ASSERT(propertiesReg.volatile_());
+ MOZ_ASSERT(newKindReg.volatile_());
+
+ AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All());
+ regs.take(propertiesReg);
+ regs.take(newKindReg);
+ Register object = regs.takeAny(), scratch1 = regs.takeAny(), scratch2 = regs.takeAny();
+
+ LiveGeneralRegisterSet savedNonVolatileRegisters = SavedNonVolatileRegisters(regs);
+ masm.PushRegsInMask(savedNonVolatileRegisters);
+
+ // The scratch double register might be used by MacroAssembler methods.
+ if (ScratchDoubleReg.volatile_())
+ masm.push(ScratchDoubleReg);
+
+ Label failure, tenuredObject, allocated;
+ masm.branch32(Assembler::NotEqual, newKindReg, Imm32(GenericObject), &tenuredObject);
+ masm.branchTest32(Assembler::NonZero, AbsoluteAddress(group->addressOfFlags()),
+ Imm32(OBJECT_FLAG_PRE_TENURE), &tenuredObject);
+
+ // Allocate an object in the nursery
+ masm.createGCObject(object, scratch1, templateObject, gc::DefaultHeap, &failure,
+ /* initFixedSlots = */ false);
+
+ masm.jump(&allocated);
+ masm.bind(&tenuredObject);
+
+ // Allocate an object in the tenured heap.
+ masm.createGCObject(object, scratch1, templateObject, gc::TenuredHeap, &failure,
+ /* initFixedSlots = */ false);
+
+ // If any of the properties being stored are in the nursery, add a store
+ // buffer entry for the new object.
+ Label postBarrier;
+ for (size_t i = 0; i < layout.properties().length(); i++) {
+ const UnboxedLayout::Property& property = layout.properties()[i];
+ if (property.type == JSVAL_TYPE_OBJECT) {
+ Address valueAddress(propertiesReg, i * sizeof(IdValuePair) + offsetof(IdValuePair, value));
+ Label notObject;
+ masm.branchTestObject(Assembler::NotEqual, valueAddress, &notObject);
+ Register valueObject = masm.extractObject(valueAddress, scratch1);
+ masm.branchPtrInNurseryChunk(Assembler::Equal, valueObject, scratch2, &postBarrier);
+ masm.bind(&notObject);
+ }
+ }
+
+ masm.jump(&allocated);
+ masm.bind(&postBarrier);
+
+ LiveGeneralRegisterSet liveVolatileRegisters;
+ liveVolatileRegisters.add(propertiesReg);
+ if (object.volatile_())
+ liveVolatileRegisters.add(object);
+ masm.PushRegsInMask(liveVolatileRegisters);
+
+ masm.mov(ImmPtr(cx->runtime()), scratch1);
+ masm.setupUnalignedABICall(scratch2);
+ masm.passABIArg(scratch1);
+ masm.passABIArg(object);
+ masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, PostWriteBarrier));
+
+ masm.PopRegsInMask(liveVolatileRegisters);
+
+ masm.bind(&allocated);
+
+ ValueOperand valueOperand;
+#ifdef JS_NUNBOX32
+ valueOperand = ValueOperand(scratch1, scratch2);
+#else
+ valueOperand = ValueOperand(scratch1);
+#endif
+
+ Label failureStoreOther, failureStoreObject;
+
+ for (size_t i = 0; i < layout.properties().length(); i++) {
+ const UnboxedLayout::Property& property = layout.properties()[i];
+ Address valueAddress(propertiesReg, i * sizeof(IdValuePair) + offsetof(IdValuePair, value));
+ Address targetAddress(object, UnboxedPlainObject::offsetOfData() + property.offset);
+
+ masm.loadValue(valueAddress, valueOperand);
+
+ if (property.type == JSVAL_TYPE_OBJECT) {
+ HeapTypeSet* types = group->maybeGetProperty(IdToTypeId(NameToId(property.name)));
+
+ Label notObject;
+ masm.branchTestObject(Assembler::NotEqual, valueOperand,
+ types->mightBeMIRType(MIRType::Null) ? &notObject : &failureStoreObject);
+
+ Register payloadReg = masm.extractObject(valueOperand, scratch1);
+
+ if (!types->hasType(TypeSet::AnyObjectType())) {
+ Register scratch = (payloadReg == scratch1) ? scratch2 : scratch1;
+ masm.guardObjectType(payloadReg, types, scratch, &failureStoreObject);
+ }
+
+ masm.storeUnboxedProperty(targetAddress, JSVAL_TYPE_OBJECT,
+ TypedOrValueRegister(MIRType::Object,
+ AnyRegister(payloadReg)), nullptr);
+
+ if (notObject.used()) {
+ Label done;
+ masm.jump(&done);
+ masm.bind(&notObject);
+ masm.branchTestNull(Assembler::NotEqual, valueOperand, &failureStoreOther);
+ masm.storeUnboxedProperty(targetAddress, JSVAL_TYPE_OBJECT, NullValue(), nullptr);
+ masm.bind(&done);
+ }
+ } else {
+ masm.storeUnboxedProperty(targetAddress, property.type,
+ ConstantOrRegister(valueOperand), &failureStoreOther);
+ }
+ }
+
+ Label done;
+ masm.bind(&done);
+
+ if (object != ReturnReg)
+ masm.movePtr(object, ReturnReg);
+
+ // Restore non-volatile registers which were saved on entry.
+ if (ScratchDoubleReg.volatile_())
+ masm.pop(ScratchDoubleReg);
+ masm.PopRegsInMask(savedNonVolatileRegisters);
+
+ masm.abiret();
+
+ masm.bind(&failureStoreOther);
+
+ // There was a failure while storing a value which cannot be stored at all
+ // in the unboxed object. Initialize the object so it is safe for GC and
+ // return null.
+ masm.initUnboxedObjectContents(object, templateObject);
+
+ masm.bind(&failure);
+
+ masm.movePtr(ImmWord(0), object);
+ masm.jump(&done);
+
+ masm.bind(&failureStoreObject);
+
+ // There was a failure while storing a value to an object slot of the
+ // unboxed object. If the value is storable, the failure occurred due to
+ // incomplete type information in the object, so return a token to trigger
+ // regeneration of the jitcode after a new object is created in the VM.
+ {
+ Label isObject;
+ masm.branchTestObject(Assembler::Equal, valueOperand, &isObject);
+ masm.branchTestNull(Assembler::NotEqual, valueOperand, &failureStoreOther);
+ masm.bind(&isObject);
+ }
+
+ // Initialize the object so it is safe for GC.
+ masm.initUnboxedObjectContents(object, templateObject);
+
+ masm.movePtr(ImmWord(CLEAR_CONSTRUCTOR_CODE_TOKEN), object);
+ masm.jump(&done);
+
+ Linker linker(masm);
+ AutoFlushICache afc("UnboxedObject");
+ JitCode* code = linker.newCode<NoGC>(cx, OTHER_CODE);
+ if (!code)
+ return false;
+
+ layout.setConstructorCode(code);
+ return true;
+}
+
+void
+UnboxedLayout::detachFromCompartment()
+{
+ if (isInList())
+ remove();
+}
+
+/////////////////////////////////////////////////////////////////////
+// UnboxedPlainObject
+/////////////////////////////////////////////////////////////////////
+
+bool
+UnboxedPlainObject::setValue(ExclusiveContext* cx, const UnboxedLayout::Property& property,
+ const Value& v)
+{
+ uint8_t* p = &data_[property.offset];
+ return SetUnboxedValue(cx, this, NameToId(property.name), p, property.type, v,
+ /* preBarrier = */ true);
+}
+
+Value
+UnboxedPlainObject::getValue(const UnboxedLayout::Property& property,
+ bool maybeUninitialized /* = false */)
+{
+ uint8_t* p = &data_[property.offset];
+ return GetUnboxedValue(p, property.type, maybeUninitialized);
+}
+
+void
+UnboxedPlainObject::trace(JSTracer* trc, JSObject* obj)
+{
+ if (obj->as<UnboxedPlainObject>().expando_) {
+ TraceManuallyBarrieredEdge(trc,
+ reinterpret_cast<NativeObject**>(&obj->as<UnboxedPlainObject>().expando_),
+ "unboxed_expando");
+ }
+
+ const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layoutDontCheckGeneration();
+ const int32_t* list = layout.traceList();
+ if (!list)
+ return;
+
+ uint8_t* data = obj->as<UnboxedPlainObject>().data();
+ while (*list != -1) {
+ GCPtrString* heap = reinterpret_cast<GCPtrString*>(data + *list);
+ TraceEdge(trc, heap, "unboxed_string");
+ list++;
+ }
+ list++;
+ while (*list != -1) {
+ GCPtrObject* heap = reinterpret_cast<GCPtrObject*>(data + *list);
+ TraceNullableEdge(trc, heap, "unboxed_object");
+ list++;
+ }
+
+ // Unboxed objects don't have Values to trace.
+ MOZ_ASSERT(*(list + 1) == -1);
+}
+
+/* static */ UnboxedExpandoObject*
+UnboxedPlainObject::ensureExpando(JSContext* cx, Handle<UnboxedPlainObject*> obj)
+{
+ if (obj->expando_)
+ return obj->expando_;
+
+ UnboxedExpandoObject* expando =
+ NewObjectWithGivenProto<UnboxedExpandoObject>(cx, nullptr, gc::AllocKind::OBJECT4);
+ if (!expando)
+ return nullptr;
+
+ // Don't track property types for expando objects. This allows Baseline
+ // and Ion AddSlot ICs to guard on the unboxed group without guarding on
+ // the expando group.
+ MarkObjectGroupUnknownProperties(cx, expando->group());
+
+ // If the expando is tenured then the original object must also be tenured.
+ // Otherwise barriers triggered on the original object for writes to the
+ // expando (as can happen in the JIT) won't see the tenured->nursery edge.
+ // See WholeCellEdges::mark.
+ MOZ_ASSERT_IF(!IsInsideNursery(expando), !IsInsideNursery(obj));
+
+ // As with setValue(), we need to manually trigger post barriers on the
+ // whole object. If we treat the field as a GCPtrObject and later
+ // convert the object to its native representation, we will end up with a
+ // corrupted store buffer entry.
+ if (IsInsideNursery(expando) && !IsInsideNursery(obj))
+ cx->runtime()->gc.storeBuffer.putWholeCell(obj);
+
+ obj->expando_ = expando;
+ return expando;
+}
+
+bool
+UnboxedPlainObject::containsUnboxedOrExpandoProperty(ExclusiveContext* cx, jsid id) const
+{
+ if (layout().lookup(id))
+ return true;
+
+ if (maybeExpando() && maybeExpando()->containsShapeOrElement(cx, id))
+ return true;
+
+ return false;
+}
+
+static bool
+PropagatePropertyTypes(JSContext* cx, jsid id, ObjectGroup* oldGroup, ObjectGroup* newGroup)
+{
+ HeapTypeSet* typeProperty = oldGroup->maybeGetProperty(id);
+ TypeSet::TypeList types;
+ if (!typeProperty->enumerateTypes(&types)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ for (size_t j = 0; j < types.length(); j++)
+ AddTypePropertyId(cx, newGroup, nullptr, id, types[j]);
+ return true;
+}
+
+static PlainObject*
+MakeReplacementTemplateObject(JSContext* cx, HandleObjectGroup group, const UnboxedLayout &layout)
+{
+ PlainObject* obj = NewObjectWithGroup<PlainObject>(cx, group, layout.getAllocKind(),
+ TenuredObject);
+ if (!obj)
+ return nullptr;
+
+ for (size_t i = 0; i < layout.properties().length(); i++) {
+ const UnboxedLayout::Property& property = layout.properties()[i];
+ if (!obj->addDataProperty(cx, NameToId(property.name), i, JSPROP_ENUMERATE))
+ return nullptr;
+ MOZ_ASSERT(obj->slotSpan() == i + 1);
+ MOZ_ASSERT(!obj->inDictionaryMode());
+ }
+
+ return obj;
+}
+
+/* static */ bool
+UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group)
+{
+ AutoEnterAnalysis enter(cx);
+
+ UnboxedLayout& layout = group->unboxedLayout();
+ Rooted<TaggedProto> proto(cx, group->proto());
+
+ MOZ_ASSERT(!layout.nativeGroup());
+
+ RootedObjectGroup replacementGroup(cx);
+
+ const Class* clasp = layout.isArray() ? &ArrayObject::class_ : &PlainObject::class_;
+
+ // Immediately clear any new script on the group. This is done by replacing
+ // the existing new script with one for a replacement default new group.
+ // This is done so that the size of the replacment group's objects is the
+ // same as that for the unboxed group, so that we do not see polymorphic
+ // slot accesses later on for sites that see converted objects from this
+ // group and objects that were allocated using the replacement new group.
+ if (layout.newScript()) {
+ MOZ_ASSERT(!layout.isArray());
+
+ replacementGroup = ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto);
+ if (!replacementGroup)
+ return false;
+
+ PlainObject* templateObject = MakeReplacementTemplateObject(cx, replacementGroup, layout);
+ if (!templateObject)
+ return false;
+
+ TypeNewScript* replacementNewScript =
+ TypeNewScript::makeNativeVersion(cx, layout.newScript(), templateObject);
+ if (!replacementNewScript)
+ return false;
+
+ replacementGroup->setNewScript(replacementNewScript);
+ gc::TraceTypeNewScript(replacementGroup);
+
+ group->clearNewScript(cx, replacementGroup);
+ }
+
+ // Similarly, if this group is keyed to an allocation site, replace its
+ // entry with a new group that has no unboxed layout.
+ if (layout.allocationScript()) {
+ RootedScript script(cx, layout.allocationScript());
+ jsbytecode* pc = layout.allocationPc();
+
+ replacementGroup = ObjectGroupCompartment::makeGroup(cx, clasp, proto);
+ if (!replacementGroup)
+ return false;
+
+ PlainObject* templateObject = &script->getObject(pc)->as<PlainObject>();
+ replacementGroup->addDefiniteProperties(cx, templateObject->lastProperty());
+
+ JSProtoKey key = layout.isArray() ? JSProto_Array : JSProto_Object;
+ cx->compartment()->objectGroups.replaceAllocationSiteGroup(script, pc, key,
+ replacementGroup);
+
+ // Clear any baseline information at this opcode which might use the old group.
+ if (script->hasBaselineScript()) {
+ jit::ICEntry& entry = script->baselineScript()->icEntryFromPCOffset(script->pcToOffset(pc));
+ jit::ICFallbackStub* fallback = entry.fallbackStub();
+ for (jit::ICStubIterator iter = fallback->beginChain(); !iter.atEnd(); iter++)
+ iter.unlink(cx);
+ if (fallback->isNewObject_Fallback())
+ fallback->toNewObject_Fallback()->setTemplateObject(nullptr);
+ else if (fallback->isNewArray_Fallback())
+ fallback->toNewArray_Fallback()->setTemplateGroup(replacementGroup);
+ }
+ }
+
+ size_t nfixed = layout.isArray() ? 0 : gc::GetGCKindSlots(layout.getAllocKind());
+
+ if (layout.isArray()) {
+ // The length shape to use for arrays is cached via a modified initial
+ // shape for array objects. Create an array now to make sure this entry
+ // is instantiated.
+ if (!NewDenseEmptyArray(cx))
+ return false;
+ }
+
+ RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, proto, nfixed, 0));
+ if (!shape)
+ return false;
+
+ MOZ_ASSERT_IF(layout.isArray(), !shape->isEmptyShape() && shape->slotSpan() == 0);
+
+ // Add shapes for each property, if this is for a plain object.
+ for (size_t i = 0; i < layout.properties().length(); i++) {
+ const UnboxedLayout::Property& property = layout.properties()[i];
+
+ Rooted<StackShape> child(cx, StackShape(shape->base()->unowned(), NameToId(property.name),
+ i, JSPROP_ENUMERATE, 0));
+ shape = cx->zone()->propertyTree.getChild(cx, shape, child);
+ if (!shape)
+ return false;
+ }
+
+ ObjectGroup* nativeGroup =
+ ObjectGroupCompartment::makeGroup(cx, clasp, proto,
+ group->flags() & OBJECT_FLAG_DYNAMIC_MASK);
+ if (!nativeGroup)
+ return false;
+
+ // No sense propagating if we don't know what we started with.
+ if (!group->unknownProperties()) {
+ // Propagate all property types from the old group to the new group.
+ if (layout.isArray()) {
+ if (!PropagatePropertyTypes(cx, JSID_VOID, group, nativeGroup))
+ return false;
+ } else {
+ for (size_t i = 0; i < layout.properties().length(); i++) {
+ const UnboxedLayout::Property& property = layout.properties()[i];
+ jsid id = NameToId(property.name);
+ if (!PropagatePropertyTypes(cx, id, group, nativeGroup))
+ return false;
+
+ // If we are OOM we may not be able to propagate properties.
+ if (nativeGroup->unknownProperties())
+ break;
+
+ HeapTypeSet* nativeProperty = nativeGroup->maybeGetProperty(id);
+ if (nativeProperty && nativeProperty->canSetDefinite(i))
+ nativeProperty->setDefinite(i);
+ }
+ }
+ } else {
+ // If we skip, though, the new group had better agree.
+ MOZ_ASSERT(nativeGroup->unknownProperties());
+ }
+
+ layout.nativeGroup_ = nativeGroup;
+ layout.nativeShape_ = shape;
+ layout.replacementGroup_ = replacementGroup;
+
+ nativeGroup->setOriginalUnboxedGroup(group);
+
+ group->markStateChange(cx);
+
+ return true;
+}
+
+/* static */ bool
+UnboxedPlainObject::convertToNative(JSContext* cx, JSObject* obj)
+{
+ const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layout();
+ UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando();
+
+ 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++) {
+ // We might be reading properties off the object which have not been
+ // initialized yet. Make sure any double values we read here are
+ // canonicalized.
+ if (!values.append(obj->as<UnboxedPlainObject>().getValue(layout.properties()[i], true)))
+ return false;
+ }
+
+ // We are eliminating the expando edge with the conversion, so trigger a
+ // pre barrier.
+ JSObject::writeBarrierPre(expando);
+
+ // Additionally trigger a post barrier on the expando itself. Whole cell
+ // store buffer entries can be added on the original unboxed object for
+ // writes to the expando (see WholeCellEdges::trace), so after conversion
+ // we need to make sure the expando itself will still be traced.
+ if (expando && !IsInsideNursery(expando))
+ cx->runtime()->gc.storeBuffer.putWholeCell(expando);
+
+ obj->setGroup(layout.nativeGroup());
+ obj->as<PlainObject>().setLastPropertyMakeNative(cx, layout.nativeShape());
+
+ for (size_t i = 0; i < values.length(); i++)
+ obj->as<PlainObject>().initSlotUnchecked(i, values[i]);
+
+ if (expando) {
+ // Add properties from the expando object to the object, in order.
+ // Suppress GC here, so that callers don't need to worry about this
+ // method collecting. The stuff below can only fail due to OOM, in
+ // which case the object will not have been completely filled back in.
+ gc::AutoSuppressGC suppress(cx);
+
+ Vector<jsid> ids(cx);
+ for (Shape::Range<NoGC> r(expando->lastProperty()); !r.empty(); r.popFront()) {
+ if (!ids.append(r.front().propid()))
+ return false;
+ }
+ for (size_t i = 0; i < expando->getDenseInitializedLength(); i++) {
+ if (!expando->getDenseElement(i).isMagic(JS_ELEMENTS_HOLE)) {
+ if (!ids.append(INT_TO_JSID(i)))
+ return false;
+ }
+ }
+ ::Reverse(ids.begin(), ids.end());
+
+ RootedPlainObject nobj(cx, &obj->as<PlainObject>());
+ Rooted<UnboxedExpandoObject*> nexpando(cx, expando);
+ RootedId id(cx);
+ Rooted<PropertyDescriptor> desc(cx);
+ for (size_t i = 0; i < ids.length(); i++) {
+ id = ids[i];
+ if (!GetOwnPropertyDescriptor(cx, nexpando, id, &desc))
+ return false;
+ ObjectOpResult result;
+ if (!DefineProperty(cx, nobj, id, desc, result))
+ return false;
+ MOZ_ASSERT(result.ok());
+ }
+ }
+
+ return true;
+}
+
+/* static */
+UnboxedPlainObject*
+UnboxedPlainObject::create(ExclusiveContext* cx, HandleObjectGroup group, NewObjectKind newKind)
+{
+ AutoSetNewObjectMetadata metadata(cx);
+
+ MOZ_ASSERT(group->clasp() == &class_);
+ gc::AllocKind allocKind = group->unboxedLayout().getAllocKind();
+
+ UnboxedPlainObject* res =
+ NewObjectWithGroup<UnboxedPlainObject>(cx, group, allocKind, newKind);
+ if (!res)
+ return nullptr;
+
+ // Overwrite the dummy shape which was written to the object's expando field.
+ res->initExpando();
+
+ // Initialize reference fields of the object. All fields in the object will
+ // be overwritten shortly, but references need to be safe for the GC.
+ const int32_t* list = res->layout().traceList();
+ if (list) {
+ uint8_t* data = res->data();
+ while (*list != -1) {
+ GCPtrString* heap = reinterpret_cast<GCPtrString*>(data + *list);
+ heap->init(cx->names().empty);
+ list++;
+ }
+ list++;
+ while (*list != -1) {
+ GCPtrObject* heap = reinterpret_cast<GCPtrObject*>(data + *list);
+ heap->init(nullptr);
+ list++;
+ }
+ // Unboxed objects don't have Values to initialize.
+ MOZ_ASSERT(*(list + 1) == -1);
+ }
+
+ return res;
+}
+
+/* static */ JSObject*
+UnboxedPlainObject::createWithProperties(ExclusiveContext* cx, HandleObjectGroup group,
+ NewObjectKind newKind, IdValuePair* properties)
+{
+ MOZ_ASSERT(newKind == GenericObject || newKind == TenuredObject);
+
+ UnboxedLayout& layout = group->unboxedLayout();
+
+ if (layout.constructorCode()) {
+ MOZ_ASSERT(cx->isJSContext());
+
+ typedef JSObject* (*ConstructorCodeSignature)(IdValuePair*, NewObjectKind);
+ ConstructorCodeSignature function =
+ reinterpret_cast<ConstructorCodeSignature>(layout.constructorCode()->raw());
+
+ JSObject* obj;
+ {
+ JS::AutoSuppressGCAnalysis nogc;
+ obj = reinterpret_cast<JSObject*>(CALL_GENERATED_2(function, properties, newKind));
+ }
+ if (obj > reinterpret_cast<JSObject*>(CLEAR_CONSTRUCTOR_CODE_TOKEN))
+ return obj;
+
+ if (obj == reinterpret_cast<JSObject*>(CLEAR_CONSTRUCTOR_CODE_TOKEN))
+ layout.setConstructorCode(nullptr);
+ }
+
+ UnboxedPlainObject* obj = UnboxedPlainObject::create(cx, group, newKind);
+ if (!obj)
+ return nullptr;
+
+ for (size_t i = 0; i < layout.properties().length(); i++) {
+ if (!obj->setValue(cx, layout.properties()[i], properties[i].value))
+ return NewPlainObjectWithProperties(cx, properties, layout.properties().length(), newKind);
+ }
+
+#ifndef JS_CODEGEN_NONE
+ if (cx->isJSContext() &&
+ !group->unknownProperties() &&
+ !layout.constructorCode() &&
+ cx->asJSContext()->runtime()->jitSupportsFloatingPoint &&
+ jit::CanLikelyAllocateMoreExecutableMemory())
+ {
+ if (!UnboxedLayout::makeConstructorCode(cx->asJSContext(), group))
+ return nullptr;
+ }
+#endif
+
+ return obj;
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_lookupProperty(JSContext* cx, HandleObject obj,
+ HandleId id, MutableHandleObject objp,
+ MutableHandleShape propp)
+{
+ if (obj->as<UnboxedPlainObject>().containsUnboxedOrExpandoProperty(cx, id)) {
+ MarkNonNativePropertyFound<CanGC>(propp);
+ objp.set(obj);
+ return true;
+ }
+
+ RootedObject proto(cx, obj->staticPrototype());
+ if (!proto) {
+ objp.set(nullptr);
+ propp.set(nullptr);
+ return true;
+ }
+
+ return LookupProperty(cx, proto, id, objp, propp);
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result)
+{
+ const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layout();
+
+ if (const UnboxedLayout::Property* property = layout.lookup(id)) {
+ if (!desc.getter() && !desc.setter() && desc.attributes() == JSPROP_ENUMERATE) {
+ // This define is equivalent to setting an existing property.
+ if (obj->as<UnboxedPlainObject>().setValue(cx, *property, desc.value()))
+ return result.succeed();
+ }
+
+ // Trying to incompatibly redefine an existing property requires the
+ // object to be converted to a native object.
+ if (!convertToNative(cx, obj))
+ return false;
+
+ return DefineProperty(cx, obj, id, desc, result);
+ }
+
+ // Define the property on the expando object.
+ Rooted<UnboxedExpandoObject*> expando(cx, ensureExpando(cx, obj.as<UnboxedPlainObject>()));
+ if (!expando)
+ return false;
+
+ // Update property types on the unboxed object as well.
+ AddTypePropertyId(cx, obj, id, desc.value());
+
+ return DefineProperty(cx, expando, id, desc, result);
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp)
+{
+ if (obj->as<UnboxedPlainObject>().containsUnboxedOrExpandoProperty(cx, id)) {
+ *foundp = true;
+ return true;
+ }
+
+ RootedObject proto(cx, obj->staticPrototype());
+ if (!proto) {
+ *foundp = false;
+ return true;
+ }
+
+ return HasProperty(cx, proto, id, foundp);
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_getProperty(JSContext* cx, HandleObject obj, HandleValue receiver,
+ HandleId id, MutableHandleValue vp)
+{
+ const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layout();
+
+ if (const UnboxedLayout::Property* property = layout.lookup(id)) {
+ vp.set(obj->as<UnboxedPlainObject>().getValue(*property));
+ return true;
+ }
+
+ if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) {
+ if (expando->containsShapeOrElement(cx, id)) {
+ RootedObject nexpando(cx, expando);
+ return GetProperty(cx, nexpando, receiver, id, vp);
+ }
+ }
+
+ RootedObject proto(cx, obj->staticPrototype());
+ if (!proto) {
+ vp.setUndefined();
+ return true;
+ }
+
+ return GetProperty(cx, proto, receiver, id, vp);
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
+ HandleValue receiver, ObjectOpResult& result)
+{
+ const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layout();
+
+ if (const UnboxedLayout::Property* property = layout.lookup(id)) {
+ if (receiver.isObject() && obj == &receiver.toObject()) {
+ if (obj->as<UnboxedPlainObject>().setValue(cx, *property, v))
+ return result.succeed();
+
+ if (!convertToNative(cx, obj))
+ return false;
+ return SetProperty(cx, obj, id, v, receiver, result);
+ }
+
+ return SetPropertyByDefining(cx, id, v, receiver, result);
+ }
+
+ if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) {
+ if (expando->containsShapeOrElement(cx, id)) {
+ // Update property types on the unboxed object as well.
+ AddTypePropertyId(cx, obj, id, v);
+
+ RootedObject nexpando(cx, expando);
+ return SetProperty(cx, nexpando, id, v, receiver, result);
+ }
+ }
+
+ return SetPropertyOnProto(cx, obj, id, v, receiver, result);
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id,
+ MutableHandle<PropertyDescriptor> desc)
+{
+ const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layout();
+
+ if (const UnboxedLayout::Property* property = layout.lookup(id)) {
+ desc.value().set(obj->as<UnboxedPlainObject>().getValue(*property));
+ desc.setAttributes(JSPROP_ENUMERATE);
+ desc.object().set(obj);
+ return true;
+ }
+
+ if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) {
+ if (expando->containsShapeOrElement(cx, id)) {
+ RootedObject nexpando(cx, expando);
+ if (!GetOwnPropertyDescriptor(cx, nexpando, id, desc))
+ return false;
+ if (desc.object() == nexpando)
+ desc.object().set(obj);
+ return true;
+ }
+ }
+
+ desc.object().set(nullptr);
+ return true;
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id,
+ ObjectOpResult& result)
+{
+ if (!convertToNative(cx, obj))
+ return false;
+ return DeleteProperty(cx, obj, id, result);
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties,
+ bool enumerableOnly)
+{
+ // Ignore expando properties here, they are special-cased by the property
+ // enumeration code.
+
+ const UnboxedLayout::PropertyVector& unboxed = obj->as<UnboxedPlainObject>().layout().properties();
+ for (size_t i = 0; i < unboxed.length(); i++) {
+ if (!properties.append(NameToId(unboxed[i].name)))
+ return false;
+ }
+
+ return true;
+}
+
+const Class UnboxedExpandoObject::class_ = {
+ "UnboxedExpandoObject",
+ 0
+};
+
+static const ClassOps UnboxedPlainObjectClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ nullptr, /* finalize */
+ nullptr, /* call */
+ nullptr, /* hasInstance */
+ nullptr, /* construct */
+ UnboxedPlainObject::trace,
+};
+
+static const ObjectOps UnboxedPlainObjectObjectOps = {
+ UnboxedPlainObject::obj_lookupProperty,
+ UnboxedPlainObject::obj_defineProperty,
+ UnboxedPlainObject::obj_hasProperty,
+ UnboxedPlainObject::obj_getProperty,
+ UnboxedPlainObject::obj_setProperty,
+ UnboxedPlainObject::obj_getOwnPropertyDescriptor,
+ UnboxedPlainObject::obj_deleteProperty,
+ nullptr, /* getElements */
+ UnboxedPlainObject::obj_enumerate,
+ nullptr /* funToString */
+};
+
+const Class UnboxedPlainObject::class_ = {
+ js_Object_str,
+ Class::NON_NATIVE |
+ JSCLASS_HAS_CACHED_PROTO(JSProto_Object) |
+ JSCLASS_DELAY_METADATA_BUILDER,
+ &UnboxedPlainObjectClassOps,
+ JS_NULL_CLASS_SPEC,
+ JS_NULL_CLASS_EXT,
+ &UnboxedPlainObjectObjectOps
+};
+
+/////////////////////////////////////////////////////////////////////
+// UnboxedArrayObject
+/////////////////////////////////////////////////////////////////////
+
+template <JSValueType Type>
+DenseElementResult
+AppendUnboxedDenseElements(UnboxedArrayObject* obj, uint32_t initlen,
+ MutableHandle<GCVector<Value>> values)
+{
+ for (size_t i = 0; i < initlen; i++)
+ values.infallibleAppend(obj->getElementSpecific<Type>(i));
+ return DenseElementResult::Success;
+}
+
+DefineBoxedOrUnboxedFunctor3(AppendUnboxedDenseElements,
+ UnboxedArrayObject*, uint32_t, MutableHandle<GCVector<Value>>);
+
+/* static */ bool
+UnboxedArrayObject::convertToNativeWithGroup(ExclusiveContext* cx, JSObject* obj,
+ ObjectGroup* group, Shape* shape)
+{
+ size_t length = obj->as<UnboxedArrayObject>().length();
+ size_t initlen = obj->as<UnboxedArrayObject>().initializedLength();
+
+ Rooted<GCVector<Value>> values(cx, GCVector<Value>(cx));
+ if (!values.reserve(initlen))
+ return false;
+
+ AppendUnboxedDenseElementsFunctor functor(&obj->as<UnboxedArrayObject>(), initlen, &values);
+ DebugOnly<DenseElementResult> result = CallBoxedOrUnboxedSpecialization(functor, obj);
+ MOZ_ASSERT(result.value == DenseElementResult::Success);
+
+ obj->setGroup(group);
+
+ ArrayObject* aobj = &obj->as<ArrayObject>();
+ aobj->setLastPropertyMakeNative(cx, shape);
+
+ // Make sure there is at least one element, so that this array does not
+ // use emptyObjectElements / emptyObjectElementsShared.
+ if (!aobj->ensureElements(cx, Max<size_t>(initlen, 1)))
+ return false;
+
+ MOZ_ASSERT(!aobj->getDenseInitializedLength());
+ aobj->setDenseInitializedLength(initlen);
+ aobj->initDenseElements(0, values.begin(), initlen);
+ aobj->setLengthInt32(length);
+
+ return true;
+}
+
+/* static */ bool
+UnboxedArrayObject::convertToNative(JSContext* cx, JSObject* obj)
+{
+ const UnboxedLayout& layout = obj->as<UnboxedArrayObject>().layout();
+
+ if (!layout.nativeGroup()) {
+ if (!UnboxedLayout::makeNativeGroup(cx, obj->group()))
+ return false;
+ }
+
+ return convertToNativeWithGroup(cx, obj, layout.nativeGroup(), layout.nativeShape());
+}
+
+bool
+UnboxedArrayObject::convertInt32ToDouble(ExclusiveContext* cx, ObjectGroup* group)
+{
+ MOZ_ASSERT(elementType() == JSVAL_TYPE_INT32);
+ MOZ_ASSERT(group->unboxedLayout().elementType() == JSVAL_TYPE_DOUBLE);
+
+ Vector<int32_t> values(cx);
+ if (!values.reserve(initializedLength()))
+ return false;
+ for (size_t i = 0; i < initializedLength(); i++)
+ values.infallibleAppend(getElementSpecific<JSVAL_TYPE_INT32>(i).toInt32());
+
+ uint8_t* newElements;
+ if (hasInlineElements()) {
+ newElements = AllocateObjectBuffer<uint8_t>(cx, this, capacity() * sizeof(double));
+ } else {
+ newElements = ReallocateObjectBuffer<uint8_t>(cx, this, elements(),
+ capacity() * sizeof(int32_t),
+ capacity() * sizeof(double));
+ }
+ if (!newElements)
+ return false;
+
+ setGroup(group);
+ elements_ = newElements;
+
+ for (size_t i = 0; i < initializedLength(); i++)
+ setElementNoTypeChangeSpecific<JSVAL_TYPE_DOUBLE>(i, DoubleValue(values[i]));
+
+ return true;
+}
+
+/* static */ UnboxedArrayObject*
+UnboxedArrayObject::create(ExclusiveContext* cx, HandleObjectGroup group, uint32_t length,
+ NewObjectKind newKind, uint32_t maxLength)
+{
+ MOZ_ASSERT(length <= MaximumCapacity);
+
+ MOZ_ASSERT(group->clasp() == &class_);
+ uint32_t elementSize = UnboxedTypeSize(group->unboxedLayout().elementType());
+ uint32_t capacity = Min(length, maxLength);
+ uint32_t nbytes = offsetOfInlineElements() + elementSize * capacity;
+
+ UnboxedArrayObject* res;
+ if (nbytes <= JSObject::MAX_BYTE_SIZE) {
+ gc::AllocKind allocKind = gc::GetGCObjectKindForBytes(nbytes);
+
+ // If there was no provided length information, pick an allocation kind
+ // to accommodate small arrays (as is done for normal native arrays).
+ if (capacity == 0)
+ allocKind = gc::AllocKind::OBJECT8;
+
+ res = NewObjectWithGroup<UnboxedArrayObject>(cx, group, allocKind, newKind);
+ if (!res)
+ return nullptr;
+ res->setInitializedLengthNoBarrier(0);
+ res->setInlineElements();
+
+ size_t actualCapacity = (GetGCKindBytes(allocKind) - offsetOfInlineElements()) / elementSize;
+ MOZ_ASSERT(actualCapacity >= capacity);
+ res->setCapacityIndex(exactCapacityIndex(actualCapacity));
+ } else {
+ res = NewObjectWithGroup<UnboxedArrayObject>(cx, group, gc::AllocKind::OBJECT0, newKind);
+ if (!res)
+ return nullptr;
+ res->setInitializedLengthNoBarrier(0);
+
+ uint32_t capacityIndex = (capacity == length)
+ ? CapacityMatchesLengthIndex
+ : chooseCapacityIndex(capacity, length);
+ uint32_t actualCapacity = computeCapacity(capacityIndex, length);
+
+ res->elements_ = AllocateObjectBuffer<uint8_t>(cx, res, actualCapacity * elementSize);
+ if (!res->elements_) {
+ // Make the object safe for GC.
+ res->setInlineElements();
+ return nullptr;
+ }
+
+ res->setCapacityIndex(capacityIndex);
+ }
+
+ res->setLength(cx, length);
+ return res;
+}
+
+bool
+UnboxedArrayObject::setElement(ExclusiveContext* cx, size_t index, const Value& v)
+{
+ MOZ_ASSERT(index < initializedLength());
+ uint8_t* p = elements() + index * elementSize();
+ return SetUnboxedValue(cx, this, JSID_VOID, p, elementType(), v, /* preBarrier = */ true);
+}
+
+bool
+UnboxedArrayObject::initElement(ExclusiveContext* cx, size_t index, const Value& v)
+{
+ MOZ_ASSERT(index < initializedLength());
+ uint8_t* p = elements() + index * elementSize();
+ return SetUnboxedValue(cx, this, JSID_VOID, p, elementType(), v, /* preBarrier = */ false);
+}
+
+void
+UnboxedArrayObject::initElementNoTypeChange(size_t index, const Value& v)
+{
+ MOZ_ASSERT(index < initializedLength());
+ uint8_t* p = elements() + index * elementSize();
+ if (UnboxedTypeNeedsPreBarrier(elementType()))
+ *reinterpret_cast<void**>(p) = nullptr;
+ SetUnboxedValueNoTypeChange(this, p, elementType(), v, /* preBarrier = */ false);
+}
+
+Value
+UnboxedArrayObject::getElement(size_t index)
+{
+ MOZ_ASSERT(index < initializedLength());
+ uint8_t* p = elements() + index * elementSize();
+ return GetUnboxedValue(p, elementType(), /* maybeUninitialized = */ false);
+}
+
+/* static */ void
+UnboxedArrayObject::trace(JSTracer* trc, JSObject* obj)
+{
+ JSValueType type = obj->as<UnboxedArrayObject>().elementType();
+ if (!UnboxedTypeNeedsPreBarrier(type))
+ return;
+
+ MOZ_ASSERT(obj->as<UnboxedArrayObject>().elementSize() == sizeof(uintptr_t));
+ size_t initlen = obj->as<UnboxedArrayObject>().initializedLength();
+ void** elements = reinterpret_cast<void**>(obj->as<UnboxedArrayObject>().elements());
+
+ switch (type) {
+ case JSVAL_TYPE_OBJECT:
+ for (size_t i = 0; i < initlen; i++) {
+ GCPtrObject* heap = reinterpret_cast<GCPtrObject*>(elements + i);
+ TraceNullableEdge(trc, heap, "unboxed_object");
+ }
+ break;
+
+ case JSVAL_TYPE_STRING:
+ for (size_t i = 0; i < initlen; i++) {
+ GCPtrString* heap = reinterpret_cast<GCPtrString*>(elements + i);
+ TraceEdge(trc, heap, "unboxed_string");
+ }
+ break;
+
+ default:
+ MOZ_CRASH();
+ }
+}
+
+/* static */ void
+UnboxedArrayObject::objectMoved(JSObject* obj, const JSObject* old)
+{
+ UnboxedArrayObject& dst = obj->as<UnboxedArrayObject>();
+ const UnboxedArrayObject& src = old->as<UnboxedArrayObject>();
+
+ // Fix up possible inline data pointer.
+ if (src.hasInlineElements())
+ dst.setInlineElements();
+}
+
+/* static */ void
+UnboxedArrayObject::finalize(FreeOp* fop, JSObject* obj)
+{
+ MOZ_ASSERT(!IsInsideNursery(obj));
+ if (!obj->as<UnboxedArrayObject>().hasInlineElements())
+ js_free(obj->as<UnboxedArrayObject>().elements());
+}
+
+/* static */ size_t
+UnboxedArrayObject::objectMovedDuringMinorGC(JSTracer* trc, JSObject* dst, JSObject* src,
+ gc::AllocKind allocKind)
+{
+ UnboxedArrayObject* ndst = &dst->as<UnboxedArrayObject>();
+ UnboxedArrayObject* nsrc = &src->as<UnboxedArrayObject>();
+ MOZ_ASSERT(ndst->elements() == nsrc->elements());
+
+ Nursery& nursery = trc->runtime()->gc.nursery;
+
+ if (!nursery.isInside(nsrc->elements())) {
+ nursery.removeMallocedBuffer(nsrc->elements());
+ return 0;
+ }
+
+ // Determine if we can use inline data for the target array. If this is
+ // possible, the nursery will have picked an allocation size that is large
+ // enough.
+ size_t nbytes = nsrc->capacity() * nsrc->elementSize();
+ if (offsetOfInlineElements() + nbytes <= GetGCKindBytes(allocKind)) {
+ ndst->setInlineElements();
+ } else {
+ MOZ_ASSERT(allocKind == gc::AllocKind::OBJECT0);
+
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ uint8_t* data = nsrc->zone()->pod_malloc<uint8_t>(nbytes);
+ if (!data)
+ oomUnsafe.crash("Failed to allocate unboxed array elements while tenuring.");
+ ndst->elements_ = data;
+ }
+
+ PodCopy(ndst->elements(), nsrc->elements(), nsrc->initializedLength() * nsrc->elementSize());
+
+ // Set a forwarding pointer for the element buffers in case they were
+ // preserved on the stack by Ion.
+ bool direct = nsrc->capacity() * nsrc->elementSize() >= sizeof(uintptr_t);
+ nursery.maybeSetForwardingPointer(trc, nsrc->elements(), ndst->elements(), direct);
+
+ return ndst->hasInlineElements() ? 0 : nbytes;
+}
+
+// Possible capacities for unboxed arrays. Some of these capacities might seem
+// a little weird, but were chosen to allow the inline data of objects of each
+// size to be fully utilized for arrays of the various types on both 32 bit and
+// 64 bit platforms.
+//
+// To find the possible inline capacities, the following script was used:
+//
+// var fixedSlotCapacities = [0, 2, 4, 8, 12, 16];
+// var dataSizes = [1, 4, 8];
+// var header32 = 4 * 2 + 4 * 2;
+// var header64 = 8 * 2 + 4 * 2;
+//
+// for (var i = 0; i < fixedSlotCapacities.length; i++) {
+// var nfixed = fixedSlotCapacities[i];
+// var size32 = 4 * 4 + 8 * nfixed - header32;
+// var size64 = 8 * 4 + 8 * nfixed - header64;
+// for (var j = 0; j < dataSizes.length; j++) {
+// print(size32 / dataSizes[j]);
+// print(size64 / dataSizes[j]);
+// }
+// }
+//
+/* static */ const uint32_t
+UnboxedArrayObject::CapacityArray[] = {
+ UINT32_MAX, // For CapacityMatchesLengthIndex.
+ 0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 13, 16, 17, 18, 24, 26, 32, 34, 40, 64, 72, 96, 104, 128, 136,
+ 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288,
+ 1048576, 2097152, 3145728, 4194304, 5242880, 6291456, 7340032, 8388608, 9437184, 11534336,
+ 13631488, 15728640, 17825792, 20971520, 24117248, 27262976, 31457280, 35651584, 40894464,
+ 46137344, 52428800, 59768832, MaximumCapacity
+};
+
+static const uint32_t
+Pow2CapacityIndexes[] = {
+ 2, // 1
+ 3, // 2
+ 5, // 4
+ 8, // 8
+ 13, // 16
+ 18, // 32
+ 21, // 64
+ 25, // 128
+ 27, // 256
+ 28, // 512
+ 29, // 1024
+ 30, // 2048
+ 31, // 4096
+ 32, // 8192
+ 33, // 16384
+ 34, // 32768
+ 35, // 65536
+ 36, // 131072
+ 37, // 262144
+ 38, // 524288
+ 39 // 1048576
+};
+
+static const uint32_t MebiCapacityIndex = 39;
+
+/* static */ uint32_t
+UnboxedArrayObject::chooseCapacityIndex(uint32_t capacity, uint32_t length)
+{
+ // Note: the structure and behavior of this method follow along with
+ // NativeObject::goodAllocated. Changes to the allocation strategy in one
+ // should generally be matched by the other.
+
+ // Make sure we have enough space to store all possible values for the capacity index.
+ // This ought to be a static_assert, but MSVC doesn't like that.
+ MOZ_ASSERT(mozilla::ArrayLength(CapacityArray) - 1 <= (CapacityMask >> CapacityShift));
+
+ // The caller should have ensured the capacity is possible for an unboxed array.
+ MOZ_ASSERT(capacity <= MaximumCapacity);
+
+ static const uint32_t Mebi = 1024 * 1024;
+
+ if (capacity <= Mebi) {
+ capacity = mozilla::RoundUpPow2(capacity);
+
+ // When the required capacity is close to the array length, then round
+ // up to the array length itself, as for NativeObject.
+ if (length >= capacity && capacity > (length / 3) * 2)
+ return CapacityMatchesLengthIndex;
+
+ if (capacity < MinimumDynamicCapacity)
+ capacity = MinimumDynamicCapacity;
+
+ uint32_t bit = mozilla::FloorLog2Size(capacity);
+ MOZ_ASSERT(capacity == uint32_t(1 << bit));
+ MOZ_ASSERT(bit <= 20);
+ MOZ_ASSERT(mozilla::ArrayLength(Pow2CapacityIndexes) == 21);
+
+ uint32_t index = Pow2CapacityIndexes[bit];
+ MOZ_ASSERT(CapacityArray[index] == capacity);
+
+ return index;
+ }
+
+ MOZ_ASSERT(CapacityArray[MebiCapacityIndex] == Mebi);
+
+ for (uint32_t i = MebiCapacityIndex + 1;; i++) {
+ if (CapacityArray[i] >= capacity)
+ return i;
+ }
+
+ MOZ_CRASH("Invalid capacity");
+}
+
+/* static */ uint32_t
+UnboxedArrayObject::exactCapacityIndex(uint32_t capacity)
+{
+ for (size_t i = CapacityMatchesLengthIndex + 1; i < ArrayLength(CapacityArray); i++) {
+ if (CapacityArray[i] == capacity)
+ return i;
+ }
+ MOZ_CRASH();
+}
+
+bool
+UnboxedArrayObject::growElements(ExclusiveContext* cx, size_t cap)
+{
+ // The caller should have checked if this capacity is possible for an
+ // unboxed array, so the only way this call can fail is from OOM.
+ MOZ_ASSERT(cap <= MaximumCapacity);
+
+ uint32_t oldCapacity = capacity();
+ uint32_t newCapacityIndex = chooseCapacityIndex(cap, length());
+ uint32_t newCapacity = computeCapacity(newCapacityIndex, length());
+
+ MOZ_ASSERT(oldCapacity < cap);
+ MOZ_ASSERT(cap <= newCapacity);
+
+ // The allocation size computation below cannot have integer overflows.
+ JS_STATIC_ASSERT(MaximumCapacity < UINT32_MAX / sizeof(double));
+
+ uint8_t* newElements;
+ if (hasInlineElements()) {
+ newElements = AllocateObjectBuffer<uint8_t>(cx, this, newCapacity * elementSize());
+ if (!newElements)
+ return false;
+ js_memcpy(newElements, elements(), initializedLength() * elementSize());
+ } else {
+ newElements = ReallocateObjectBuffer<uint8_t>(cx, this, elements(),
+ oldCapacity * elementSize(),
+ newCapacity * elementSize());
+ if (!newElements)
+ return false;
+ }
+
+ elements_ = newElements;
+ setCapacityIndex(newCapacityIndex);
+
+ return true;
+}
+
+void
+UnboxedArrayObject::shrinkElements(ExclusiveContext* cx, size_t cap)
+{
+ if (hasInlineElements())
+ return;
+
+ uint32_t oldCapacity = capacity();
+ uint32_t newCapacityIndex = chooseCapacityIndex(cap, 0);
+ uint32_t newCapacity = computeCapacity(newCapacityIndex, 0);
+
+ MOZ_ASSERT(cap < oldCapacity);
+ MOZ_ASSERT(cap <= newCapacity);
+
+ if (newCapacity >= oldCapacity)
+ return;
+
+ uint8_t* newElements = ReallocateObjectBuffer<uint8_t>(cx, this, elements(),
+ oldCapacity * elementSize(),
+ newCapacity * elementSize());
+ if (!newElements)
+ return;
+
+ elements_ = newElements;
+ setCapacityIndex(newCapacityIndex);
+}
+
+bool
+UnboxedArrayObject::containsProperty(ExclusiveContext* cx, jsid id)
+{
+ if (JSID_IS_INT(id) && uint32_t(JSID_TO_INT(id)) < initializedLength())
+ return true;
+ if (JSID_IS_ATOM(id) && JSID_TO_ATOM(id) == cx->names().length)
+ return true;
+ return false;
+}
+
+/* static */ bool
+UnboxedArrayObject::obj_lookupProperty(JSContext* cx, HandleObject obj,
+ HandleId id, MutableHandleObject objp,
+ MutableHandleShape propp)
+{
+ if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) {
+ MarkNonNativePropertyFound<CanGC>(propp);
+ objp.set(obj);
+ return true;
+ }
+
+ RootedObject proto(cx, obj->staticPrototype());
+ if (!proto) {
+ objp.set(nullptr);
+ propp.set(nullptr);
+ return true;
+ }
+
+ return LookupProperty(cx, proto, id, objp, propp);
+}
+
+/* static */ bool
+UnboxedArrayObject::obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result)
+{
+ if (JSID_IS_INT(id) && !desc.getter() && !desc.setter() && desc.attributes() == JSPROP_ENUMERATE) {
+ UnboxedArrayObject* nobj = &obj->as<UnboxedArrayObject>();
+
+ uint32_t index = JSID_TO_INT(id);
+ if (index < nobj->initializedLength()) {
+ if (nobj->setElement(cx, index, desc.value()))
+ return result.succeed();
+ } else if (index == nobj->initializedLength() && index < MaximumCapacity) {
+ if (nobj->initializedLength() == nobj->capacity()) {
+ if (!nobj->growElements(cx, index + 1))
+ return false;
+ }
+ nobj->setInitializedLength(index + 1);
+ if (nobj->initElement(cx, index, desc.value())) {
+ if (nobj->length() <= index)
+ nobj->setLengthInt32(index + 1);
+ return result.succeed();
+ }
+ nobj->setInitializedLengthNoBarrier(index);
+ }
+ }
+
+ if (!convertToNative(cx, obj))
+ return false;
+
+ return DefineProperty(cx, obj, id, desc, result);
+}
+
+/* static */ bool
+UnboxedArrayObject::obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp)
+{
+ if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) {
+ *foundp = true;
+ return true;
+ }
+
+ RootedObject proto(cx, obj->staticPrototype());
+ if (!proto) {
+ *foundp = false;
+ return true;
+ }
+
+ return HasProperty(cx, proto, id, foundp);
+}
+
+/* static */ bool
+UnboxedArrayObject::obj_getProperty(JSContext* cx, HandleObject obj, HandleValue receiver,
+ HandleId id, MutableHandleValue vp)
+{
+ if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) {
+ if (JSID_IS_INT(id))
+ vp.set(obj->as<UnboxedArrayObject>().getElement(JSID_TO_INT(id)));
+ else
+ vp.set(Int32Value(obj->as<UnboxedArrayObject>().length()));
+ return true;
+ }
+
+ RootedObject proto(cx, obj->staticPrototype());
+ if (!proto) {
+ vp.setUndefined();
+ return true;
+ }
+
+ return GetProperty(cx, proto, receiver, id, vp);
+}
+
+/* static */ bool
+UnboxedArrayObject::obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
+ HandleValue receiver, ObjectOpResult& result)
+{
+ if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) {
+ if (receiver.isObject() && obj == &receiver.toObject()) {
+ if (JSID_IS_INT(id)) {
+ if (obj->as<UnboxedArrayObject>().setElement(cx, JSID_TO_INT(id), v))
+ return result.succeed();
+ } else {
+ uint32_t len;
+ if (!CanonicalizeArrayLengthValue(cx, v, &len))
+ return false;
+ UnboxedArrayObject* nobj = &obj->as<UnboxedArrayObject>();
+ if (len < nobj->initializedLength()) {
+ nobj->setInitializedLength(len);
+ nobj->shrinkElements(cx, len);
+ }
+ nobj->setLength(cx, len);
+ return result.succeed();
+ }
+
+ if (!convertToNative(cx, obj))
+ return false;
+ return SetProperty(cx, obj, id, v, receiver, result);
+ }
+
+ return SetPropertyByDefining(cx, id, v, receiver, result);
+ }
+
+ return SetPropertyOnProto(cx, obj, id, v, receiver, result);
+}
+
+/* static */ bool
+UnboxedArrayObject::obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id,
+ MutableHandle<PropertyDescriptor> desc)
+{
+ if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) {
+ if (JSID_IS_INT(id)) {
+ desc.value().set(obj->as<UnboxedArrayObject>().getElement(JSID_TO_INT(id)));
+ desc.setAttributes(JSPROP_ENUMERATE);
+ } else {
+ desc.value().set(Int32Value(obj->as<UnboxedArrayObject>().length()));
+ desc.setAttributes(JSPROP_PERMANENT);
+ }
+ desc.object().set(obj);
+ return true;
+ }
+
+ desc.object().set(nullptr);
+ return true;
+}
+
+/* static */ bool
+UnboxedArrayObject::obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id,
+ ObjectOpResult& result)
+{
+ if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) {
+ size_t initlen = obj->as<UnboxedArrayObject>().initializedLength();
+ if (JSID_IS_INT(id) && JSID_TO_INT(id) == int32_t(initlen - 1)) {
+ obj->as<UnboxedArrayObject>().setInitializedLength(initlen - 1);
+ obj->as<UnboxedArrayObject>().shrinkElements(cx, initlen - 1);
+ return result.succeed();
+ }
+ }
+
+ if (!convertToNative(cx, obj))
+ return false;
+ return DeleteProperty(cx, obj, id, result);
+}
+
+/* static */ bool
+UnboxedArrayObject::obj_enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties,
+ bool enumerableOnly)
+{
+ for (size_t i = 0; i < obj->as<UnboxedArrayObject>().initializedLength(); i++) {
+ if (!properties.append(INT_TO_JSID(i)))
+ return false;
+ }
+
+ if (!enumerableOnly && !properties.append(NameToId(cx->names().length)))
+ return false;
+
+ return true;
+}
+
+static const ClassOps UnboxedArrayObjectClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ UnboxedArrayObject::finalize,
+ nullptr, /* call */
+ nullptr, /* hasInstance */
+ nullptr, /* construct */
+ UnboxedArrayObject::trace,
+};
+
+static const ClassExtension UnboxedArrayObjectClassExtension = {
+ nullptr, /* weakmapKeyDelegateOp */
+ UnboxedArrayObject::objectMoved
+};
+
+static const ObjectOps UnboxedArrayObjectObjectOps = {
+ UnboxedArrayObject::obj_lookupProperty,
+ UnboxedArrayObject::obj_defineProperty,
+ UnboxedArrayObject::obj_hasProperty,
+ UnboxedArrayObject::obj_getProperty,
+ UnboxedArrayObject::obj_setProperty,
+ UnboxedArrayObject::obj_getOwnPropertyDescriptor,
+ UnboxedArrayObject::obj_deleteProperty,
+ nullptr, /* getElements */
+ UnboxedArrayObject::obj_enumerate,
+ nullptr /* funToString */
+};
+
+const Class UnboxedArrayObject::class_ = {
+ "Array",
+ Class::NON_NATIVE |
+ JSCLASS_SKIP_NURSERY_FINALIZE |
+ JSCLASS_BACKGROUND_FINALIZE,
+ &UnboxedArrayObjectClassOps,
+ JS_NULL_CLASS_SPEC,
+ &UnboxedArrayObjectClassExtension,
+ &UnboxedArrayObjectObjectOps
+};
+
+/////////////////////////////////////////////////////////////////////
+// API
+/////////////////////////////////////////////////////////////////////
+
+static bool
+UnboxedTypeIncludes(JSValueType supertype, JSValueType subtype)
+{
+ if (supertype == JSVAL_TYPE_DOUBLE && subtype == JSVAL_TYPE_INT32)
+ return true;
+ if (supertype == JSVAL_TYPE_OBJECT && subtype == JSVAL_TYPE_NULL)
+ return true;
+ return false;
+}
+
+static bool
+CombineUnboxedTypes(const Value& value, JSValueType* existing)
+{
+ JSValueType type = value.isDouble() ? JSVAL_TYPE_DOUBLE : value.extractNonDoubleType();
+
+ if (*existing == JSVAL_TYPE_MAGIC || *existing == type || UnboxedTypeIncludes(type, *existing)) {
+ *existing = type;
+ return true;
+ }
+ if (UnboxedTypeIncludes(*existing, type))
+ return true;
+ return false;
+}
+
+// Return whether the property names and types in layout are a subset of the
+// specified vector.
+static bool
+PropertiesAreSuperset(const UnboxedLayout::PropertyVector& properties, UnboxedLayout* layout)
+{
+ for (size_t i = 0; i < layout->properties().length(); i++) {
+ const UnboxedLayout::Property& layoutProperty = layout->properties()[i];
+ bool found = false;
+ for (size_t j = 0; j < properties.length(); j++) {
+ if (layoutProperty.name == properties[j].name) {
+ found = (layoutProperty.type == properties[j].type);
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
+static bool
+CombinePlainObjectProperties(PlainObject* obj, Shape* templateShape,
+ UnboxedLayout::PropertyVector& properties)
+{
+ // All preliminary objects must have been created with enough space to
+ // fill in their unboxed data inline. This is ensured either by using
+ // the largest allocation kind (which limits the maximum size of an
+ // unboxed object), or by using an allocation kind that covers all
+ // properties in the template, as the space used by unboxed properties
+ // is less than or equal to that used by boxed properties.
+ MOZ_ASSERT(gc::GetGCKindSlots(obj->asTenured().getAllocKind()) >=
+ Min(NativeObject::MAX_FIXED_SLOTS, templateShape->slotSpan()));
+
+ if (obj->lastProperty() != templateShape || obj->hasDynamicElements()) {
+ // Only use an unboxed representation if all created objects match
+ // the template shape exactly.
+ return false;
+ }
+
+ for (size_t i = 0; i < templateShape->slotSpan(); i++) {
+ Value val = obj->getSlot(i);
+
+ JSValueType& existing = properties[i].type;
+ if (!CombineUnboxedTypes(val, &existing))
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+CombineArrayObjectElements(ExclusiveContext* cx, ArrayObject* obj, JSValueType* elementType)
+{
+ if (obj->inDictionaryMode() ||
+ obj->lastProperty()->propid() != AtomToId(cx->names().length) ||
+ !obj->lastProperty()->previous()->isEmptyShape())
+ {
+ // Only use an unboxed representation if the object has no properties.
+ return false;
+ }
+
+ for (size_t i = 0; i < obj->getDenseInitializedLength(); i++) {
+ Value val = obj->getDenseElement(i);
+
+ // For now, unboxed arrays cannot have holes.
+ if (val.isMagic(JS_ELEMENTS_HOLE))
+ return false;
+
+ if (!CombineUnboxedTypes(val, elementType))
+ return false;
+ }
+
+ return true;
+}
+
+static size_t
+ComputePlainObjectLayout(ExclusiveContext* cx, Shape* templateShape,
+ UnboxedLayout::PropertyVector& properties)
+{
+ // Fill in the names for all the object's properties.
+ for (Shape::Range<NoGC> r(templateShape); !r.empty(); r.popFront()) {
+ size_t slot = r.front().slot();
+ MOZ_ASSERT(!properties[slot].name);
+ properties[slot].name = JSID_TO_ATOM(r.front().propid())->asPropertyName();
+ }
+
+ // Fill in all the unboxed object's property offsets.
+ uint32_t offset = 0;
+
+ // Search for an existing unboxed layout which is a subset of this one.
+ // If there are multiple such layouts, use the largest one. If we're able
+ // to find such a layout, use the same property offsets for the shared
+ // properties, which will allow us to generate better code if the objects
+ // have a subtype/supertype relation and are accessed at common sites.
+ UnboxedLayout* bestExisting = nullptr;
+ for (UnboxedLayout* existing : cx->compartment()->unboxedLayouts) {
+ if (PropertiesAreSuperset(properties, existing)) {
+ if (!bestExisting ||
+ existing->properties().length() > bestExisting->properties().length())
+ {
+ bestExisting = existing;
+ }
+ }
+ }
+ if (bestExisting) {
+ for (size_t i = 0; i < bestExisting->properties().length(); i++) {
+ const UnboxedLayout::Property& existingProperty = bestExisting->properties()[i];
+ for (size_t j = 0; j < templateShape->slotSpan(); j++) {
+ if (existingProperty.name == properties[j].name) {
+ MOZ_ASSERT(existingProperty.type == properties[j].type);
+ properties[j].offset = existingProperty.offset;
+ }
+ }
+ }
+ offset = bestExisting->size();
+ }
+
+ // Order remaining properties from the largest down for the best space
+ // utilization.
+ static const size_t typeSizes[] = { 8, 4, 1 };
+
+ for (size_t i = 0; i < ArrayLength(typeSizes); i++) {
+ size_t size = typeSizes[i];
+ for (size_t j = 0; j < templateShape->slotSpan(); j++) {
+ if (properties[j].offset != UINT32_MAX)
+ continue;
+ JSValueType type = properties[j].type;
+ if (UnboxedTypeSize(type) == size) {
+ offset = JS_ROUNDUP(offset, size);
+ properties[j].offset = offset;
+ offset += size;
+ }
+ }
+ }
+
+ // The final offset is the amount of data needed by the object.
+ return offset;
+}
+
+static bool
+SetLayoutTraceList(ExclusiveContext* cx, UnboxedLayout* layout)
+{
+ // Figure out the offsets of any objects or string properties.
+ Vector<int32_t, 8, SystemAllocPolicy> objectOffsets, stringOffsets;
+ for (size_t i = 0; i < layout->properties().length(); i++) {
+ const UnboxedLayout::Property& property = layout->properties()[i];
+ MOZ_ASSERT(property.offset != UINT32_MAX);
+ if (property.type == JSVAL_TYPE_OBJECT) {
+ if (!objectOffsets.append(property.offset))
+ return false;
+ } else if (property.type == JSVAL_TYPE_STRING) {
+ if (!stringOffsets.append(property.offset))
+ return false;
+ }
+ }
+
+ // Construct the layout's trace list.
+ if (!objectOffsets.empty() || !stringOffsets.empty()) {
+ Vector<int32_t, 8, SystemAllocPolicy> entries;
+ if (!entries.appendAll(stringOffsets) ||
+ !entries.append(-1) ||
+ !entries.appendAll(objectOffsets) ||
+ !entries.append(-1) ||
+ !entries.append(-1))
+ {
+ return false;
+ }
+ int32_t* traceList = cx->zone()->pod_malloc<int32_t>(entries.length());
+ if (!traceList)
+ return false;
+ PodCopy(traceList, entries.begin(), entries.length());
+ layout->setTraceList(traceList);
+ }
+
+ return true;
+}
+
+static inline Value
+NextValue(Handle<GCVector<Value>> values, size_t* valueCursor)
+{
+ return values[(*valueCursor)++];
+}
+
+static bool
+GetValuesFromPreliminaryArrayObject(ArrayObject* obj, MutableHandle<GCVector<Value>> values)
+{
+ if (!values.append(Int32Value(obj->length())))
+ return false;
+ if (!values.append(Int32Value(obj->getDenseInitializedLength())))
+ return false;
+ for (size_t i = 0; i < obj->getDenseInitializedLength(); i++) {
+ if (!values.append(obj->getDenseElement(i)))
+ return false;
+ }
+ return true;
+}
+
+void
+UnboxedArrayObject::fillAfterConvert(ExclusiveContext* cx,
+ Handle<GCVector<Value>> values, size_t* valueCursor)
+{
+ MOZ_ASSERT(CapacityArray[1] == 0);
+ setCapacityIndex(1);
+ setInitializedLengthNoBarrier(0);
+ setInlineElements();
+
+ setLength(cx, NextValue(values, valueCursor).toInt32());
+
+ int32_t initlen = NextValue(values, valueCursor).toInt32();
+ if (!initlen)
+ return;
+
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ if (!growElements(cx, initlen))
+ oomUnsafe.crash("UnboxedArrayObject::fillAfterConvert");
+
+ setInitializedLength(initlen);
+
+ for (size_t i = 0; i < size_t(initlen); i++)
+ JS_ALWAYS_TRUE(initElement(cx, i, NextValue(values, valueCursor)));
+}
+
+static bool
+GetValuesFromPreliminaryPlainObject(PlainObject* obj, MutableHandle<GCVector<Value>> values)
+{
+ for (size_t i = 0; i < obj->slotSpan(); i++) {
+ if (!values.append(obj->getSlot(i)))
+ return false;
+ }
+ return true;
+}
+
+void
+UnboxedPlainObject::fillAfterConvert(ExclusiveContext* cx,
+ Handle<GCVector<Value>> values, size_t* valueCursor)
+{
+ initExpando();
+ memset(data(), 0, layout().size());
+ for (size_t i = 0; i < layout().properties().length(); i++)
+ JS_ALWAYS_TRUE(setValue(cx, layout().properties()[i], NextValue(values, valueCursor)));
+}
+
+bool
+js::TryConvertToUnboxedLayout(ExclusiveContext* cx, AutoEnterAnalysis& enter, Shape* templateShape,
+ ObjectGroup* group, PreliminaryObjectArray* objects)
+{
+ bool isArray = !templateShape;
+
+ // Unboxed arrays are nightly only for now. The getenv() call will be
+ // removed when they are on by default. See bug 1153266.
+ if (isArray) {
+#ifdef NIGHTLY_BUILD
+ if (!getenv("JS_OPTION_USE_UNBOXED_ARRAYS")) {
+ if (!cx->options().unboxedArrays())
+ return true;
+ }
+#else
+ return true;
+#endif
+ } else {
+ if (jit::JitOptions.disableUnboxedObjects)
+ return true;
+ }
+
+ MOZ_ASSERT_IF(templateShape, !templateShape->getObjectFlags());
+
+ if (group->runtimeFromAnyThread()->isSelfHostingGlobal(cx->global()))
+ return true;
+
+ if (!isArray && templateShape->slotSpan() == 0)
+ return true;
+
+ UnboxedLayout::PropertyVector properties;
+ if (!isArray) {
+ if (!properties.appendN(UnboxedLayout::Property(), templateShape->slotSpan()))
+ return false;
+ }
+ JSValueType elementType = JSVAL_TYPE_MAGIC;
+
+ size_t objectCount = 0;
+ for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
+ JSObject* obj = objects->get(i);
+ if (!obj)
+ continue;
+
+ if (obj->isSingleton() || obj->group() != group)
+ return true;
+
+ objectCount++;
+
+ if (isArray) {
+ if (!CombineArrayObjectElements(cx, &obj->as<ArrayObject>(), &elementType))
+ return true;
+ } else {
+ if (!CombinePlainObjectProperties(&obj->as<PlainObject>(), templateShape, properties))
+ return true;
+ }
+ }
+
+ size_t layoutSize = 0;
+ if (isArray) {
+ // Don't use an unboxed representation if we couldn't determine an
+ // element type for the objects.
+ if (UnboxedTypeSize(elementType) == 0)
+ return true;
+ } else {
+ if (objectCount <= 1) {
+ // If only one of the objects has been created, it is more likely
+ // to have new properties added later. This heuristic is not used
+ // for array objects, where we might want an unboxed representation
+ // even if there is only one large array.
+ return true;
+ }
+
+ for (size_t i = 0; i < templateShape->slotSpan(); i++) {
+ // We can't use an unboxed representation if e.g. all the objects have
+ // a null value for one of the properties, as we can't decide what type
+ // it is supposed to have.
+ if (UnboxedTypeSize(properties[i].type) == 0)
+ return true;
+ }
+
+ // Make sure that all properties on the template shape are property
+ // names, and not indexes.
+ for (Shape::Range<NoGC> r(templateShape); !r.empty(); r.popFront()) {
+ jsid id = r.front().propid();
+ uint32_t dummy;
+ if (!JSID_IS_ATOM(id) || JSID_TO_ATOM(id)->isIndex(&dummy))
+ return true;
+ }
+
+ layoutSize = ComputePlainObjectLayout(cx, templateShape, properties);
+
+ // The entire object must be allocatable inline.
+ if (UnboxedPlainObject::offsetOfData() + layoutSize > JSObject::MAX_BYTE_SIZE)
+ return true;
+ }
+
+ UniquePtr<UnboxedLayout>& layout = enter.unboxedLayoutToCleanUp;
+ MOZ_ASSERT(!layout);
+ layout = group->zone()->make_unique<UnboxedLayout>();
+ if (!layout)
+ return false;
+
+ if (isArray) {
+ layout->initArray(elementType);
+ } else {
+ if (!layout->initProperties(properties, layoutSize))
+ return false;
+
+ // The unboxedLayouts list only tracks layouts for plain objects.
+ cx->compartment()->unboxedLayouts.insertFront(layout.get());
+
+ if (!SetLayoutTraceList(cx, layout.get()))
+ return false;
+ }
+
+ // We've determined that all the preliminary objects can use the new layout
+ // just constructed, so convert the existing group to use the unboxed class,
+ // and update the preliminary objects to use the new layout. Do the
+ // fallible stuff first before modifying any objects.
+
+ // Get an empty shape which we can use for the preliminary objects.
+ const Class* clasp = isArray ? &UnboxedArrayObject::class_ : &UnboxedPlainObject::class_;
+ Shape* newShape = EmptyShape::getInitialShape(cx, clasp, group->proto(), 0);
+ if (!newShape) {
+ cx->recoverFromOutOfMemory();
+ return false;
+ }
+
+ // Accumulate a list of all the values in each preliminary object, and
+ // update their shapes.
+ Rooted<GCVector<Value>> values(cx, GCVector<Value>(cx));
+ for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
+ JSObject* obj = objects->get(i);
+ if (!obj)
+ continue;
+
+ bool ok;
+ if (isArray)
+ ok = GetValuesFromPreliminaryArrayObject(&obj->as<ArrayObject>(), &values);
+ else
+ ok = GetValuesFromPreliminaryPlainObject(&obj->as<PlainObject>(), &values);
+
+ if (!ok) {
+ cx->recoverFromOutOfMemory();
+ return false;
+ }
+ }
+
+ if (TypeNewScript* newScript = group->newScript())
+ layout->setNewScript(newScript);
+
+ for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
+ if (JSObject* obj = objects->get(i))
+ obj->as<NativeObject>().setLastPropertyMakeNonNative(newShape);
+ }
+
+ group->setClasp(clasp);
+ group->setUnboxedLayout(layout.release());
+
+ size_t valueCursor = 0;
+ for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
+ JSObject* obj = objects->get(i);
+ if (!obj)
+ continue;
+
+ if (isArray)
+ obj->as<UnboxedArrayObject>().fillAfterConvert(cx, values, &valueCursor);
+ else
+ obj->as<UnboxedPlainObject>().fillAfterConvert(cx, values, &valueCursor);
+ }
+
+ MOZ_ASSERT(valueCursor == values.length());
+ return true;
+}
+
+DefineBoxedOrUnboxedFunctor6(SetOrExtendBoxedOrUnboxedDenseElements,
+ ExclusiveContext*, JSObject*, uint32_t, const Value*, uint32_t,
+ ShouldUpdateTypes);
+
+DenseElementResult
+js::SetOrExtendAnyBoxedOrUnboxedDenseElements(ExclusiveContext* cx, JSObject* obj,
+ uint32_t start, const Value* vp, uint32_t count,
+ ShouldUpdateTypes updateTypes)
+{
+ SetOrExtendBoxedOrUnboxedDenseElementsFunctor functor(cx, obj, start, vp, count, updateTypes);
+ return CallBoxedOrUnboxedSpecialization(functor, obj);
+};
+
+DefineBoxedOrUnboxedFunctor5(MoveBoxedOrUnboxedDenseElements,
+ JSContext*, JSObject*, uint32_t, uint32_t, uint32_t);
+
+DenseElementResult
+js::MoveAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj,
+ uint32_t dstStart, uint32_t srcStart, uint32_t length)
+{
+ MoveBoxedOrUnboxedDenseElementsFunctor functor(cx, obj, dstStart, srcStart, length);
+ return CallBoxedOrUnboxedSpecialization(functor, obj);
+}
+
+DefineBoxedOrUnboxedFunctorPair6(CopyBoxedOrUnboxedDenseElements,
+ JSContext*, JSObject*, JSObject*, uint32_t, uint32_t, uint32_t);
+
+DenseElementResult
+js::CopyAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* dst, JSObject* src,
+ uint32_t dstStart, uint32_t srcStart, uint32_t length)
+{
+ CopyBoxedOrUnboxedDenseElementsFunctor functor(cx, dst, src, dstStart, srcStart, length);
+ return CallBoxedOrUnboxedSpecialization(functor, dst, src);
+}
+
+DefineBoxedOrUnboxedFunctor3(SetBoxedOrUnboxedInitializedLength,
+ JSContext*, JSObject*, size_t);
+
+void
+js::SetAnyBoxedOrUnboxedInitializedLength(JSContext* cx, JSObject* obj, size_t initlen)
+{
+ SetBoxedOrUnboxedInitializedLengthFunctor functor(cx, obj, initlen);
+ JS_ALWAYS_TRUE(CallBoxedOrUnboxedSpecialization(functor, obj) == DenseElementResult::Success);
+}
+
+DefineBoxedOrUnboxedFunctor3(EnsureBoxedOrUnboxedDenseElements,
+ JSContext*, JSObject*, size_t);
+
+DenseElementResult
+js::EnsureAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj, size_t initlen)
+{
+ EnsureBoxedOrUnboxedDenseElementsFunctor functor(cx, obj, initlen);
+ return CallBoxedOrUnboxedSpecialization(functor, obj);
+}
diff --git a/js/src/vm/UnboxedObject.h b/js/src/vm/UnboxedObject.h
new file mode 100644
index 000000000..ecff8be5b
--- /dev/null
+++ b/js/src/vm/UnboxedObject.h
@@ -0,0 +1,531 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef vm_UnboxedObject_h
+#define vm_UnboxedObject_h
+
+#include "jsgc.h"
+#include "jsobj.h"
+
+#include "vm/Runtime.h"
+#include "vm/TypeInference.h"
+
+namespace js {
+
+// Memory required for an unboxed value of a given type. Returns zero for types
+// which can't be used for unboxed objects.
+static inline size_t
+UnboxedTypeSize(JSValueType type)
+{
+ switch (type) {
+ case JSVAL_TYPE_BOOLEAN: return 1;
+ case JSVAL_TYPE_INT32: return 4;
+ case JSVAL_TYPE_DOUBLE: return 8;
+ case JSVAL_TYPE_STRING: return sizeof(void*);
+ case JSVAL_TYPE_OBJECT: return sizeof(void*);
+ default: return 0;
+ }
+}
+
+static inline bool
+UnboxedTypeNeedsPreBarrier(JSValueType type)
+{
+ return type == JSVAL_TYPE_STRING || type == JSVAL_TYPE_OBJECT;
+}
+
+static inline bool
+UnboxedTypeNeedsPostBarrier(JSValueType type)
+{
+ return type == JSVAL_TYPE_OBJECT;
+}
+
+// Class tracking information specific to unboxed objects.
+class UnboxedLayout : public mozilla::LinkedListElement<UnboxedLayout>
+{
+ public:
+ struct Property {
+ PropertyName* name;
+ uint32_t offset;
+ JSValueType type;
+
+ Property()
+ : name(nullptr), offset(UINT32_MAX), type(JSVAL_TYPE_MAGIC)
+ {}
+ };
+
+ typedef Vector<Property, 0, SystemAllocPolicy> PropertyVector;
+
+ private:
+ // 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.
+ GCPtrObjectGroup nativeGroup_;
+ GCPtrShape nativeShape_;
+
+ // Any script/pc which the associated group is created for.
+ GCPtrScript allocationScript_;
+ jsbytecode* allocationPc_;
+
+ // If nativeGroup is set and this object originally had a TypeNewScript or
+ // was keyed to an allocation site, this points to the group which replaced
+ // this one. This link is only needed to keep the replacement group from
+ // being GC'ed. If it were GC'ed and a new one regenerated later, that new
+ // group might have a different allocation kind from this group.
+ GCPtrObjectGroup replacementGroup_;
+
+ // The following members are only used for unboxed plain objects.
+
+ // All properties on objects with this layout, in enumeration order.
+ PropertyVector properties_;
+
+ // Byte size of the data for objects with this layout.
+ size_t size_;
+
+ // Any 'new' script information associated with this layout.
+ TypeNewScript* newScript_;
+
+ // List for use in tracing objects with this layout. This has the same
+ // structure as the trace list on a TypeDescr.
+ int32_t* traceList_;
+
+ // If this layout has been used to construct script or JSON constant
+ // objects, this code might be filled in to more quickly fill in objects
+ // from an array of values.
+ GCPtrJitCode constructorCode_;
+
+ // The following members are only used for unboxed arrays.
+
+ // The type of array elements.
+ JSValueType elementType_;
+
+ public:
+ UnboxedLayout()
+ : nativeGroup_(nullptr), nativeShape_(nullptr),
+ allocationScript_(nullptr), allocationPc_(nullptr), replacementGroup_(nullptr),
+ size_(0), newScript_(nullptr), traceList_(nullptr), constructorCode_(nullptr),
+ elementType_(JSVAL_TYPE_MAGIC)
+ {}
+
+ bool initProperties(const PropertyVector& properties, size_t size) {
+ size_ = size;
+ return properties_.appendAll(properties);
+ }
+
+ void initArray(JSValueType elementType) {
+ elementType_ = elementType;
+ }
+
+ ~UnboxedLayout() {
+ if (newScript_)
+ newScript_->clear();
+ js_delete(newScript_);
+ js_free(traceList_);
+
+ nativeGroup_.init(nullptr);
+ nativeShape_.init(nullptr);
+ replacementGroup_.init(nullptr);
+ constructorCode_.init(nullptr);
+ }
+
+ bool isArray() const {
+ return elementType_ != JSVAL_TYPE_MAGIC;
+ }
+
+ void detachFromCompartment();
+
+ const PropertyVector& properties() const {
+ return properties_;
+ }
+
+ TypeNewScript* newScript() const {
+ return newScript_;
+ }
+
+ void setNewScript(TypeNewScript* newScript, bool writeBarrier = true);
+
+ JSScript* allocationScript() const {
+ return allocationScript_;
+ }
+
+ jsbytecode* allocationPc() const {
+ return allocationPc_;
+ }
+
+ void setAllocationSite(JSScript* script, jsbytecode* pc) {
+ allocationScript_ = script;
+ allocationPc_ = pc;
+ }
+
+ const int32_t* traceList() const {
+ return traceList_;
+ }
+
+ void setTraceList(int32_t* traceList) {
+ traceList_ = traceList;
+ }
+
+ const Property* lookup(JSAtom* atom) const {
+ for (size_t i = 0; i < properties_.length(); i++) {
+ if (properties_[i].name == atom)
+ return &properties_[i];
+ }
+ return nullptr;
+ }
+
+ const Property* lookup(jsid id) const {
+ if (JSID_IS_STRING(id))
+ return lookup(JSID_TO_ATOM(id));
+ return nullptr;
+ }
+
+ size_t size() const {
+ return size_;
+ }
+
+ ObjectGroup* nativeGroup() const {
+ return nativeGroup_;
+ }
+
+ Shape* nativeShape() const {
+ return nativeShape_;
+ }
+
+ jit::JitCode* constructorCode() const {
+ return constructorCode_;
+ }
+
+ void setConstructorCode(jit::JitCode* code) {
+ constructorCode_ = code;
+ }
+
+ JSValueType elementType() const {
+ return elementType_;
+ }
+
+ inline gc::AllocKind getAllocKind() const;
+
+ void trace(JSTracer* trc);
+
+ size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
+
+ static bool makeNativeGroup(JSContext* cx, ObjectGroup* group);
+ static bool makeConstructorCode(JSContext* cx, HandleObjectGroup group);
+};
+
+// Class for expando objects holding extra properties given to an unboxed plain
+// object. These objects behave identically to normal native plain objects, and
+// have a separate Class to distinguish them for memory usage reporting.
+class UnboxedExpandoObject : public NativeObject
+{
+ public:
+ static const Class class_;
+};
+
+// Class for a plain object using an unboxed representation. The physical
+// layout of these objects is identical to that of an InlineTypedObject, though
+// these objects use an UnboxedLayout instead of a TypeDescr to keep track of
+// how their properties are stored.
+class UnboxedPlainObject : public JSObject
+{
+ // Optional object which stores extra properties on this object. This is
+ // not automatically barriered to avoid problems if the object is converted
+ // to a native. See ensureExpando().
+ UnboxedExpandoObject* expando_;
+
+ // Start of the inline data, which immediately follows the group and extra properties.
+ uint8_t data_[1];
+
+ public:
+ static const Class class_;
+
+ static bool obj_lookupProperty(JSContext* cx, HandleObject obj,
+ HandleId id, MutableHandleObject objp,
+ MutableHandleShape propp);
+
+ static bool obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result);
+
+ static bool obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp);
+
+ static bool obj_getProperty(JSContext* cx, HandleObject obj, HandleValue receiver,
+ HandleId id, MutableHandleValue vp);
+
+ static bool obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
+ HandleValue receiver, ObjectOpResult& result);
+
+ static bool obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id,
+ MutableHandle<PropertyDescriptor> desc);
+
+ static bool obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id,
+ ObjectOpResult& result);
+
+ static bool obj_enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties,
+ bool enumerableOnly);
+ static bool obj_watch(JSContext* cx, HandleObject obj, HandleId id, HandleObject callable);
+
+ inline const UnboxedLayout& layout() const;
+
+ const UnboxedLayout& layoutDontCheckGeneration() const {
+ return group()->unboxedLayoutDontCheckGeneration();
+ }
+
+ uint8_t* data() {
+ return &data_[0];
+ }
+
+ UnboxedExpandoObject* maybeExpando() const {
+ return expando_;
+ }
+
+ void initExpando() {
+ expando_ = nullptr;
+ }
+
+ // For use during GC.
+ JSObject** addressOfExpando() {
+ return reinterpret_cast<JSObject**>(&expando_);
+ }
+
+ bool containsUnboxedOrExpandoProperty(ExclusiveContext* cx, jsid id) const;
+
+ static UnboxedExpandoObject* ensureExpando(JSContext* cx, Handle<UnboxedPlainObject*> obj);
+
+ bool setValue(ExclusiveContext* cx, const UnboxedLayout::Property& property, const Value& v);
+ Value getValue(const UnboxedLayout::Property& property, bool maybeUninitialized = false);
+
+ static bool convertToNative(JSContext* cx, JSObject* obj);
+ static UnboxedPlainObject* create(ExclusiveContext* cx, HandleObjectGroup group,
+ NewObjectKind newKind);
+ static JSObject* createWithProperties(ExclusiveContext* cx, HandleObjectGroup group,
+ NewObjectKind newKind, IdValuePair* properties);
+
+ void fillAfterConvert(ExclusiveContext* cx,
+ Handle<GCVector<Value>> values, size_t* valueCursor);
+
+ static void trace(JSTracer* trc, JSObject* object);
+
+ static size_t offsetOfExpando() {
+ return offsetof(UnboxedPlainObject, expando_);
+ }
+
+ static size_t offsetOfData() {
+ return offsetof(UnboxedPlainObject, data_[0]);
+ }
+};
+
+// Try to construct an UnboxedLayout for each of the preliminary objects,
+// provided they all match the template shape. If successful, converts the
+// preliminary objects and their group to the new unboxed representation.
+bool
+TryConvertToUnboxedLayout(ExclusiveContext* cx, AutoEnterAnalysis& enter, Shape* templateShape,
+ ObjectGroup* group, PreliminaryObjectArray* objects);
+
+inline gc::AllocKind
+UnboxedLayout::getAllocKind() const
+{
+ MOZ_ASSERT(size());
+ return gc::GetGCObjectKindForBytes(UnboxedPlainObject::offsetOfData() + size());
+}
+
+// Class for an array object using an unboxed representation.
+class UnboxedArrayObject : public JSObject
+{
+ // Elements pointer for the object.
+ uint8_t* elements_;
+
+ // The nominal array length. This always fits in an int32_t.
+ uint32_t length_;
+
+ // Value indicating the allocated capacity and initialized length of the
+ // array. The top CapacityBits bits are an index into CapacityArray, which
+ // indicates the elements capacity. The low InitializedLengthBits store the
+ // initialized length of the array.
+ uint32_t capacityIndexAndInitializedLength_;
+
+ // If the elements are inline, they will point here.
+ uint8_t inlineElements_[1];
+
+ public:
+ static const uint32_t CapacityBits = 6;
+ static const uint32_t CapacityShift = 26;
+
+ static const uint32_t CapacityMask = uint32_t(-1) << CapacityShift;
+ static const uint32_t InitializedLengthMask = (1 << CapacityShift) - 1;
+
+ static const uint32_t MaximumCapacity = InitializedLengthMask;
+ static const uint32_t MinimumDynamicCapacity = 8;
+
+ static const uint32_t CapacityArray[];
+
+ // Capacity index which indicates the array's length is also its capacity.
+ static const uint32_t CapacityMatchesLengthIndex = 0;
+
+ private:
+ static inline uint32_t computeCapacity(uint32_t index, uint32_t length) {
+ if (index == CapacityMatchesLengthIndex)
+ return length;
+ return CapacityArray[index];
+ }
+
+ static uint32_t chooseCapacityIndex(uint32_t capacity, uint32_t length);
+ static uint32_t exactCapacityIndex(uint32_t capacity);
+
+ public:
+ static const Class class_;
+
+ static bool obj_lookupProperty(JSContext* cx, HandleObject obj,
+ HandleId id, MutableHandleObject objp,
+ MutableHandleShape propp);
+
+ static bool obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result);
+
+ static bool obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp);
+
+ static bool obj_getProperty(JSContext* cx, HandleObject obj, HandleValue receiver,
+ HandleId id, MutableHandleValue vp);
+
+ static bool obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
+ HandleValue receiver, ObjectOpResult& result);
+
+ static bool obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id,
+ MutableHandle<PropertyDescriptor> desc);
+
+ static bool obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id,
+ ObjectOpResult& result);
+
+ static bool obj_enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties,
+ bool enumerableOnly);
+ static bool obj_watch(JSContext* cx, HandleObject obj, HandleId id, HandleObject callable);
+
+ inline const UnboxedLayout& layout() const;
+
+ const UnboxedLayout& layoutDontCheckGeneration() const {
+ return group()->unboxedLayoutDontCheckGeneration();
+ }
+
+ JSValueType elementType() const {
+ return layoutDontCheckGeneration().elementType();
+ }
+
+ uint32_t elementSize() const {
+ return UnboxedTypeSize(elementType());
+ }
+
+ static bool convertToNative(JSContext* cx, JSObject* obj);
+ static UnboxedArrayObject* create(ExclusiveContext* cx, HandleObjectGroup group,
+ uint32_t length, NewObjectKind newKind,
+ uint32_t maxLength = MaximumCapacity);
+
+ static bool convertToNativeWithGroup(ExclusiveContext* cx, JSObject* obj,
+ ObjectGroup* group, Shape* shape);
+ bool convertInt32ToDouble(ExclusiveContext* cx, ObjectGroup* group);
+
+ void fillAfterConvert(ExclusiveContext* cx,
+ Handle<GCVector<Value>> values, size_t* valueCursor);
+
+ static void trace(JSTracer* trc, JSObject* object);
+ static void objectMoved(JSObject* obj, const JSObject* old);
+ static void finalize(FreeOp* fop, JSObject* obj);
+
+ static size_t objectMovedDuringMinorGC(JSTracer* trc, JSObject* dst, JSObject* src,
+ gc::AllocKind allocKind);
+
+ uint8_t* elements() {
+ return elements_;
+ }
+
+ bool hasInlineElements() const {
+ return elements_ == &inlineElements_[0];
+ }
+
+ uint32_t length() const {
+ return length_;
+ }
+
+ uint32_t initializedLength() const {
+ return capacityIndexAndInitializedLength_ & InitializedLengthMask;
+ }
+
+ uint32_t capacityIndex() const {
+ return (capacityIndexAndInitializedLength_ & CapacityMask) >> CapacityShift;
+ }
+
+ uint32_t capacity() const {
+ return computeCapacity(capacityIndex(), length());
+ }
+
+ bool containsProperty(ExclusiveContext* cx, jsid id);
+
+ bool setElement(ExclusiveContext* cx, size_t index, const Value& v);
+ bool initElement(ExclusiveContext* cx, size_t index, const Value& v);
+ void initElementNoTypeChange(size_t index, const Value& v);
+ Value getElement(size_t index);
+
+ template <JSValueType Type> inline bool setElementSpecific(ExclusiveContext* cx, size_t index,
+ const Value& v);
+ template <JSValueType Type> inline void setElementNoTypeChangeSpecific(size_t index, const Value& v);
+ template <JSValueType Type> inline bool initElementSpecific(ExclusiveContext* cx, size_t index,
+ const Value& v);
+ template <JSValueType Type> inline void initElementNoTypeChangeSpecific(size_t index, const Value& v);
+ template <JSValueType Type> inline Value getElementSpecific(size_t index);
+ template <JSValueType Type> inline void triggerPreBarrier(size_t index);
+
+ bool growElements(ExclusiveContext* cx, size_t cap);
+ void shrinkElements(ExclusiveContext* cx, size_t cap);
+
+ static uint32_t offsetOfElements() {
+ return offsetof(UnboxedArrayObject, elements_);
+ }
+ static uint32_t offsetOfLength() {
+ return offsetof(UnboxedArrayObject, length_);
+ }
+ static uint32_t offsetOfCapacityIndexAndInitializedLength() {
+ return offsetof(UnboxedArrayObject, capacityIndexAndInitializedLength_);
+ }
+ static uint32_t offsetOfInlineElements() {
+ return offsetof(UnboxedArrayObject, inlineElements_);
+ }
+
+ void setLengthInt32(uint32_t length) {
+ MOZ_ASSERT(length <= INT32_MAX);
+ length_ = length;
+ }
+
+ inline void setLength(ExclusiveContext* cx, uint32_t len);
+ inline void setInitializedLength(uint32_t initlen);
+
+ inline void setInitializedLengthNoBarrier(uint32_t initlen) {
+ MOZ_ASSERT(initlen <= InitializedLengthMask);
+ capacityIndexAndInitializedLength_ =
+ (capacityIndexAndInitializedLength_ & CapacityMask) | initlen;
+ }
+
+ private:
+ void setInlineElements() {
+ elements_ = &inlineElements_[0];
+ }
+
+ void setCapacityIndex(uint32_t index) {
+ MOZ_ASSERT(index <= (CapacityMask >> CapacityShift));
+ capacityIndexAndInitializedLength_ =
+ (index << CapacityShift) | initializedLength();
+ }
+};
+
+} // namespace js
+
+namespace JS {
+
+template <>
+struct DeletePolicy<js::UnboxedLayout> : public js::GCManagedDeletePolicy<js::UnboxedLayout>
+{};
+
+} /* namespace JS */
+
+#endif /* vm_UnboxedObject_h */