summaryrefslogtreecommitdiff
path: root/js/src/vm/UnboxedObject.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/UnboxedObject.cpp')
-rw-r--r--js/src/vm/UnboxedObject.cpp946
1 files changed, 946 insertions, 0 deletions
diff --git a/js/src/vm/UnboxedObject.cpp b/js/src/vm/UnboxedObject.cpp
new file mode 100644
index 0000000000..2e017ca3bd
--- /dev/null
+++ b/js/src/vm/UnboxedObject.cpp
@@ -0,0 +1,946 @@
+/* -*- 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);
+
+ // 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()) {
+ 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, &PlainObject::class_, proto);
+ if (!replacementGroup)
+ return false;
+
+ PlainObject* templateObject = &script->getObject(pc)->as<PlainObject>();
+ replacementGroup->addDefiniteProperties(cx, templateObject->lastProperty());
+
+ cx->compartment()->objectGroups.replaceAllocationSiteGroup(script, pc, JSProto_Object,
+ 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 = gc::GetGCKindSlots(layout.getAllocKind());
+
+ RootedShape shape(cx, EmptyShape::getInitialShape(cx, &PlainObject::class_, proto, nfixed, 0));
+ if (!shape)
+ return false;
+
+ // 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, &PlainObject::class_, 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.
+ 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
+};
+
+/////////////////////////////////////////////////////////////////////
+// API
+/////////////////////////////////////////////////////////////////////
+
+static inline Value
+NextValue(Handle<GCVector<Value>> values, size_t* valueCursor)
+{
+ return values[(*valueCursor)++];
+}
+
+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)));
+}