diff options
Diffstat (limited to 'js/src/vm/UnboxedObject.cpp')
-rw-r--r-- | js/src/vm/UnboxedObject.cpp | 1752 |
1 files changed, 0 insertions, 1752 deletions
diff --git a/js/src/vm/UnboxedObject.cpp b/js/src/vm/UnboxedObject.cpp deleted file mode 100644 index d7ad91de4..000000000 --- a/js/src/vm/UnboxedObject.cpp +++ /dev/null @@ -1,1752 +0,0 @@ -/* -*- 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, ¬Object); - Register valueObject = masm.extractObject(valueAddress, scratch1); - masm.branchPtrInNurseryChunk(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) { - GCPtrString* heap = reinterpret_cast<GCPtrString*>(data + *list); - TraceEdge(trc, heap, "unboxed_string"); - list++; - } - list++; - while (*list != -1) { - GCPtrObject* heap = reinterpret_cast<GCPtrObject*>(data + *list); - TraceNullableEdge(trc, heap, "unboxed_object"); - list++; - } - - // Unboxed objects don't have Values to trace. - MOZ_ASSERT(*(list + 1) == -1); -} - -/* static */ UnboxedExpandoObject* -UnboxedPlainObject::ensureExpando(JSContext* cx, Handle<UnboxedPlainObject*> obj) -{ - if (obj->expando_) - return obj->expando_; - - UnboxedExpandoObject* expando = - NewObjectWithGivenProto<UnboxedExpandoObject>(cx, nullptr, gc::AllocKind::OBJECT4); - if (!expando) - return nullptr; - - // Don't track property types for expando objects. This allows Baseline - // and Ion AddSlot ICs to guard on the unboxed group without guarding on - // the expando group. - MarkObjectGroupUnknownProperties(cx, expando->group()); - - // If the expando is tenured then the original object must also be tenured. - // Otherwise barriers triggered on the original object for writes to the - // expando (as can happen in the JIT) won't see the tenured->nursery edge. - // See WholeCellEdges::mark. - MOZ_ASSERT_IF(!IsInsideNursery(expando), !IsInsideNursery(obj)); - - // As with setValue(), we need to manually trigger post barriers on the - // whole object. If we treat the field as a GCPtrObject and later - // convert the object to its native representation, we will end up with a - // corrupted store buffer entry. - if (IsInsideNursery(expando) && !IsInsideNursery(obj)) - cx->runtime()->gc.storeBuffer.putWholeCell(obj); - - obj->expando_ = expando; - return expando; -} - -bool -UnboxedPlainObject::containsUnboxedOrExpandoProperty(ExclusiveContext* cx, jsid id) const -{ - if (layout().lookup(id)) - return true; - - if (maybeExpando() && maybeExpando()->containsShapeOrElement(cx, id)) - return true; - - return false; -} - -static bool -PropagatePropertyTypes(JSContext* cx, jsid id, ObjectGroup* oldGroup, ObjectGroup* newGroup) -{ - HeapTypeSet* typeProperty = oldGroup->maybeGetProperty(id); - TypeSet::TypeList types; - if (!typeProperty->enumerateTypes(&types)) { - ReportOutOfMemory(cx); - return false; - } - for (size_t j = 0; j < types.length(); j++) - AddTypePropertyId(cx, newGroup, nullptr, id, types[j]); - return true; -} - -static PlainObject* -MakeReplacementTemplateObject(JSContext* cx, HandleObjectGroup group, const UnboxedLayout &layout) -{ - PlainObject* obj = NewObjectWithGroup<PlainObject>(cx, group, layout.getAllocKind(), - TenuredObject); - if (!obj) - return nullptr; - - for (size_t i = 0; i < layout.properties().length(); i++) { - const UnboxedLayout::Property& property = layout.properties()[i]; - if (!obj->addDataProperty(cx, NameToId(property.name), i, JSPROP_ENUMERATE)) - return nullptr; - MOZ_ASSERT(obj->slotSpan() == i + 1); - MOZ_ASSERT(!obj->inDictionaryMode()); - } - - return obj; -} - -/* static */ bool -UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group) -{ - AutoEnterAnalysis enter(cx); - - UnboxedLayout& layout = group->unboxedLayout(); - Rooted<TaggedProto> proto(cx, group->proto()); - - MOZ_ASSERT(!layout.nativeGroup()); - - RootedObjectGroup replacementGroup(cx); - - const Class* clasp = layout.isArray() ? &ArrayObject::class_ : &PlainObject::class_; - - // Immediately clear any new script on the group. This is done by replacing - // the existing new script with one for a replacement default new group. - // This is done so that the size of the replacment group's objects is the - // same as that for the unboxed group, so that we do not see polymorphic - // slot accesses later on for sites that see converted objects from this - // group and objects that were allocated using the replacement new group. - if (layout.newScript()) { - MOZ_ASSERT(!layout.isArray()); - - replacementGroup = ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto); - if (!replacementGroup) - return false; - - PlainObject* templateObject = MakeReplacementTemplateObject(cx, replacementGroup, layout); - if (!templateObject) - return false; - - TypeNewScript* replacementNewScript = - TypeNewScript::makeNativeVersion(cx, layout.newScript(), templateObject); - if (!replacementNewScript) - return false; - - replacementGroup->setNewScript(replacementNewScript); - gc::TraceTypeNewScript(replacementGroup); - - group->clearNewScript(cx, replacementGroup); - } - - // Similarly, if this group is keyed to an allocation site, replace its - // entry with a new group that has no unboxed layout. - if (layout.allocationScript()) { - RootedScript script(cx, layout.allocationScript()); - jsbytecode* pc = layout.allocationPc(); - - replacementGroup = ObjectGroupCompartment::makeGroup(cx, clasp, proto); - if (!replacementGroup) - return false; - - PlainObject* templateObject = &script->getObject(pc)->as<PlainObject>(); - replacementGroup->addDefiniteProperties(cx, templateObject->lastProperty()); - - JSProtoKey key = layout.isArray() ? JSProto_Array : JSProto_Object; - cx->compartment()->objectGroups.replaceAllocationSiteGroup(script, pc, key, - replacementGroup); - - // Clear any baseline information at this opcode which might use the old group. - if (script->hasBaselineScript()) { - jit::ICEntry& entry = script->baselineScript()->icEntryFromPCOffset(script->pcToOffset(pc)); - jit::ICFallbackStub* fallback = entry.fallbackStub(); - for (jit::ICStubIterator iter = fallback->beginChain(); !iter.atEnd(); iter++) - iter.unlink(cx); - if (fallback->isNewObject_Fallback()) - fallback->toNewObject_Fallback()->setTemplateObject(nullptr); - else if (fallback->isNewArray_Fallback()) - fallback->toNewArray_Fallback()->setTemplateGroup(replacementGroup); - } - } - - size_t nfixed = layout.isArray() ? 0 : gc::GetGCKindSlots(layout.getAllocKind()); - - if (layout.isArray()) { - // The length shape to use for arrays is cached via a modified initial - // shape for array objects. Create an array now to make sure this entry - // is instantiated. - if (!NewDenseEmptyArray(cx)) - return false; - } - - RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, proto, nfixed, 0)); - if (!shape) - return false; - - MOZ_ASSERT_IF(layout.isArray(), !shape->isEmptyShape() && shape->slotSpan() == 0); - - // Add shapes for each property, if this is for a plain object. - for (size_t i = 0; i < layout.properties().length(); i++) { - const UnboxedLayout::Property& property = layout.properties()[i]; - - Rooted<StackShape> child(cx, StackShape(shape->base()->unowned(), NameToId(property.name), - i, JSPROP_ENUMERATE, 0)); - shape = cx->zone()->propertyTree.getChild(cx, shape, child); - if (!shape) - return false; - } - - ObjectGroup* nativeGroup = - ObjectGroupCompartment::makeGroup(cx, clasp, proto, - group->flags() & OBJECT_FLAG_DYNAMIC_MASK); - if (!nativeGroup) - return false; - - // No sense propagating if we don't know what we started with. - if (!group->unknownProperties()) { - // Propagate all property types from the old group to the new group. - if (layout.isArray()) { - if (!PropagatePropertyTypes(cx, JSID_VOID, group, nativeGroup)) - return false; - } else { - for (size_t i = 0; i < layout.properties().length(); i++) { - const UnboxedLayout::Property& property = layout.properties()[i]; - jsid id = NameToId(property.name); - if (!PropagatePropertyTypes(cx, id, group, nativeGroup)) - return false; - - // If we are OOM we may not be able to propagate properties. - if (nativeGroup->unknownProperties()) - break; - - HeapTypeSet* nativeProperty = nativeGroup->maybeGetProperty(id); - if (nativeProperty && nativeProperty->canSetDefinite(i)) - nativeProperty->setDefinite(i); - } - } - } else { - // If we skip, though, the new group had better agree. - MOZ_ASSERT(nativeGroup->unknownProperties()); - } - - layout.nativeGroup_ = nativeGroup; - layout.nativeShape_ = shape; - layout.replacementGroup_ = replacementGroup; - - nativeGroup->setOriginalUnboxedGroup(group); - - group->markStateChange(cx); - - return true; -} - -/* static */ bool -UnboxedPlainObject::convertToNative(JSContext* cx, JSObject* obj) -{ - const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layout(); - UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando(); - - if (!layout.nativeGroup()) { - if (!UnboxedLayout::makeNativeGroup(cx, obj->group())) - return false; - - // makeNativeGroup can reentrantly invoke this method. - if (obj->is<PlainObject>()) - return true; - } - - AutoValueVector values(cx); - for (size_t i = 0; i < layout.properties().length(); i++) { - // We might be reading properties off the object which have not been - // initialized yet. Make sure any double values we read here are - // canonicalized. - if (!values.append(obj->as<UnboxedPlainObject>().getValue(layout.properties()[i], true))) - return false; - } - - // We are eliminating the expando edge with the conversion, so trigger a - // pre barrier. - JSObject::writeBarrierPre(expando); - - // Additionally trigger a post barrier on the expando itself. Whole cell - // store buffer entries can be added on the original unboxed object for - // writes to the expando (see WholeCellEdges::trace), so after conversion - // we need to make sure the expando itself will still be traced. - if (expando && !IsInsideNursery(expando)) - cx->runtime()->gc.storeBuffer.putWholeCell(expando); - - obj->setGroup(layout.nativeGroup()); - obj->as<PlainObject>().setLastPropertyMakeNative(cx, layout.nativeShape()); - - for (size_t i = 0; i < values.length(); i++) - obj->as<PlainObject>().initSlotUnchecked(i, values[i]); - - if (expando) { - // Add properties from the expando object to the object, in order. - // Suppress GC here, so that callers don't need to worry about this - // method collecting. The stuff below can only fail due to OOM, in - // which case the object will not have been completely filled back in. - gc::AutoSuppressGC suppress(cx); - - Vector<jsid> ids(cx); - for (Shape::Range<NoGC> r(expando->lastProperty()); !r.empty(); r.popFront()) { - if (!ids.append(r.front().propid())) - return false; - } - for (size_t i = 0; i < expando->getDenseInitializedLength(); i++) { - if (!expando->getDenseElement(i).isMagic(JS_ELEMENTS_HOLE)) { - if (!ids.append(INT_TO_JSID(i))) - return false; - } - } - ::Reverse(ids.begin(), ids.end()); - - RootedPlainObject nobj(cx, &obj->as<PlainObject>()); - Rooted<UnboxedExpandoObject*> nexpando(cx, expando); - RootedId id(cx); - Rooted<PropertyDescriptor> desc(cx); - for (size_t i = 0; i < ids.length(); i++) { - id = ids[i]; - if (!GetOwnPropertyDescriptor(cx, nexpando, id, &desc)) - return false; - ObjectOpResult result; - if (!DefineProperty(cx, nobj, id, desc, result)) - return false; - MOZ_ASSERT(result.ok()); - } - } - - return true; -} - -/* static */ -UnboxedPlainObject* -UnboxedPlainObject::create(ExclusiveContext* cx, HandleObjectGroup group, NewObjectKind newKind) -{ - AutoSetNewObjectMetadata metadata(cx); - - MOZ_ASSERT(group->clasp() == &class_); - gc::AllocKind allocKind = group->unboxedLayout().getAllocKind(); - - UnboxedPlainObject* res = - NewObjectWithGroup<UnboxedPlainObject>(cx, group, allocKind, newKind); - if (!res) - return nullptr; - - // Overwrite the dummy shape which was written to the object's expando field. - res->initExpando(); - - // Initialize reference fields of the object. All fields in the object will - // be overwritten shortly, but references need to be safe for the GC. - const int32_t* list = res->layout().traceList(); - if (list) { - uint8_t* data = res->data(); - while (*list != -1) { - GCPtrString* heap = reinterpret_cast<GCPtrString*>(data + *list); - heap->init(cx->names().empty); - list++; - } - list++; - while (*list != -1) { - GCPtrObject* heap = reinterpret_cast<GCPtrObject*>(data + *list); - heap->init(nullptr); - list++; - } - // Unboxed objects don't have Values to initialize. - MOZ_ASSERT(*(list + 1) == -1); - } - - return res; -} - -/* static */ JSObject* -UnboxedPlainObject::createWithProperties(ExclusiveContext* cx, HandleObjectGroup group, - NewObjectKind newKind, IdValuePair* properties) -{ - MOZ_ASSERT(newKind == GenericObject || newKind == TenuredObject); - - UnboxedLayout& layout = group->unboxedLayout(); - - if (layout.constructorCode()) { - MOZ_ASSERT(cx->isJSContext()); - - typedef JSObject* (*ConstructorCodeSignature)(IdValuePair*, NewObjectKind); - ConstructorCodeSignature function = - reinterpret_cast<ConstructorCodeSignature>(layout.constructorCode()->raw()); - - JSObject* obj; - { - JS::AutoSuppressGCAnalysis nogc; - obj = reinterpret_cast<JSObject*>(CALL_GENERATED_2(function, properties, newKind)); - } - if (obj > reinterpret_cast<JSObject*>(CLEAR_CONSTRUCTOR_CODE_TOKEN)) - return obj; - - if (obj == reinterpret_cast<JSObject*>(CLEAR_CONSTRUCTOR_CODE_TOKEN)) - layout.setConstructorCode(nullptr); - } - - UnboxedPlainObject* obj = UnboxedPlainObject::create(cx, group, newKind); - if (!obj) - return nullptr; - - for (size_t i = 0; i < layout.properties().length(); i++) { - if (!obj->setValue(cx, layout.properties()[i], properties[i].value)) - return NewPlainObjectWithProperties(cx, properties, layout.properties().length(), newKind); - } - -#ifndef JS_CODEGEN_NONE - if (cx->isJSContext() && - !group->unknownProperties() && - !layout.constructorCode() && - cx->asJSContext()->runtime()->jitSupportsFloatingPoint && - jit::CanLikelyAllocateMoreExecutableMemory()) - { - if (!UnboxedLayout::makeConstructorCode(cx->asJSContext(), group)) - return nullptr; - } -#endif - - return obj; -} - -/* static */ bool -UnboxedPlainObject::obj_lookupProperty(JSContext* cx, HandleObject obj, - HandleId id, MutableHandleObject objp, - MutableHandleShape propp) -{ - if (obj->as<UnboxedPlainObject>().containsUnboxedOrExpandoProperty(cx, id)) { - MarkNonNativePropertyFound<CanGC>(propp); - objp.set(obj); - return true; - } - - RootedObject proto(cx, obj->staticPrototype()); - if (!proto) { - objp.set(nullptr); - propp.set(nullptr); - return true; - } - - return LookupProperty(cx, proto, id, objp, propp); -} - -/* static */ bool -UnboxedPlainObject::obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id, - Handle<PropertyDescriptor> desc, - ObjectOpResult& result) -{ - const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layout(); - - if (const UnboxedLayout::Property* property = layout.lookup(id)) { - if (!desc.getter() && !desc.setter() && desc.attributes() == JSPROP_ENUMERATE) { - // This define is equivalent to setting an existing property. - if (obj->as<UnboxedPlainObject>().setValue(cx, *property, desc.value())) - return result.succeed(); - } - - // Trying to incompatibly redefine an existing property requires the - // object to be converted to a native object. - if (!convertToNative(cx, obj)) - return false; - - return DefineProperty(cx, obj, id, desc, result); - } - - // Define the property on the expando object. - Rooted<UnboxedExpandoObject*> expando(cx, ensureExpando(cx, obj.as<UnboxedPlainObject>())); - if (!expando) - return false; - - // Update property types on the unboxed object as well. - AddTypePropertyId(cx, obj, id, desc.value()); - - return DefineProperty(cx, expando, id, desc, result); -} - -/* static */ bool -UnboxedPlainObject::obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp) -{ - if (obj->as<UnboxedPlainObject>().containsUnboxedOrExpandoProperty(cx, id)) { - *foundp = true; - return true; - } - - RootedObject proto(cx, obj->staticPrototype()); - if (!proto) { - *foundp = false; - return true; - } - - return HasProperty(cx, proto, id, foundp); -} - -/* static */ bool -UnboxedPlainObject::obj_getProperty(JSContext* cx, HandleObject obj, HandleValue receiver, - HandleId id, MutableHandleValue vp) -{ - const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layout(); - - if (const UnboxedLayout::Property* property = layout.lookup(id)) { - vp.set(obj->as<UnboxedPlainObject>().getValue(*property)); - return true; - } - - if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) { - if (expando->containsShapeOrElement(cx, id)) { - RootedObject nexpando(cx, expando); - return GetProperty(cx, nexpando, receiver, id, vp); - } - } - - RootedObject proto(cx, obj->staticPrototype()); - if (!proto) { - vp.setUndefined(); - return true; - } - - return GetProperty(cx, proto, receiver, id, vp); -} - -/* static */ bool -UnboxedPlainObject::obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v, - HandleValue receiver, ObjectOpResult& result) -{ - const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layout(); - - if (const UnboxedLayout::Property* property = layout.lookup(id)) { - if (receiver.isObject() && obj == &receiver.toObject()) { - if (obj->as<UnboxedPlainObject>().setValue(cx, *property, v)) - return result.succeed(); - - if (!convertToNative(cx, obj)) - return false; - return SetProperty(cx, obj, id, v, receiver, result); - } - - return SetPropertyByDefining(cx, id, v, receiver, result); - } - - if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) { - if (expando->containsShapeOrElement(cx, id)) { - // Update property types on the unboxed object as well. - AddTypePropertyId(cx, obj, id, v); - - RootedObject nexpando(cx, expando); - return SetProperty(cx, nexpando, id, v, receiver, result); - } - } - - return SetPropertyOnProto(cx, obj, id, v, receiver, result); -} - -/* static */ bool -UnboxedPlainObject::obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id, - MutableHandle<PropertyDescriptor> desc) -{ - const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layout(); - - if (const UnboxedLayout::Property* property = layout.lookup(id)) { - desc.value().set(obj->as<UnboxedPlainObject>().getValue(*property)); - desc.setAttributes(JSPROP_ENUMERATE); - desc.object().set(obj); - return true; - } - - if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) { - if (expando->containsShapeOrElement(cx, id)) { - RootedObject nexpando(cx, expando); - if (!GetOwnPropertyDescriptor(cx, nexpando, id, desc)) - return false; - if (desc.object() == nexpando) - desc.object().set(obj); - return true; - } - } - - desc.object().set(nullptr); - return true; -} - -/* static */ bool -UnboxedPlainObject::obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id, - ObjectOpResult& result) -{ - if (!convertToNative(cx, obj)) - return false; - return DeleteProperty(cx, obj, id, result); -} - -/* static */ bool -UnboxedPlainObject::obj_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 -}; - -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, - UnboxedPlainObject::obj_watch, - nullptr, /* No unwatch needed, as watch() converts the object to native */ - nullptr, /* getElements */ - UnboxedPlainObject::obj_enumerate, - nullptr /* funToString */ -}; - -const Class UnboxedPlainObject::class_ = { - js_Object_str, - Class::NON_NATIVE | - JSCLASS_HAS_CACHED_PROTO(JSProto_Object) | - JSCLASS_DELAY_METADATA_BUILDER, - &UnboxedPlainObjectClassOps, - JS_NULL_CLASS_SPEC, - JS_NULL_CLASS_EXT, - &UnboxedPlainObjectObjectOps -}; - -///////////////////////////////////////////////////////////////////// -// UnboxedArrayObject -///////////////////////////////////////////////////////////////////// - -template <JSValueType Type> -DenseElementResult -AppendUnboxedDenseElements(UnboxedArrayObject* obj, uint32_t initlen, - MutableHandle<GCVector<Value>> values) -{ - for (size_t i = 0; i < initlen; i++) - values.infallibleAppend(obj->getElementSpecific<Type>(i)); - return DenseElementResult::Success; -} - -DefineBoxedOrUnboxedFunctor3(AppendUnboxedDenseElements, - UnboxedArrayObject*, uint32_t, MutableHandle<GCVector<Value>>); - -/* static */ bool -UnboxedArrayObject::convertToNativeWithGroup(ExclusiveContext* cx, JSObject* obj, - ObjectGroup* group, Shape* shape) -{ - size_t length = obj->as<UnboxedArrayObject>().length(); - size_t initlen = obj->as<UnboxedArrayObject>().initializedLength(); - - Rooted<GCVector<Value>> values(cx, GCVector<Value>(cx)); - if (!values.reserve(initlen)) - return false; - - AppendUnboxedDenseElementsFunctor functor(&obj->as<UnboxedArrayObject>(), initlen, &values); - DebugOnly<DenseElementResult> result = CallBoxedOrUnboxedSpecialization(functor, obj); - MOZ_ASSERT(result.value == DenseElementResult::Success); - - obj->setGroup(group); - - ArrayObject* aobj = &obj->as<ArrayObject>(); - aobj->setLastPropertyMakeNative(cx, shape); - - // Make sure there is at least one element, so that this array does not - // use emptyObjectElements / emptyObjectElementsShared. - if (!aobj->ensureElements(cx, Max<size_t>(initlen, 1))) - return false; - - MOZ_ASSERT(!aobj->getDenseInitializedLength()); - aobj->setDenseInitializedLength(initlen); - aobj->initDenseElements(0, values.begin(), initlen); - aobj->setLengthInt32(length); - - return true; -} - -/* static */ bool -UnboxedArrayObject::convertToNative(JSContext* cx, JSObject* obj) -{ - const UnboxedLayout& layout = obj->as<UnboxedArrayObject>().layout(); - - if (!layout.nativeGroup()) { - if (!UnboxedLayout::makeNativeGroup(cx, obj->group())) - return false; - } - - return convertToNativeWithGroup(cx, obj, layout.nativeGroup(), layout.nativeShape()); -} - -bool -UnboxedArrayObject::convertInt32ToDouble(ExclusiveContext* cx, ObjectGroup* group) -{ - MOZ_ASSERT(elementType() == JSVAL_TYPE_INT32); - MOZ_ASSERT(group->unboxedLayout().elementType() == JSVAL_TYPE_DOUBLE); - - Vector<int32_t> values(cx); - if (!values.reserve(initializedLength())) - return false; - for (size_t i = 0; i < initializedLength(); i++) - values.infallibleAppend(getElementSpecific<JSVAL_TYPE_INT32>(i).toInt32()); - - uint8_t* newElements; - if (hasInlineElements()) { - newElements = AllocateObjectBuffer<uint8_t>(cx, this, capacity() * sizeof(double)); - } else { - newElements = ReallocateObjectBuffer<uint8_t>(cx, this, elements(), - capacity() * sizeof(int32_t), - capacity() * sizeof(double)); - } - if (!newElements) - return false; - - setGroup(group); - elements_ = newElements; - - for (size_t i = 0; i < initializedLength(); i++) - setElementNoTypeChangeSpecific<JSVAL_TYPE_DOUBLE>(i, DoubleValue(values[i])); - - return true; -} - -/* static */ UnboxedArrayObject* -UnboxedArrayObject::create(ExclusiveContext* cx, HandleObjectGroup group, uint32_t length, - NewObjectKind newKind, uint32_t maxLength) -{ - MOZ_ASSERT(length <= MaximumCapacity); - - MOZ_ASSERT(group->clasp() == &class_); - uint32_t elementSize = UnboxedTypeSize(group->unboxedLayout().elementType()); - uint32_t capacity = Min(length, maxLength); - uint32_t nbytes = offsetOfInlineElements() + elementSize * capacity; - - UnboxedArrayObject* res; - if (nbytes <= JSObject::MAX_BYTE_SIZE) { - gc::AllocKind allocKind = gc::GetGCObjectKindForBytes(nbytes); - - // If there was no provided length information, pick an allocation kind - // to accommodate small arrays (as is done for normal native arrays). - if (capacity == 0) - allocKind = gc::AllocKind::OBJECT8; - - res = NewObjectWithGroup<UnboxedArrayObject>(cx, group, allocKind, newKind); - if (!res) - return nullptr; - res->setInitializedLengthNoBarrier(0); - res->setInlineElements(); - - size_t actualCapacity = (GetGCKindBytes(allocKind) - offsetOfInlineElements()) / elementSize; - MOZ_ASSERT(actualCapacity >= capacity); - res->setCapacityIndex(exactCapacityIndex(actualCapacity)); - } else { - res = NewObjectWithGroup<UnboxedArrayObject>(cx, group, gc::AllocKind::OBJECT0, newKind); - if (!res) - return nullptr; - res->setInitializedLengthNoBarrier(0); - - uint32_t capacityIndex = (capacity == length) - ? CapacityMatchesLengthIndex - : chooseCapacityIndex(capacity, length); - uint32_t actualCapacity = computeCapacity(capacityIndex, length); - - res->elements_ = AllocateObjectBuffer<uint8_t>(cx, res, actualCapacity * elementSize); - if (!res->elements_) { - // Make the object safe for GC. - res->setInlineElements(); - return nullptr; - } - - res->setCapacityIndex(capacityIndex); - } - - res->setLength(cx, length); - return res; -} - -bool -UnboxedArrayObject::setElement(ExclusiveContext* cx, size_t index, const Value& v) -{ - MOZ_ASSERT(index < initializedLength()); - uint8_t* p = elements() + index * elementSize(); - return SetUnboxedValue(cx, this, JSID_VOID, p, elementType(), v, /* preBarrier = */ true); -} - -bool -UnboxedArrayObject::initElement(ExclusiveContext* cx, size_t index, const Value& v) -{ - MOZ_ASSERT(index < initializedLength()); - uint8_t* p = elements() + index * elementSize(); - return SetUnboxedValue(cx, this, JSID_VOID, p, elementType(), v, /* preBarrier = */ false); -} - -void -UnboxedArrayObject::initElementNoTypeChange(size_t index, const Value& v) -{ - MOZ_ASSERT(index < initializedLength()); - uint8_t* p = elements() + index * elementSize(); - if (UnboxedTypeNeedsPreBarrier(elementType())) - *reinterpret_cast<void**>(p) = nullptr; - SetUnboxedValueNoTypeChange(this, p, elementType(), v, /* preBarrier = */ false); -} - -Value -UnboxedArrayObject::getElement(size_t index) -{ - MOZ_ASSERT(index < initializedLength()); - uint8_t* p = elements() + index * elementSize(); - return GetUnboxedValue(p, elementType(), /* maybeUninitialized = */ false); -} - -/* static */ void -UnboxedArrayObject::trace(JSTracer* trc, JSObject* obj) -{ - JSValueType type = obj->as<UnboxedArrayObject>().elementType(); - if (!UnboxedTypeNeedsPreBarrier(type)) - return; - - MOZ_ASSERT(obj->as<UnboxedArrayObject>().elementSize() == sizeof(uintptr_t)); - size_t initlen = obj->as<UnboxedArrayObject>().initializedLength(); - void** elements = reinterpret_cast<void**>(obj->as<UnboxedArrayObject>().elements()); - - switch (type) { - case JSVAL_TYPE_OBJECT: - for (size_t i = 0; i < initlen; i++) { - GCPtrObject* heap = reinterpret_cast<GCPtrObject*>(elements + i); - TraceNullableEdge(trc, heap, "unboxed_object"); - } - break; - - case JSVAL_TYPE_STRING: - for (size_t i = 0; i < initlen; i++) { - GCPtrString* heap = reinterpret_cast<GCPtrString*>(elements + i); - TraceEdge(trc, heap, "unboxed_string"); - } - break; - - default: - MOZ_CRASH(); - } -} - -/* static */ void -UnboxedArrayObject::objectMoved(JSObject* obj, const JSObject* old) -{ - UnboxedArrayObject& dst = obj->as<UnboxedArrayObject>(); - const UnboxedArrayObject& src = old->as<UnboxedArrayObject>(); - - // Fix up possible inline data pointer. - if (src.hasInlineElements()) - dst.setInlineElements(); -} - -/* static */ void -UnboxedArrayObject::finalize(FreeOp* fop, JSObject* obj) -{ - MOZ_ASSERT(!IsInsideNursery(obj)); - if (!obj->as<UnboxedArrayObject>().hasInlineElements()) - js_free(obj->as<UnboxedArrayObject>().elements()); -} - -/* static */ size_t -UnboxedArrayObject::objectMovedDuringMinorGC(JSTracer* trc, JSObject* dst, JSObject* src, - gc::AllocKind allocKind) -{ - UnboxedArrayObject* ndst = &dst->as<UnboxedArrayObject>(); - UnboxedArrayObject* nsrc = &src->as<UnboxedArrayObject>(); - MOZ_ASSERT(ndst->elements() == nsrc->elements()); - - Nursery& nursery = trc->runtime()->gc.nursery; - - if (!nursery.isInside(nsrc->elements())) { - nursery.removeMallocedBuffer(nsrc->elements()); - return 0; - } - - // Determine if we can use inline data for the target array. If this is - // possible, the nursery will have picked an allocation size that is large - // enough. - size_t nbytes = nsrc->capacity() * nsrc->elementSize(); - if (offsetOfInlineElements() + nbytes <= GetGCKindBytes(allocKind)) { - ndst->setInlineElements(); - } else { - MOZ_ASSERT(allocKind == gc::AllocKind::OBJECT0); - - AutoEnterOOMUnsafeRegion oomUnsafe; - uint8_t* data = nsrc->zone()->pod_malloc<uint8_t>(nbytes); - if (!data) - oomUnsafe.crash("Failed to allocate unboxed array elements while tenuring."); - ndst->elements_ = data; - } - - PodCopy(ndst->elements(), nsrc->elements(), nsrc->initializedLength() * nsrc->elementSize()); - - // Set a forwarding pointer for the element buffers in case they were - // preserved on the stack by Ion. - bool direct = nsrc->capacity() * nsrc->elementSize() >= sizeof(uintptr_t); - nursery.maybeSetForwardingPointer(trc, nsrc->elements(), ndst->elements(), direct); - - return ndst->hasInlineElements() ? 0 : nbytes; -} - -// Possible capacities for unboxed arrays. Some of these capacities might seem -// a little weird, but were chosen to allow the inline data of objects of each -// size to be fully utilized for arrays of the various types on both 32 bit and -// 64 bit platforms. -// -// To find the possible inline capacities, the following script was used: -// -// var fixedSlotCapacities = [0, 2, 4, 8, 12, 16]; -// var dataSizes = [1, 4, 8]; -// var header32 = 4 * 2 + 4 * 2; -// var header64 = 8 * 2 + 4 * 2; -// -// for (var i = 0; i < fixedSlotCapacities.length; i++) { -// var nfixed = fixedSlotCapacities[i]; -// var size32 = 4 * 4 + 8 * nfixed - header32; -// var size64 = 8 * 4 + 8 * nfixed - header64; -// for (var j = 0; j < dataSizes.length; j++) { -// print(size32 / dataSizes[j]); -// print(size64 / dataSizes[j]); -// } -// } -// -/* static */ const uint32_t -UnboxedArrayObject::CapacityArray[] = { - UINT32_MAX, // For CapacityMatchesLengthIndex. - 0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 13, 16, 17, 18, 24, 26, 32, 34, 40, 64, 72, 96, 104, 128, 136, - 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, - 1048576, 2097152, 3145728, 4194304, 5242880, 6291456, 7340032, 8388608, 9437184, 11534336, - 13631488, 15728640, 17825792, 20971520, 24117248, 27262976, 31457280, 35651584, 40894464, - 46137344, 52428800, 59768832, MaximumCapacity -}; - -static const uint32_t -Pow2CapacityIndexes[] = { - 2, // 1 - 3, // 2 - 5, // 4 - 8, // 8 - 13, // 16 - 18, // 32 - 21, // 64 - 25, // 128 - 27, // 256 - 28, // 512 - 29, // 1024 - 30, // 2048 - 31, // 4096 - 32, // 8192 - 33, // 16384 - 34, // 32768 - 35, // 65536 - 36, // 131072 - 37, // 262144 - 38, // 524288 - 39 // 1048576 -}; - -static const uint32_t MebiCapacityIndex = 39; - -/* static */ uint32_t -UnboxedArrayObject::chooseCapacityIndex(uint32_t capacity, uint32_t length) -{ - // Note: the structure and behavior of this method follow along with - // NativeObject::goodAllocated. Changes to the allocation strategy in one - // should generally be matched by the other. - - // Make sure we have enough space to store all possible values for the capacity index. - // This ought to be a static_assert, but MSVC doesn't like that. - MOZ_ASSERT(mozilla::ArrayLength(CapacityArray) - 1 <= (CapacityMask >> CapacityShift)); - - // The caller should have ensured the capacity is possible for an unboxed array. - MOZ_ASSERT(capacity <= MaximumCapacity); - - static const uint32_t Mebi = 1024 * 1024; - - if (capacity <= Mebi) { - capacity = mozilla::RoundUpPow2(capacity); - - // When the required capacity is close to the array length, then round - // up to the array length itself, as for NativeObject. - if (length >= capacity && capacity > (length / 3) * 2) - return CapacityMatchesLengthIndex; - - if (capacity < MinimumDynamicCapacity) - capacity = MinimumDynamicCapacity; - - uint32_t bit = mozilla::FloorLog2Size(capacity); - MOZ_ASSERT(capacity == uint32_t(1 << bit)); - MOZ_ASSERT(bit <= 20); - MOZ_ASSERT(mozilla::ArrayLength(Pow2CapacityIndexes) == 21); - - uint32_t index = Pow2CapacityIndexes[bit]; - MOZ_ASSERT(CapacityArray[index] == capacity); - - return index; - } - - MOZ_ASSERT(CapacityArray[MebiCapacityIndex] == Mebi); - - for (uint32_t i = MebiCapacityIndex + 1;; i++) { - if (CapacityArray[i] >= capacity) - return i; - } - - MOZ_CRASH("Invalid capacity"); -} - -/* static */ uint32_t -UnboxedArrayObject::exactCapacityIndex(uint32_t capacity) -{ - for (size_t i = CapacityMatchesLengthIndex + 1; i < ArrayLength(CapacityArray); i++) { - if (CapacityArray[i] == capacity) - return i; - } - MOZ_CRASH(); -} - -bool -UnboxedArrayObject::growElements(ExclusiveContext* cx, size_t cap) -{ - // The caller should have checked if this capacity is possible for an - // unboxed array, so the only way this call can fail is from OOM. - MOZ_ASSERT(cap <= MaximumCapacity); - - uint32_t oldCapacity = capacity(); - uint32_t newCapacityIndex = chooseCapacityIndex(cap, length()); - uint32_t newCapacity = computeCapacity(newCapacityIndex, length()); - - MOZ_ASSERT(oldCapacity < cap); - MOZ_ASSERT(cap <= newCapacity); - - // The allocation size computation below cannot have integer overflows. - JS_STATIC_ASSERT(MaximumCapacity < UINT32_MAX / sizeof(double)); - - uint8_t* newElements; - if (hasInlineElements()) { - newElements = AllocateObjectBuffer<uint8_t>(cx, this, newCapacity * elementSize()); - if (!newElements) - return false; - js_memcpy(newElements, elements(), initializedLength() * elementSize()); - } else { - newElements = ReallocateObjectBuffer<uint8_t>(cx, this, elements(), - oldCapacity * elementSize(), - newCapacity * elementSize()); - if (!newElements) - return false; - } - - elements_ = newElements; - setCapacityIndex(newCapacityIndex); - - return true; -} - -void -UnboxedArrayObject::shrinkElements(ExclusiveContext* cx, size_t cap) -{ - if (hasInlineElements()) - return; - - uint32_t oldCapacity = capacity(); - uint32_t newCapacityIndex = chooseCapacityIndex(cap, 0); - uint32_t newCapacity = computeCapacity(newCapacityIndex, 0); - - MOZ_ASSERT(cap < oldCapacity); - MOZ_ASSERT(cap <= newCapacity); - - if (newCapacity >= oldCapacity) - return; - - uint8_t* newElements = ReallocateObjectBuffer<uint8_t>(cx, this, elements(), - oldCapacity * elementSize(), - newCapacity * elementSize()); - if (!newElements) - return; - - elements_ = newElements; - setCapacityIndex(newCapacityIndex); -} - -bool -UnboxedArrayObject::containsProperty(ExclusiveContext* cx, jsid id) -{ - if (JSID_IS_INT(id) && uint32_t(JSID_TO_INT(id)) < initializedLength()) - return true; - if (JSID_IS_ATOM(id) && JSID_TO_ATOM(id) == cx->names().length) - return true; - return false; -} - -/* static */ bool -UnboxedArrayObject::obj_lookupProperty(JSContext* cx, HandleObject obj, - HandleId id, MutableHandleObject objp, - MutableHandleShape propp) -{ - if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) { - MarkNonNativePropertyFound<CanGC>(propp); - objp.set(obj); - return true; - } - - RootedObject proto(cx, obj->staticPrototype()); - if (!proto) { - objp.set(nullptr); - propp.set(nullptr); - return true; - } - - return LookupProperty(cx, proto, id, objp, propp); -} - -/* static */ bool -UnboxedArrayObject::obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id, - Handle<PropertyDescriptor> desc, - ObjectOpResult& result) -{ - if (JSID_IS_INT(id) && !desc.getter() && !desc.setter() && desc.attributes() == JSPROP_ENUMERATE) { - UnboxedArrayObject* nobj = &obj->as<UnboxedArrayObject>(); - - uint32_t index = JSID_TO_INT(id); - if (index < nobj->initializedLength()) { - if (nobj->setElement(cx, index, desc.value())) - return result.succeed(); - } else if (index == nobj->initializedLength() && index < MaximumCapacity) { - if (nobj->initializedLength() == nobj->capacity()) { - if (!nobj->growElements(cx, index + 1)) - return false; - } - nobj->setInitializedLength(index + 1); - if (nobj->initElement(cx, index, desc.value())) { - if (nobj->length() <= index) - nobj->setLengthInt32(index + 1); - return result.succeed(); - } - nobj->setInitializedLengthNoBarrier(index); - } - } - - if (!convertToNative(cx, obj)) - return false; - - return DefineProperty(cx, obj, id, desc, result); -} - -/* static */ bool -UnboxedArrayObject::obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp) -{ - if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) { - *foundp = true; - return true; - } - - RootedObject proto(cx, obj->staticPrototype()); - if (!proto) { - *foundp = false; - return true; - } - - return HasProperty(cx, proto, id, foundp); -} - -/* static */ bool -UnboxedArrayObject::obj_getProperty(JSContext* cx, HandleObject obj, HandleValue receiver, - HandleId id, MutableHandleValue vp) -{ - if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) { - if (JSID_IS_INT(id)) - vp.set(obj->as<UnboxedArrayObject>().getElement(JSID_TO_INT(id))); - else - vp.set(Int32Value(obj->as<UnboxedArrayObject>().length())); - return true; - } - - RootedObject proto(cx, obj->staticPrototype()); - if (!proto) { - vp.setUndefined(); - return true; - } - - return GetProperty(cx, proto, receiver, id, vp); -} - -/* static */ bool -UnboxedArrayObject::obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v, - HandleValue receiver, ObjectOpResult& result) -{ - if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) { - if (receiver.isObject() && obj == &receiver.toObject()) { - if (JSID_IS_INT(id)) { - if (obj->as<UnboxedArrayObject>().setElement(cx, JSID_TO_INT(id), v)) - return result.succeed(); - } else { - uint32_t len; - if (!CanonicalizeArrayLengthValue(cx, v, &len)) - return false; - UnboxedArrayObject* nobj = &obj->as<UnboxedArrayObject>(); - if (len < nobj->initializedLength()) { - nobj->setInitializedLength(len); - nobj->shrinkElements(cx, len); - } - nobj->setLength(cx, len); - return result.succeed(); - } - - if (!convertToNative(cx, obj)) - return false; - return SetProperty(cx, obj, id, v, receiver, result); - } - - return SetPropertyByDefining(cx, id, v, receiver, result); - } - - return SetPropertyOnProto(cx, obj, id, v, receiver, result); -} - -/* static */ bool -UnboxedArrayObject::obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id, - MutableHandle<PropertyDescriptor> desc) -{ - if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) { - if (JSID_IS_INT(id)) { - desc.value().set(obj->as<UnboxedArrayObject>().getElement(JSID_TO_INT(id))); - desc.setAttributes(JSPROP_ENUMERATE); - } else { - desc.value().set(Int32Value(obj->as<UnboxedArrayObject>().length())); - desc.setAttributes(JSPROP_PERMANENT); - } - desc.object().set(obj); - return true; - } - - desc.object().set(nullptr); - return true; -} - -/* static */ bool -UnboxedArrayObject::obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id, - ObjectOpResult& result) -{ - if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) { - size_t initlen = obj->as<UnboxedArrayObject>().initializedLength(); - if (JSID_IS_INT(id) && JSID_TO_INT(id) == int32_t(initlen - 1)) { - obj->as<UnboxedArrayObject>().setInitializedLength(initlen - 1); - obj->as<UnboxedArrayObject>().shrinkElements(cx, initlen - 1); - return result.succeed(); - } - } - - if (!convertToNative(cx, obj)) - return false; - return DeleteProperty(cx, obj, id, result); -} - -/* static */ bool -UnboxedArrayObject::obj_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; -} - -static const ClassOps UnboxedArrayObjectClassOps = { - nullptr, /* addProperty */ - nullptr, /* delProperty */ - nullptr, /* getProperty */ - nullptr, /* setProperty */ - nullptr, /* enumerate */ - nullptr, /* resolve */ - nullptr, /* mayResolve */ - UnboxedArrayObject::finalize, - nullptr, /* call */ - nullptr, /* hasInstance */ - nullptr, /* construct */ - UnboxedArrayObject::trace, -}; - -static const ClassExtension UnboxedArrayObjectClassExtension = { - nullptr, /* weakmapKeyDelegateOp */ - UnboxedArrayObject::objectMoved -}; - -static const ObjectOps UnboxedArrayObjectObjectOps = { - UnboxedArrayObject::obj_lookupProperty, - UnboxedArrayObject::obj_defineProperty, - UnboxedArrayObject::obj_hasProperty, - UnboxedArrayObject::obj_getProperty, - UnboxedArrayObject::obj_setProperty, - UnboxedArrayObject::obj_getOwnPropertyDescriptor, - UnboxedArrayObject::obj_deleteProperty, - UnboxedArrayObject::obj_watch, - nullptr, /* No unwatch needed, as watch() converts the object to native */ - nullptr, /* getElements */ - UnboxedArrayObject::obj_enumerate, - nullptr /* funToString */ -}; - -const Class UnboxedArrayObject::class_ = { - "Array", - Class::NON_NATIVE | - JSCLASS_SKIP_NURSERY_FINALIZE | - JSCLASS_BACKGROUND_FINALIZE, - &UnboxedArrayObjectClassOps, - JS_NULL_CLASS_SPEC, - &UnboxedArrayObjectClassExtension, - &UnboxedArrayObjectObjectOps -}; - -///////////////////////////////////////////////////////////////////// -// API -///////////////////////////////////////////////////////////////////// - -static inline Value -NextValue(Handle<GCVector<Value>> values, size_t* valueCursor) -{ - return values[(*valueCursor)++]; -} - -void -UnboxedArrayObject::fillAfterConvert(ExclusiveContext* cx, - Handle<GCVector<Value>> values, size_t* valueCursor) -{ - MOZ_ASSERT(CapacityArray[1] == 0); - setCapacityIndex(1); - setInitializedLengthNoBarrier(0); - setInlineElements(); - - setLength(cx, NextValue(values, valueCursor).toInt32()); - - int32_t initlen = NextValue(values, valueCursor).toInt32(); - if (!initlen) - return; - - AutoEnterOOMUnsafeRegion oomUnsafe; - if (!growElements(cx, initlen)) - oomUnsafe.crash("UnboxedArrayObject::fillAfterConvert"); - - setInitializedLength(initlen); - - for (size_t i = 0; i < size_t(initlen); i++) - JS_ALWAYS_TRUE(initElement(cx, i, NextValue(values, valueCursor))); -} - -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))); -} - -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); -} |