| /* -*- 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/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 mozilla::UniquePtr; |
| |
| 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); |
| |
| if (nativeGroup_) |
| TraceEdge(trc, &nativeGroup_, "unboxed_layout_nativeGroup"); |
| |
| if (nativeShape_) |
| TraceEdge(trc, &nativeShape_, "unboxed_layout_nativeShape"); |
| |
| if (allocationScript_) |
| TraceEdge(trc, &allocationScript_, "unboxed_layout_allocationScript"); |
| |
| if (replacementGroup_) |
| TraceEdge(trc, &replacementGroup_, "unboxed_layout_replacementGroup"); |
| |
| if (constructorCode_) |
| TraceEdge(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 |
| |
| 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, ¬Object); |
| Register valueObject = masm.extractObject(valueAddress, scratch1); |
| masm.branchPtrInNurseryRange(Assembler::Equal, valueObject, scratch2, &postBarrier); |
| masm.bind(¬Object); |
| } |
| } |
| |
| 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) ? ¬Object : &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(¬Object); |
| 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) { |
| HeapPtrString* heap = reinterpret_cast<HeapPtrString*>(data + *list); |
| TraceEdge(trc, heap, "unboxed_string"); |
| list++; |
| } |
| list++; |
| while (*list != -1) { |
| HeapPtrObject* heap = reinterpret_cast<HeapPtrObject*>(data + *list); |
| if (*heap) |
| TraceEdge(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; |
| |
| // 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 HeapPtrObject 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->compartment()->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; |
| |
| // 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); |
| } |
| } |
| |
| 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) |
| { |
| 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) { |
| HeapPtrString* heap = reinterpret_cast<HeapPtrString*>(data + *list); |
| heap->init(cx->names().empty); |
| list++; |
| } |
| list++; |
| while (*list != -1) { |
| HeapPtrObject* heap = reinterpret_cast<HeapPtrObject*>(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() && |
| !layout.constructorCode() && |
| cx->asJSContext()->runtime()->jitSupportsFloatingPoint) |
| { |
| 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->getProto()); |
| 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<JSPropertyDescriptor> 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->getProto()); |
| 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->getProto()); |
| 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<JSPropertyDescriptor> 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_watch(JSContext* cx, HandleObject obj, HandleId id, HandleObject callable) |
| { |
| if (!convertToNative(cx, obj)) |
| return false; |
| return WatchProperty(cx, obj, id, callable); |
| } |
| |
| /* 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 |
| }; |
| |
| const Class UnboxedPlainObject::class_ = { |
| js_Object_str, |
| Class::NON_NATIVE | |
| JSCLASS_HAS_CACHED_PROTO(JSProto_Object), |
| nullptr, /* addProperty */ |
| nullptr, /* delProperty */ |
| nullptr, /* getProperty */ |
| nullptr, /* setProperty */ |
| nullptr, /* enumerate */ |
| nullptr, /* resolve */ |
| nullptr, /* mayResolve */ |
| nullptr, /* finalize */ |
| nullptr, /* call */ |
| nullptr, /* hasInstance */ |
| nullptr, /* construct */ |
| UnboxedPlainObject::trace, |
| JS_NULL_CLASS_SPEC, |
| JS_NULL_CLASS_EXT, |
| { |
| UnboxedPlainObject::obj_lookupProperty, |
| UnboxedPlainObject::obj_defineProperty, |
| UnboxedPlainObject::obj_hasProperty, |
| UnboxedPlainObject::obj_getProperty, |
| UnboxedPlainObject::obj_setProperty, |
| UnboxedPlainObject::obj_getOwnPropertyDescriptor, |
| UnboxedPlainObject::obj_deleteProperty, |
| UnboxedPlainObject::obj_watch, |
| nullptr, /* No unwatch needed, as watch() converts the object to native */ |
| nullptr, /* getElements */ |
| UnboxedPlainObject::obj_enumerate, |
| } |
| }; |
| |
| ///////////////////////////////////////////////////////////////////// |
| // UnboxedArrayObject |
| ///////////////////////////////////////////////////////////////////// |
| |
| template <JSValueType Type> |
| DenseElementResult |
| AppendUnboxedDenseElements(UnboxedArrayObject* obj, uint32_t initlen, AutoValueVector* values) |
| { |
| for (size_t i = 0; i < initlen; i++) |
| values->infallibleAppend(obj->getElementSpecific<Type>(i)); |
| return DenseElementResult::Success; |
| } |
| |
| DefineBoxedOrUnboxedFunctor3(AppendUnboxedDenseElements, |
| UnboxedArrayObject*, uint32_t, AutoValueVector*); |
| |
| /* 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(); |
| |
| AutoValueVector values(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++) { |
| HeapPtrObject* heap = reinterpret_cast<HeapPtrObject*>(elements + i); |
| if (*heap) |
| TraceEdge(trc, heap, "unboxed_object"); |
| } |
| break; |
| |
| case JSVAL_TYPE_STRING: |
| for (size_t i = 0; i < initlen; i++) { |
| HeapPtrString* heap = reinterpret_cast<HeapPtrString*>(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->getProto()); |
| 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<JSPropertyDescriptor> 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->getProto()); |
| 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->getProto()); |
| 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<JSPropertyDescriptor> 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_watch(JSContext* cx, HandleObject obj, HandleId id, HandleObject callable) |
| { |
| if (!convertToNative(cx, obj)) |
| return false; |
| return WatchProperty(cx, obj, id, callable); |
| } |
| |
| /* 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; |
| } |
| |
| const Class UnboxedArrayObject::class_ = { |
| "Array", |
| Class::NON_NATIVE | |
| JSCLASS_SKIP_NURSERY_FINALIZE | |
| JSCLASS_BACKGROUND_FINALIZE, |
| nullptr, /* addProperty */ |
| nullptr, /* delProperty */ |
| nullptr, /* getProperty */ |
| nullptr, /* setProperty */ |
| nullptr, /* enumerate */ |
| nullptr, /* resolve */ |
| nullptr, /* mayResolve */ |
| UnboxedArrayObject::finalize, |
| nullptr, /* call */ |
| nullptr, /* hasInstance */ |
| nullptr, /* construct */ |
| UnboxedArrayObject::trace, |
| JS_NULL_CLASS_SPEC, |
| { |
| false, /* isWrappedNative */ |
| nullptr, /* weakmapKeyDelegateOp */ |
| UnboxedArrayObject::objectMoved |
| }, |
| { |
| UnboxedArrayObject::obj_lookupProperty, |
| UnboxedArrayObject::obj_defineProperty, |
| UnboxedArrayObject::obj_hasProperty, |
| UnboxedArrayObject::obj_getProperty, |
| UnboxedArrayObject::obj_setProperty, |
| UnboxedArrayObject::obj_getOwnPropertyDescriptor, |
| UnboxedArrayObject::obj_deleteProperty, |
| UnboxedArrayObject::obj_watch, |
| nullptr, /* No unwatch needed, as watch() converts the object to native */ |
| nullptr, /* getElements */ |
| UnboxedArrayObject::obj_enumerate, |
| } |
| }; |
| |
| ///////////////////////////////////////////////////////////////////// |
| // 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(const AutoValueVector& values, size_t* valueCursor) |
| { |
| return values[(*valueCursor)++]; |
| } |
| |
| static bool |
| GetValuesFromPreliminaryArrayObject(ArrayObject* obj, AutoValueVector& 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, |
| const AutoValueVector& 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, AutoValueVector& 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, |
| const AutoValueVector& 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, Shape* templateShape, |
| ObjectGroup* group, PreliminaryObjectArray* objects) |
| { |
| bool isArray = !templateShape; |
| |
| // Unboxed arrays are nightly only for now. The js_sb_getenv() call will be |
| // removed when they are on by default. See bug 1153266. |
| if (isArray) { |
| #ifdef NIGHTLY_BUILD |
| if (!js_sb_getenv("JS_OPTION_USE_UNBOXED_ARRAYS")) { |
| if (!group->runtimeFromAnyThread()->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; |
| } |
| |
| AutoInitGCManagedObject<UnboxedLayout> 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. |
| AutoValueVector values(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()); |
| layout.release(); |
| 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); |
| } |