Mypal/js/src/vm/UnboxedObject.cpp
2020-04-03 20:51:38 +03:00

1733 lines
60 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "vm/UnboxedObject-inl.h"
#include "jit/BaselineIC.h"
#include "jit/ExecutableAllocator.h"
#include "jit/JitCommon.h"
#include "jit/Linker.h"
#include "jsobjinlines.h"
#include "gc/Nursery-inl.h"
#include "jit/MacroAssembler-inl.h"
#include "vm/Shape-inl.h"
using mozilla::ArrayLength;
using mozilla::DebugOnly;
using mozilla::PodCopy;
using namespace js;
/////////////////////////////////////////////////////////////////////
// UnboxedLayout
/////////////////////////////////////////////////////////////////////
void
UnboxedLayout::trace(JSTracer* trc)
{
for (size_t i = 0; i < properties_.length(); i++)
TraceManuallyBarrieredEdge(trc, &properties_[i].name, "unboxed_layout_name");
if (newScript())
newScript()->trace(trc);
TraceNullableEdge(trc, &nativeGroup_, "unboxed_layout_nativeGroup");
TraceNullableEdge(trc, &nativeShape_, "unboxed_layout_nativeShape");
TraceNullableEdge(trc, &allocationScript_, "unboxed_layout_allocationScript");
TraceNullableEdge(trc, &replacementGroup_, "unboxed_layout_replacementGroup");
TraceNullableEdge(trc, &constructorCode_, "unboxed_layout_constructorCode");
}
size_t
UnboxedLayout::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
{
return mallocSizeOf(this)
+ properties_.sizeOfExcludingThis(mallocSizeOf)
+ (newScript() ? newScript()->sizeOfIncludingThis(mallocSizeOf) : 0)
+ mallocSizeOf(traceList());
}
void
UnboxedLayout::setNewScript(TypeNewScript* newScript, bool writeBarrier /* = true */)
{
if (newScript_ && writeBarrier)
TypeNewScript::writeBarrierPre(newScript_);
newScript_ = newScript;
}
// Constructor code returns a 0x1 value to indicate the constructor code should
// be cleared.
static const uintptr_t CLEAR_CONSTRUCTOR_CODE_TOKEN = 0x1;
/* static */ bool
UnboxedLayout::makeConstructorCode(JSContext* cx, HandleObjectGroup group)
{
gc::AutoSuppressGC suppress(cx);
using namespace jit;
if (!cx->compartment()->ensureJitCompartmentExists(cx))
return false;
UnboxedLayout& layout = group->unboxedLayout();
MOZ_ASSERT(!layout.constructorCode());
UnboxedPlainObject* templateObject = UnboxedPlainObject::create(cx, group, TenuredObject);
if (!templateObject)
return false;
JitContext jitContext(cx, nullptr);
MacroAssembler masm;
Register propertiesReg, newKindReg;
#ifdef JS_CODEGEN_X86
propertiesReg = eax;
newKindReg = ecx;
masm.loadPtr(Address(masm.getStackPointer(), sizeof(void*)), propertiesReg);
masm.loadPtr(Address(masm.getStackPointer(), 2 * sizeof(void*)), newKindReg);
#else
propertiesReg = IntArgReg0;
newKindReg = IntArgReg1;
#endif
#ifdef JS_CODEGEN_ARM64
// ARM64 communicates stack address via sp, but uses a pseudo-sp for addressing.
masm.initStackPtr();
#endif
MOZ_ASSERT(propertiesReg.volatile_());
MOZ_ASSERT(newKindReg.volatile_());
AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All());
regs.take(propertiesReg);
regs.take(newKindReg);
Register object = regs.takeAny(), scratch1 = regs.takeAny(), scratch2 = regs.takeAny();
LiveGeneralRegisterSet savedNonVolatileRegisters = SavedNonVolatileRegisters(regs);
masm.PushRegsInMask(savedNonVolatileRegisters);
// The scratch double register might be used by MacroAssembler methods.
if (ScratchDoubleReg.volatile_())
masm.push(ScratchDoubleReg);
Label failure, tenuredObject, allocated;
masm.branch32(Assembler::NotEqual, newKindReg, Imm32(GenericObject), &tenuredObject);
masm.branchTest32(Assembler::NonZero, AbsoluteAddress(group->addressOfFlags()),
Imm32(OBJECT_FLAG_PRE_TENURE), &tenuredObject);
// Allocate an object in the nursery
masm.createGCObject(object, scratch1, templateObject, gc::DefaultHeap, &failure,
/* initFixedSlots = */ false);
masm.jump(&allocated);
masm.bind(&tenuredObject);
// Allocate an object in the tenured heap.
masm.createGCObject(object, scratch1, templateObject, gc::TenuredHeap, &failure,
/* initFixedSlots = */ false);
// If any of the properties being stored are in the nursery, add a store
// buffer entry for the new object.
Label postBarrier;
for (size_t i = 0; i < layout.properties().length(); i++) {
const UnboxedLayout::Property& property = layout.properties()[i];
if (property.type == JSVAL_TYPE_OBJECT) {
Address valueAddress(propertiesReg, i * sizeof(IdValuePair) + offsetof(IdValuePair, value));
Label notObject;
masm.branchTestObject(Assembler::NotEqual, valueAddress, &notObject);
Register valueObject = masm.extractObject(valueAddress, scratch1);
masm.branchPtrInNurseryChunk(Assembler::Equal, valueObject, scratch2, &postBarrier);
masm.bind(&notObject);
}
}
masm.jump(&allocated);
masm.bind(&postBarrier);
LiveGeneralRegisterSet liveVolatileRegisters;
liveVolatileRegisters.add(propertiesReg);
if (object.volatile_())
liveVolatileRegisters.add(object);
masm.PushRegsInMask(liveVolatileRegisters);
masm.mov(ImmPtr(cx->runtime()), scratch1);
masm.setupUnalignedABICall(scratch2);
masm.passABIArg(scratch1);
masm.passABIArg(object);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, PostWriteBarrier));
masm.PopRegsInMask(liveVolatileRegisters);
masm.bind(&allocated);
ValueOperand valueOperand;
#ifdef JS_NUNBOX32
valueOperand = ValueOperand(scratch1, scratch2);
#else
valueOperand = ValueOperand(scratch1);
#endif
Label failureStoreOther, failureStoreObject;
for (size_t i = 0; i < layout.properties().length(); i++) {
const UnboxedLayout::Property& property = layout.properties()[i];
Address valueAddress(propertiesReg, i * sizeof(IdValuePair) + offsetof(IdValuePair, value));
Address targetAddress(object, UnboxedPlainObject::offsetOfData() + property.offset);
masm.loadValue(valueAddress, valueOperand);
if (property.type == JSVAL_TYPE_OBJECT) {
HeapTypeSet* types = group->maybeGetProperty(IdToTypeId(NameToId(property.name)));
Label notObject;
masm.branchTestObject(Assembler::NotEqual, valueOperand,
types->mightBeMIRType(MIRType::Null) ? &notObject : &failureStoreObject);
Register payloadReg = masm.extractObject(valueOperand, scratch1);
if (!types->hasType(TypeSet::AnyObjectType())) {
Register scratch = (payloadReg == scratch1) ? scratch2 : scratch1;
masm.guardObjectType(payloadReg, types, scratch, &failureStoreObject);
}
masm.storeUnboxedProperty(targetAddress, JSVAL_TYPE_OBJECT,
TypedOrValueRegister(MIRType::Object,
AnyRegister(payloadReg)), nullptr);
if (notObject.used()) {
Label done;
masm.jump(&done);
masm.bind(&notObject);
masm.branchTestNull(Assembler::NotEqual, valueOperand, &failureStoreOther);
masm.storeUnboxedProperty(targetAddress, JSVAL_TYPE_OBJECT, NullValue(), nullptr);
masm.bind(&done);
}
} else {
masm.storeUnboxedProperty(targetAddress, property.type,
ConstantOrRegister(valueOperand), &failureStoreOther);
}
}
Label done;
masm.bind(&done);
if (object != ReturnReg)
masm.movePtr(object, ReturnReg);
// Restore non-volatile registers which were saved on entry.
if (ScratchDoubleReg.volatile_())
masm.pop(ScratchDoubleReg);
masm.PopRegsInMask(savedNonVolatileRegisters);
masm.abiret();
masm.bind(&failureStoreOther);
// There was a failure while storing a value which cannot be stored at all
// in the unboxed object. Initialize the object so it is safe for GC and
// return null.
masm.initUnboxedObjectContents(object, templateObject);
masm.bind(&failure);
masm.movePtr(ImmWord(0), object);
masm.jump(&done);
masm.bind(&failureStoreObject);
// There was a failure while storing a value to an object slot of the
// unboxed object. If the value is storable, the failure occurred due to
// incomplete type information in the object, so return a token to trigger
// regeneration of the jitcode after a new object is created in the VM.
{
Label isObject;
masm.branchTestObject(Assembler::Equal, valueOperand, &isObject);
masm.branchTestNull(Assembler::NotEqual, valueOperand, &failureStoreOther);
masm.bind(&isObject);
}
// Initialize the object so it is safe for GC.
masm.initUnboxedObjectContents(object, templateObject);
masm.movePtr(ImmWord(CLEAR_CONSTRUCTOR_CODE_TOKEN), object);
masm.jump(&done);
Linker linker(masm);
AutoFlushICache afc("UnboxedObject");
JitCode* code = linker.newCode<NoGC>(cx, OTHER_CODE);
if (!code)
return false;
layout.setConstructorCode(code);
return true;
}
void
UnboxedLayout::detachFromCompartment()
{
if (isInList())
remove();
}
/////////////////////////////////////////////////////////////////////
// UnboxedPlainObject
/////////////////////////////////////////////////////////////////////
bool
UnboxedPlainObject::setValue(ExclusiveContext* cx, const UnboxedLayout::Property& property,
const Value& v)
{
uint8_t* p = &data_[property.offset];
return SetUnboxedValue(cx, this, NameToId(property.name), p, property.type, v,
/* preBarrier = */ true);
}
Value
UnboxedPlainObject::getValue(const UnboxedLayout::Property& property,
bool maybeUninitialized /* = false */)
{
uint8_t* p = &data_[property.offset];
return GetUnboxedValue(p, property.type, maybeUninitialized);
}
void
UnboxedPlainObject::trace(JSTracer* trc, JSObject* obj)
{
if (obj->as<UnboxedPlainObject>().expando_) {
TraceManuallyBarrieredEdge(trc,
reinterpret_cast<NativeObject**>(&obj->as<UnboxedPlainObject>().expando_),
"unboxed_expando");
}
const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layoutDontCheckGeneration();
const int32_t* list = layout.traceList();
if (!list)
return;
uint8_t* data = obj->as<UnboxedPlainObject>().data();
while (*list != -1) {
GCPtrString* heap = reinterpret_cast<GCPtrString*>(data + *list);
TraceEdge(trc, heap, "unboxed_string");
list++;
}
list++;
while (*list != -1) {
GCPtrObject* heap = reinterpret_cast<GCPtrObject*>(data + *list);
TraceNullableEdge(trc, heap, "unboxed_object");
list++;
}
// Unboxed objects don't have Values to trace.
MOZ_ASSERT(*(list + 1) == -1);
}
/* static */ UnboxedExpandoObject*
UnboxedPlainObject::ensureExpando(JSContext* cx, Handle<UnboxedPlainObject*> obj)
{
if (obj->expando_)
return obj->expando_;
UnboxedExpandoObject* expando =
NewObjectWithGivenProto<UnboxedExpandoObject>(cx, nullptr, gc::AllocKind::OBJECT4);
if (!expando)
return nullptr;
// Don't track property types for expando objects. This allows Baseline
// and Ion AddSlot ICs to guard on the unboxed group without guarding on
// the expando group.
MarkObjectGroupUnknownProperties(cx, expando->group());
// If the expando is tenured then the original object must also be tenured.
// Otherwise barriers triggered on the original object for writes to the
// expando (as can happen in the JIT) won't see the tenured->nursery edge.
// See WholeCellEdges::mark.
MOZ_ASSERT_IF(!IsInsideNursery(expando), !IsInsideNursery(obj));
// As with setValue(), we need to manually trigger post barriers on the
// whole object. If we treat the field as a GCPtrObject and later
// convert the object to its native representation, we will end up with a
// corrupted store buffer entry.
if (IsInsideNursery(expando) && !IsInsideNursery(obj))
cx->runtime()->gc.storeBuffer.putWholeCell(obj);
obj->expando_ = expando;
return expando;
}
bool
UnboxedPlainObject::containsUnboxedOrExpandoProperty(ExclusiveContext* cx, jsid id) const
{
if (layout().lookup(id))
return true;
if (maybeExpando() && maybeExpando()->containsShapeOrElement(cx, id))
return true;
return false;
}
static bool
PropagatePropertyTypes(JSContext* cx, jsid id, ObjectGroup* oldGroup, ObjectGroup* newGroup)
{
HeapTypeSet* typeProperty = oldGroup->maybeGetProperty(id);
TypeSet::TypeList types;
if (!typeProperty->enumerateTypes(&types)) {
ReportOutOfMemory(cx);
return false;
}
for (size_t j = 0; j < types.length(); j++)
AddTypePropertyId(cx, newGroup, nullptr, id, types[j]);
return true;
}
static PlainObject*
MakeReplacementTemplateObject(JSContext* cx, HandleObjectGroup group, const UnboxedLayout &layout)
{
PlainObject* obj = NewObjectWithGroup<PlainObject>(cx, group, layout.getAllocKind(),
TenuredObject);
if (!obj)
return nullptr;
for (size_t i = 0; i < layout.properties().length(); i++) {
const UnboxedLayout::Property& property = layout.properties()[i];
if (!obj->addDataProperty(cx, NameToId(property.name), i, JSPROP_ENUMERATE))
return nullptr;
MOZ_ASSERT(obj->slotSpan() == i + 1);
MOZ_ASSERT(!obj->inDictionaryMode());
}
return obj;
}
/* static */ bool
UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group)
{
AutoEnterAnalysis enter(cx);
UnboxedLayout& layout = group->unboxedLayout();
Rooted<TaggedProto> proto(cx, group->proto());
MOZ_ASSERT(!layout.nativeGroup());
RootedObjectGroup replacementGroup(cx);
const Class* clasp = layout.isArray() ? &ArrayObject::class_ : &PlainObject::class_;
// Immediately clear any new script on the group. This is done by replacing
// the existing new script with one for a replacement default new group.
// This is done so that the size of the replacment group's objects is the
// same as that for the unboxed group, so that we do not see polymorphic
// slot accesses later on for sites that see converted objects from this
// group and objects that were allocated using the replacement new group.
if (layout.newScript()) {
MOZ_ASSERT(!layout.isArray());
replacementGroup = ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto);
if (!replacementGroup)
return false;
PlainObject* templateObject = MakeReplacementTemplateObject(cx, replacementGroup, layout);
if (!templateObject)
return false;
TypeNewScript* replacementNewScript =
TypeNewScript::makeNativeVersion(cx, layout.newScript(), templateObject);
if (!replacementNewScript)
return false;
replacementGroup->setNewScript(replacementNewScript);
gc::TraceTypeNewScript(replacementGroup);
group->clearNewScript(cx, replacementGroup);
}
// Similarly, if this group is keyed to an allocation site, replace its
// entry with a new group that has no unboxed layout.
if (layout.allocationScript()) {
RootedScript script(cx, layout.allocationScript());
jsbytecode* pc = layout.allocationPc();
replacementGroup = ObjectGroupCompartment::makeGroup(cx, clasp, proto);
if (!replacementGroup)
return false;
PlainObject* templateObject = &script->getObject(pc)->as<PlainObject>();
replacementGroup->addDefiniteProperties(cx, templateObject->lastProperty());
JSProtoKey key = layout.isArray() ? JSProto_Array : JSProto_Object;
cx->compartment()->objectGroups.replaceAllocationSiteGroup(script, pc, key,
replacementGroup);
// Clear any baseline information at this opcode which might use the old group.
if (script->hasBaselineScript()) {
jit::ICEntry& entry = script->baselineScript()->icEntryFromPCOffset(script->pcToOffset(pc));
jit::ICFallbackStub* fallback = entry.fallbackStub();
for (jit::ICStubIterator iter = fallback->beginChain(); !iter.atEnd(); iter++)
iter.unlink(cx);
if (fallback->isNewObject_Fallback())
fallback->toNewObject_Fallback()->setTemplateObject(nullptr);
else if (fallback->isNewArray_Fallback())
fallback->toNewArray_Fallback()->setTemplateGroup(replacementGroup);
}
}
size_t nfixed = layout.isArray() ? 0 : gc::GetGCKindSlots(layout.getAllocKind());
if (layout.isArray()) {
// The length shape to use for arrays is cached via a modified initial
// shape for array objects. Create an array now to make sure this entry
// is instantiated.
if (!NewDenseEmptyArray(cx))
return false;
}
RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, proto, nfixed, 0));
if (!shape)
return false;
MOZ_ASSERT_IF(layout.isArray(), !shape->isEmptyShape() && shape->slotSpan() == 0);
// Add shapes for each property, if this is for a plain object.
for (size_t i = 0; i < layout.properties().length(); i++) {
const UnboxedLayout::Property& property = layout.properties()[i];
Rooted<StackShape> child(cx, StackShape(shape->base()->unowned(), NameToId(property.name),
i, JSPROP_ENUMERATE, 0));
shape = cx->zone()->propertyTree.getChild(cx, shape, child);
if (!shape)
return false;
}
ObjectGroup* nativeGroup =
ObjectGroupCompartment::makeGroup(cx, clasp, proto,
group->flags() & OBJECT_FLAG_DYNAMIC_MASK);
if (!nativeGroup)
return false;
// No sense propagating if we don't know what we started with.
if (!group->unknownProperties()) {
// Propagate all property types from the old group to the new group.
if (layout.isArray()) {
if (!PropagatePropertyTypes(cx, JSID_VOID, group, nativeGroup))
return false;
} else {
for (size_t i = 0; i < layout.properties().length(); i++) {
const UnboxedLayout::Property& property = layout.properties()[i];
jsid id = NameToId(property.name);
if (!PropagatePropertyTypes(cx, id, group, nativeGroup))
return false;
// If we are OOM we may not be able to propagate properties.
if (nativeGroup->unknownProperties())
break;
HeapTypeSet* nativeProperty = nativeGroup->maybeGetProperty(id);
if (nativeProperty && nativeProperty->canSetDefinite(i))
nativeProperty->setDefinite(i);
}
}
} else {
// If we skip, though, the new group had better agree.
MOZ_ASSERT(nativeGroup->unknownProperties());
}
layout.nativeGroup_ = nativeGroup;
layout.nativeShape_ = shape;
layout.replacementGroup_ = replacementGroup;
nativeGroup->setOriginalUnboxedGroup(group);
group->markStateChange(cx);
return true;
}
/* static */ bool
UnboxedPlainObject::convertToNative(JSContext* cx, JSObject* obj)
{
const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layout();
UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando();
if (!layout.nativeGroup()) {
if (!UnboxedLayout::makeNativeGroup(cx, obj->group()))
return false;
// makeNativeGroup can reentrantly invoke this method.
if (obj->is<PlainObject>())
return true;
}
AutoValueVector values(cx);
for (size_t i = 0; i < layout.properties().length(); i++) {
// We might be reading properties off the object which have not been
// initialized yet. Make sure any double values we read here are
// canonicalized.
if (!values.append(obj->as<UnboxedPlainObject>().getValue(layout.properties()[i], true)))
return false;
}
// We are eliminating the expando edge with the conversion, so trigger a
// pre barrier.
JSObject::writeBarrierPre(expando);
// Additionally trigger a post barrier on the expando itself. Whole cell
// store buffer entries can be added on the original unboxed object for
// writes to the expando (see WholeCellEdges::trace), so after conversion
// we need to make sure the expando itself will still be traced.
if (expando && !IsInsideNursery(expando))
cx->runtime()->gc.storeBuffer.putWholeCell(expando);
obj->setGroup(layout.nativeGroup());
obj->as<PlainObject>().setLastPropertyMakeNative(cx, layout.nativeShape());
for (size_t i = 0; i < values.length(); i++)
obj->as<PlainObject>().initSlotUnchecked(i, values[i]);
if (expando) {
// Add properties from the expando object to the object, in order.
// Suppress GC here, so that callers don't need to worry about this
// method collecting. The stuff below can only fail due to OOM, in
// which case the object will not have been completely filled back in.
gc::AutoSuppressGC suppress(cx);
Vector<jsid> ids(cx);
for (Shape::Range<NoGC> r(expando->lastProperty()); !r.empty(); r.popFront()) {
if (!ids.append(r.front().propid()))
return false;
}
for (size_t i = 0; i < expando->getDenseInitializedLength(); i++) {
if (!expando->getDenseElement(i).isMagic(JS_ELEMENTS_HOLE)) {
if (!ids.append(INT_TO_JSID(i)))
return false;
}
}
::Reverse(ids.begin(), ids.end());
RootedPlainObject nobj(cx, &obj->as<PlainObject>());
Rooted<UnboxedExpandoObject*> nexpando(cx, expando);
RootedId id(cx);
Rooted<PropertyDescriptor> desc(cx);
for (size_t i = 0; i < ids.length(); i++) {
id = ids[i];
if (!GetOwnPropertyDescriptor(cx, nexpando, id, &desc))
return false;
ObjectOpResult result;
if (!DefineProperty(cx, nobj, id, desc, result))
return false;
MOZ_ASSERT(result.ok());
}
}
return true;
}
/* static */
UnboxedPlainObject*
UnboxedPlainObject::create(ExclusiveContext* cx, HandleObjectGroup group, NewObjectKind newKind)
{
AutoSetNewObjectMetadata metadata(cx);
MOZ_ASSERT(group->clasp() == &class_);
gc::AllocKind allocKind = group->unboxedLayout().getAllocKind();
UnboxedPlainObject* res =
NewObjectWithGroup<UnboxedPlainObject>(cx, group, allocKind, newKind);
if (!res)
return nullptr;
// Overwrite the dummy shape which was written to the object's expando field.
res->initExpando();
// Initialize reference fields of the object. All fields in the object will
// be overwritten shortly, but references need to be safe for the GC.
const int32_t* list = res->layout().traceList();
if (list) {
uint8_t* data = res->data();
while (*list != -1) {
GCPtrString* heap = reinterpret_cast<GCPtrString*>(data + *list);
heap->init(cx->names().empty);
list++;
}
list++;
while (*list != -1) {
GCPtrObject* heap = reinterpret_cast<GCPtrObject*>(data + *list);
heap->init(nullptr);
list++;
}
// Unboxed objects don't have Values to initialize.
MOZ_ASSERT(*(list + 1) == -1);
}
return res;
}
/* static */ JSObject*
UnboxedPlainObject::createWithProperties(ExclusiveContext* cx, HandleObjectGroup group,
NewObjectKind newKind, IdValuePair* properties)
{
MOZ_ASSERT(newKind == GenericObject || newKind == TenuredObject);
UnboxedLayout& layout = group->unboxedLayout();
if (layout.constructorCode()) {
MOZ_ASSERT(cx->isJSContext());
typedef JSObject* (*ConstructorCodeSignature)(IdValuePair*, NewObjectKind);
ConstructorCodeSignature function =
reinterpret_cast<ConstructorCodeSignature>(layout.constructorCode()->raw());
JSObject* obj;
{
JS::AutoSuppressGCAnalysis nogc;
obj = reinterpret_cast<JSObject*>(CALL_GENERATED_2(function, properties, newKind));
}
if (obj > reinterpret_cast<JSObject*>(CLEAR_CONSTRUCTOR_CODE_TOKEN))
return obj;
if (obj == reinterpret_cast<JSObject*>(CLEAR_CONSTRUCTOR_CODE_TOKEN))
layout.setConstructorCode(nullptr);
}
UnboxedPlainObject* obj = UnboxedPlainObject::create(cx, group, newKind);
if (!obj)
return nullptr;
for (size_t i = 0; i < layout.properties().length(); i++) {
if (!obj->setValue(cx, layout.properties()[i], properties[i].value))
return NewPlainObjectWithProperties(cx, properties, layout.properties().length(), newKind);
}
#ifndef JS_CODEGEN_NONE
if (cx->isJSContext() &&
!group->unknownProperties() &&
!layout.constructorCode() &&
cx->asJSContext()->runtime()->jitSupportsFloatingPoint &&
jit::CanLikelyAllocateMoreExecutableMemory())
{
if (!UnboxedLayout::makeConstructorCode(cx->asJSContext(), group))
return nullptr;
}
#endif
return obj;
}
/* static */ bool
UnboxedPlainObject::obj_lookupProperty(JSContext* cx, HandleObject obj,
HandleId id, MutableHandleObject objp,
MutableHandleShape propp)
{
if (obj->as<UnboxedPlainObject>().containsUnboxedOrExpandoProperty(cx, id)) {
MarkNonNativePropertyFound<CanGC>(propp);
objp.set(obj);
return true;
}
RootedObject proto(cx, obj->staticPrototype());
if (!proto) {
objp.set(nullptr);
propp.set(nullptr);
return true;
}
return LookupProperty(cx, proto, id, objp, propp);
}
/* static */ bool
UnboxedPlainObject::obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id,
Handle<PropertyDescriptor> desc,
ObjectOpResult& result)
{
const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layout();
if (const UnboxedLayout::Property* property = layout.lookup(id)) {
if (!desc.getter() && !desc.setter() && desc.attributes() == JSPROP_ENUMERATE) {
// This define is equivalent to setting an existing property.
if (obj->as<UnboxedPlainObject>().setValue(cx, *property, desc.value()))
return result.succeed();
}
// Trying to incompatibly redefine an existing property requires the
// object to be converted to a native object.
if (!convertToNative(cx, obj))
return false;
return DefineProperty(cx, obj, id, desc, result);
}
// Define the property on the expando object.
Rooted<UnboxedExpandoObject*> expando(cx, ensureExpando(cx, obj.as<UnboxedPlainObject>()));
if (!expando)
return false;
// Update property types on the unboxed object as well.
AddTypePropertyId(cx, obj, id, desc.value());
return DefineProperty(cx, expando, id, desc, result);
}
/* static */ bool
UnboxedPlainObject::obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp)
{
if (obj->as<UnboxedPlainObject>().containsUnboxedOrExpandoProperty(cx, id)) {
*foundp = true;
return true;
}
RootedObject proto(cx, obj->staticPrototype());
if (!proto) {
*foundp = false;
return true;
}
return HasProperty(cx, proto, id, foundp);
}
/* static */ bool
UnboxedPlainObject::obj_getProperty(JSContext* cx, HandleObject obj, HandleValue receiver,
HandleId id, MutableHandleValue vp)
{
const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layout();
if (const UnboxedLayout::Property* property = layout.lookup(id)) {
vp.set(obj->as<UnboxedPlainObject>().getValue(*property));
return true;
}
if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) {
if (expando->containsShapeOrElement(cx, id)) {
RootedObject nexpando(cx, expando);
return GetProperty(cx, nexpando, receiver, id, vp);
}
}
RootedObject proto(cx, obj->staticPrototype());
if (!proto) {
vp.setUndefined();
return true;
}
return GetProperty(cx, proto, receiver, id, vp);
}
/* static */ bool
UnboxedPlainObject::obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
HandleValue receiver, ObjectOpResult& result)
{
const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layout();
if (const UnboxedLayout::Property* property = layout.lookup(id)) {
if (receiver.isObject() && obj == &receiver.toObject()) {
if (obj->as<UnboxedPlainObject>().setValue(cx, *property, v))
return result.succeed();
if (!convertToNative(cx, obj))
return false;
return SetProperty(cx, obj, id, v, receiver, result);
}
return SetPropertyByDefining(cx, id, v, receiver, result);
}
if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) {
if (expando->containsShapeOrElement(cx, id)) {
// Update property types on the unboxed object as well.
AddTypePropertyId(cx, obj, id, v);
RootedObject nexpando(cx, expando);
return SetProperty(cx, nexpando, id, v, receiver, result);
}
}
return SetPropertyOnProto(cx, obj, id, v, receiver, result);
}
/* static */ bool
UnboxedPlainObject::obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id,
MutableHandle<PropertyDescriptor> desc)
{
const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layout();
if (const UnboxedLayout::Property* property = layout.lookup(id)) {
desc.value().set(obj->as<UnboxedPlainObject>().getValue(*property));
desc.setAttributes(JSPROP_ENUMERATE);
desc.object().set(obj);
return true;
}
if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) {
if (expando->containsShapeOrElement(cx, id)) {
RootedObject nexpando(cx, expando);
if (!GetOwnPropertyDescriptor(cx, nexpando, id, desc))
return false;
if (desc.object() == nexpando)
desc.object().set(obj);
return true;
}
}
desc.object().set(nullptr);
return true;
}
/* static */ bool
UnboxedPlainObject::obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id,
ObjectOpResult& result)
{
if (!convertToNative(cx, obj))
return false;
return DeleteProperty(cx, obj, id, result);
}
/* static */ bool
UnboxedPlainObject::obj_enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties,
bool enumerableOnly)
{
// Ignore expando properties here, they are special-cased by the property
// enumeration code.
const UnboxedLayout::PropertyVector& unboxed = obj->as<UnboxedPlainObject>().layout().properties();
for (size_t i = 0; i < unboxed.length(); i++) {
if (!properties.append(NameToId(unboxed[i].name)))
return false;
}
return true;
}
const Class UnboxedExpandoObject::class_ = {
"UnboxedExpandoObject",
0
};
static const ClassOps UnboxedPlainObjectClassOps = {
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* getProperty */
nullptr, /* setProperty */
nullptr, /* enumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
nullptr, /* finalize */
nullptr, /* call */
nullptr, /* hasInstance */
nullptr, /* construct */
UnboxedPlainObject::trace,
};
static const ObjectOps UnboxedPlainObjectObjectOps = {
UnboxedPlainObject::obj_lookupProperty,
UnboxedPlainObject::obj_defineProperty,
UnboxedPlainObject::obj_hasProperty,
UnboxedPlainObject::obj_getProperty,
UnboxedPlainObject::obj_setProperty,
UnboxedPlainObject::obj_getOwnPropertyDescriptor,
UnboxedPlainObject::obj_deleteProperty,
nullptr, /* getElements */
UnboxedPlainObject::obj_enumerate,
nullptr /* funToString */
};
const Class UnboxedPlainObject::class_ = {
js_Object_str,
Class::NON_NATIVE |
JSCLASS_HAS_CACHED_PROTO(JSProto_Object) |
JSCLASS_DELAY_METADATA_BUILDER,
&UnboxedPlainObjectClassOps,
JS_NULL_CLASS_SPEC,
JS_NULL_CLASS_EXT,
&UnboxedPlainObjectObjectOps
};
/////////////////////////////////////////////////////////////////////
// UnboxedArrayObject
/////////////////////////////////////////////////////////////////////
template <JSValueType Type>
DenseElementResult
AppendUnboxedDenseElements(UnboxedArrayObject* obj, uint32_t initlen,
MutableHandle<GCVector<Value>> values)
{
for (size_t i = 0; i < initlen; i++)
values.infallibleAppend(obj->getElementSpecific<Type>(i));
return DenseElementResult::Success;
}
DefineBoxedOrUnboxedFunctor3(AppendUnboxedDenseElements,
UnboxedArrayObject*, uint32_t, MutableHandle<GCVector<Value>>);
/* static */ bool
UnboxedArrayObject::convertToNativeWithGroup(ExclusiveContext* cx, JSObject* obj,
ObjectGroup* group, Shape* shape)
{
size_t length = obj->as<UnboxedArrayObject>().length();
size_t initlen = obj->as<UnboxedArrayObject>().initializedLength();
Rooted<GCVector<Value>> values(cx, GCVector<Value>(cx));
if (!values.reserve(initlen))
return false;
AppendUnboxedDenseElementsFunctor functor(&obj->as<UnboxedArrayObject>(), initlen, &values);
DebugOnly<DenseElementResult> result = CallBoxedOrUnboxedSpecialization(functor, obj);
MOZ_ASSERT(result.value == DenseElementResult::Success);
obj->setGroup(group);
ArrayObject* aobj = &obj->as<ArrayObject>();
aobj->setLastPropertyMakeNative(cx, shape);
// Make sure there is at least one element, so that this array does not
// use emptyObjectElements / emptyObjectElementsShared.
if (!aobj->ensureElements(cx, Max<size_t>(initlen, 1)))
return false;
MOZ_ASSERT(!aobj->getDenseInitializedLength());
aobj->setDenseInitializedLength(initlen);
aobj->initDenseElements(0, values.begin(), initlen);
aobj->setLengthInt32(length);
return true;
}
/* static */ bool
UnboxedArrayObject::convertToNative(JSContext* cx, JSObject* obj)
{
const UnboxedLayout& layout = obj->as<UnboxedArrayObject>().layout();
if (!layout.nativeGroup()) {
if (!UnboxedLayout::makeNativeGroup(cx, obj->group()))
return false;
}
return convertToNativeWithGroup(cx, obj, layout.nativeGroup(), layout.nativeShape());
}
bool
UnboxedArrayObject::convertInt32ToDouble(ExclusiveContext* cx, ObjectGroup* group)
{
MOZ_ASSERT(elementType() == JSVAL_TYPE_INT32);
MOZ_ASSERT(group->unboxedLayout().elementType() == JSVAL_TYPE_DOUBLE);
Vector<int32_t> values(cx);
if (!values.reserve(initializedLength()))
return false;
for (size_t i = 0; i < initializedLength(); i++)
values.infallibleAppend(getElementSpecific<JSVAL_TYPE_INT32>(i).toInt32());
uint8_t* newElements;
if (hasInlineElements()) {
newElements = AllocateObjectBuffer<uint8_t>(cx, this, capacity() * sizeof(double));
} else {
newElements = ReallocateObjectBuffer<uint8_t>(cx, this, elements(),
capacity() * sizeof(int32_t),
capacity() * sizeof(double));
}
if (!newElements)
return false;
setGroup(group);
elements_ = newElements;
for (size_t i = 0; i < initializedLength(); i++)
setElementNoTypeChangeSpecific<JSVAL_TYPE_DOUBLE>(i, DoubleValue(values[i]));
return true;
}
/* static */ UnboxedArrayObject*
UnboxedArrayObject::create(ExclusiveContext* cx, HandleObjectGroup group, uint32_t length,
NewObjectKind newKind, uint32_t maxLength)
{
MOZ_ASSERT(length <= MaximumCapacity);
MOZ_ASSERT(group->clasp() == &class_);
uint32_t elementSize = UnboxedTypeSize(group->unboxedLayout().elementType());
uint32_t capacity = Min(length, maxLength);
uint32_t nbytes = offsetOfInlineElements() + elementSize * capacity;
UnboxedArrayObject* res;
if (nbytes <= JSObject::MAX_BYTE_SIZE) {
gc::AllocKind allocKind = gc::GetGCObjectKindForBytes(nbytes);
// If there was no provided length information, pick an allocation kind
// to accommodate small arrays (as is done for normal native arrays).
if (capacity == 0)
allocKind = gc::AllocKind::OBJECT8;
res = NewObjectWithGroup<UnboxedArrayObject>(cx, group, allocKind, newKind);
if (!res)
return nullptr;
res->setInitializedLengthNoBarrier(0);
res->setInlineElements();
size_t actualCapacity = (GetGCKindBytes(allocKind) - offsetOfInlineElements()) / elementSize;
MOZ_ASSERT(actualCapacity >= capacity);
res->setCapacityIndex(exactCapacityIndex(actualCapacity));
} else {
res = NewObjectWithGroup<UnboxedArrayObject>(cx, group, gc::AllocKind::OBJECT0, newKind);
if (!res)
return nullptr;
res->setInitializedLengthNoBarrier(0);
uint32_t capacityIndex = (capacity == length)
? CapacityMatchesLengthIndex
: chooseCapacityIndex(capacity, length);
uint32_t actualCapacity = computeCapacity(capacityIndex, length);
res->elements_ = AllocateObjectBuffer<uint8_t>(cx, res, actualCapacity * elementSize);
if (!res->elements_) {
// Make the object safe for GC.
res->setInlineElements();
return nullptr;
}
res->setCapacityIndex(capacityIndex);
}
res->setLength(cx, length);
return res;
}
bool
UnboxedArrayObject::setElement(ExclusiveContext* cx, size_t index, const Value& v)
{
MOZ_ASSERT(index < initializedLength());
uint8_t* p = elements() + index * elementSize();
return SetUnboxedValue(cx, this, JSID_VOID, p, elementType(), v, /* preBarrier = */ true);
}
bool
UnboxedArrayObject::initElement(ExclusiveContext* cx, size_t index, const Value& v)
{
MOZ_ASSERT(index < initializedLength());
uint8_t* p = elements() + index * elementSize();
return SetUnboxedValue(cx, this, JSID_VOID, p, elementType(), v, /* preBarrier = */ false);
}
void
UnboxedArrayObject::initElementNoTypeChange(size_t index, const Value& v)
{
MOZ_ASSERT(index < initializedLength());
uint8_t* p = elements() + index * elementSize();
if (UnboxedTypeNeedsPreBarrier(elementType()))
*reinterpret_cast<void**>(p) = nullptr;
SetUnboxedValueNoTypeChange(this, p, elementType(), v, /* preBarrier = */ false);
}
Value
UnboxedArrayObject::getElement(size_t index)
{
MOZ_ASSERT(index < initializedLength());
uint8_t* p = elements() + index * elementSize();
return GetUnboxedValue(p, elementType(), /* maybeUninitialized = */ false);
}
/* static */ void
UnboxedArrayObject::trace(JSTracer* trc, JSObject* obj)
{
JSValueType type = obj->as<UnboxedArrayObject>().elementType();
if (!UnboxedTypeNeedsPreBarrier(type))
return;
MOZ_ASSERT(obj->as<UnboxedArrayObject>().elementSize() == sizeof(uintptr_t));
size_t initlen = obj->as<UnboxedArrayObject>().initializedLength();
void** elements = reinterpret_cast<void**>(obj->as<UnboxedArrayObject>().elements());
switch (type) {
case JSVAL_TYPE_OBJECT:
for (size_t i = 0; i < initlen; i++) {
GCPtrObject* heap = reinterpret_cast<GCPtrObject*>(elements + i);
TraceNullableEdge(trc, heap, "unboxed_object");
}
break;
case JSVAL_TYPE_STRING:
for (size_t i = 0; i < initlen; i++) {
GCPtrString* heap = reinterpret_cast<GCPtrString*>(elements + i);
TraceEdge(trc, heap, "unboxed_string");
}
break;
default:
MOZ_CRASH();
}
}
/* static */ void
UnboxedArrayObject::objectMoved(JSObject* obj, const JSObject* old)
{
UnboxedArrayObject& dst = obj->as<UnboxedArrayObject>();
const UnboxedArrayObject& src = old->as<UnboxedArrayObject>();
// Fix up possible inline data pointer.
if (src.hasInlineElements())
dst.setInlineElements();
}
/* static */ void
UnboxedArrayObject::finalize(FreeOp* fop, JSObject* obj)
{
MOZ_ASSERT(!IsInsideNursery(obj));
if (!obj->as<UnboxedArrayObject>().hasInlineElements())
js_free(obj->as<UnboxedArrayObject>().elements());
}
/* static */ size_t
UnboxedArrayObject::objectMovedDuringMinorGC(JSTracer* trc, JSObject* dst, JSObject* src,
gc::AllocKind allocKind)
{
UnboxedArrayObject* ndst = &dst->as<UnboxedArrayObject>();
UnboxedArrayObject* nsrc = &src->as<UnboxedArrayObject>();
MOZ_ASSERT(ndst->elements() == nsrc->elements());
Nursery& nursery = trc->runtime()->gc.nursery;
if (!nursery.isInside(nsrc->elements())) {
nursery.removeMallocedBuffer(nsrc->elements());
return 0;
}
// Determine if we can use inline data for the target array. If this is
// possible, the nursery will have picked an allocation size that is large
// enough.
size_t nbytes = nsrc->capacity() * nsrc->elementSize();
if (offsetOfInlineElements() + nbytes <= GetGCKindBytes(allocKind)) {
ndst->setInlineElements();
} else {
MOZ_ASSERT(allocKind == gc::AllocKind::OBJECT0);
AutoEnterOOMUnsafeRegion oomUnsafe;
uint8_t* data = nsrc->zone()->pod_malloc<uint8_t>(nbytes);
if (!data)
oomUnsafe.crash("Failed to allocate unboxed array elements while tenuring.");
ndst->elements_ = data;
}
PodCopy(ndst->elements(), nsrc->elements(), nsrc->initializedLength() * nsrc->elementSize());
// Set a forwarding pointer for the element buffers in case they were
// preserved on the stack by Ion.
bool direct = nsrc->capacity() * nsrc->elementSize() >= sizeof(uintptr_t);
nursery.maybeSetForwardingPointer(trc, nsrc->elements(), ndst->elements(), direct);
return ndst->hasInlineElements() ? 0 : nbytes;
}
// Possible capacities for unboxed arrays. Some of these capacities might seem
// a little weird, but were chosen to allow the inline data of objects of each
// size to be fully utilized for arrays of the various types on both 32 bit and
// 64 bit platforms.
//
// To find the possible inline capacities, the following script was used:
//
// var fixedSlotCapacities = [0, 2, 4, 8, 12, 16];
// var dataSizes = [1, 4, 8];
// var header32 = 4 * 2 + 4 * 2;
// var header64 = 8 * 2 + 4 * 2;
//
// for (var i = 0; i < fixedSlotCapacities.length; i++) {
// var nfixed = fixedSlotCapacities[i];
// var size32 = 4 * 4 + 8 * nfixed - header32;
// var size64 = 8 * 4 + 8 * nfixed - header64;
// for (var j = 0; j < dataSizes.length; j++) {
// print(size32 / dataSizes[j]);
// print(size64 / dataSizes[j]);
// }
// }
//
/* static */ const uint32_t
UnboxedArrayObject::CapacityArray[] = {
UINT32_MAX, // For CapacityMatchesLengthIndex.
0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 13, 16, 17, 18, 24, 26, 32, 34, 40, 64, 72, 96, 104, 128, 136,
256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288,
1048576, 2097152, 3145728, 4194304, 5242880, 6291456, 7340032, 8388608, 9437184, 11534336,
13631488, 15728640, 17825792, 20971520, 24117248, 27262976, 31457280, 35651584, 40894464,
46137344, 52428800, 59768832, MaximumCapacity
};
static const uint32_t
Pow2CapacityIndexes[] = {
2, // 1
3, // 2
5, // 4
8, // 8
13, // 16
18, // 32
21, // 64
25, // 128
27, // 256
28, // 512
29, // 1024
30, // 2048
31, // 4096
32, // 8192
33, // 16384
34, // 32768
35, // 65536
36, // 131072
37, // 262144
38, // 524288
39 // 1048576
};
static const uint32_t MebiCapacityIndex = 39;
/* static */ uint32_t
UnboxedArrayObject::chooseCapacityIndex(uint32_t capacity, uint32_t length)
{
// Note: the structure and behavior of this method follow along with
// NativeObject::goodAllocated. Changes to the allocation strategy in one
// should generally be matched by the other.
// Make sure we have enough space to store all possible values for the capacity index.
// This ought to be a static_assert, but MSVC doesn't like that.
MOZ_ASSERT(mozilla::ArrayLength(CapacityArray) - 1 <= (CapacityMask >> CapacityShift));
// The caller should have ensured the capacity is possible for an unboxed array.
MOZ_ASSERT(capacity <= MaximumCapacity);
static const uint32_t Mebi = 1024 * 1024;
if (capacity <= Mebi) {
capacity = mozilla::RoundUpPow2(capacity);
// When the required capacity is close to the array length, then round
// up to the array length itself, as for NativeObject.
if (length >= capacity && capacity > (length / 3) * 2)
return CapacityMatchesLengthIndex;
if (capacity < MinimumDynamicCapacity)
capacity = MinimumDynamicCapacity;
uint32_t bit = mozilla::FloorLog2Size(capacity);
MOZ_ASSERT(capacity == uint32_t(1 << bit));
MOZ_ASSERT(bit <= 20);
MOZ_ASSERT(mozilla::ArrayLength(Pow2CapacityIndexes) == 21);
uint32_t index = Pow2CapacityIndexes[bit];
MOZ_ASSERT(CapacityArray[index] == capacity);
return index;
}
MOZ_ASSERT(CapacityArray[MebiCapacityIndex] == Mebi);
for (uint32_t i = MebiCapacityIndex + 1;; i++) {
if (CapacityArray[i] >= capacity)
return i;
}
MOZ_CRASH("Invalid capacity");
}
/* static */ uint32_t
UnboxedArrayObject::exactCapacityIndex(uint32_t capacity)
{
for (size_t i = CapacityMatchesLengthIndex + 1; i < ArrayLength(CapacityArray); i++) {
if (CapacityArray[i] == capacity)
return i;
}
MOZ_CRASH();
}
bool
UnboxedArrayObject::growElements(ExclusiveContext* cx, size_t cap)
{
// The caller should have checked if this capacity is possible for an
// unboxed array, so the only way this call can fail is from OOM.
MOZ_ASSERT(cap <= MaximumCapacity);
uint32_t oldCapacity = capacity();
uint32_t newCapacityIndex = chooseCapacityIndex(cap, length());
uint32_t newCapacity = computeCapacity(newCapacityIndex, length());
MOZ_ASSERT(oldCapacity < cap);
MOZ_ASSERT(cap <= newCapacity);
// The allocation size computation below cannot have integer overflows.
JS_STATIC_ASSERT(MaximumCapacity < UINT32_MAX / sizeof(double));
uint8_t* newElements;
if (hasInlineElements()) {
newElements = AllocateObjectBuffer<uint8_t>(cx, this, newCapacity * elementSize());
if (!newElements)
return false;
js_memcpy(newElements, elements(), initializedLength() * elementSize());
} else {
newElements = ReallocateObjectBuffer<uint8_t>(cx, this, elements(),
oldCapacity * elementSize(),
newCapacity * elementSize());
if (!newElements)
return false;
}
elements_ = newElements;
setCapacityIndex(newCapacityIndex);
return true;
}
void
UnboxedArrayObject::shrinkElements(ExclusiveContext* cx, size_t cap)
{
if (hasInlineElements())
return;
uint32_t oldCapacity = capacity();
uint32_t newCapacityIndex = chooseCapacityIndex(cap, 0);
uint32_t newCapacity = computeCapacity(newCapacityIndex, 0);
MOZ_ASSERT(cap < oldCapacity);
MOZ_ASSERT(cap <= newCapacity);
if (newCapacity >= oldCapacity)
return;
uint8_t* newElements = ReallocateObjectBuffer<uint8_t>(cx, this, elements(),
oldCapacity * elementSize(),
newCapacity * elementSize());
if (!newElements)
return;
elements_ = newElements;
setCapacityIndex(newCapacityIndex);
}
bool
UnboxedArrayObject::containsProperty(ExclusiveContext* cx, jsid id)
{
if (JSID_IS_INT(id) && uint32_t(JSID_TO_INT(id)) < initializedLength())
return true;
if (JSID_IS_ATOM(id) && JSID_TO_ATOM(id) == cx->names().length)
return true;
return false;
}
/* static */ bool
UnboxedArrayObject::obj_lookupProperty(JSContext* cx, HandleObject obj,
HandleId id, MutableHandleObject objp,
MutableHandleShape propp)
{
if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) {
MarkNonNativePropertyFound<CanGC>(propp);
objp.set(obj);
return true;
}
RootedObject proto(cx, obj->staticPrototype());
if (!proto) {
objp.set(nullptr);
propp.set(nullptr);
return true;
}
return LookupProperty(cx, proto, id, objp, propp);
}
/* static */ bool
UnboxedArrayObject::obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id,
Handle<PropertyDescriptor> desc,
ObjectOpResult& result)
{
if (JSID_IS_INT(id) && !desc.getter() && !desc.setter() && desc.attributes() == JSPROP_ENUMERATE) {
UnboxedArrayObject* nobj = &obj->as<UnboxedArrayObject>();
uint32_t index = JSID_TO_INT(id);
if (index < nobj->initializedLength()) {
if (nobj->setElement(cx, index, desc.value()))
return result.succeed();
} else if (index == nobj->initializedLength() && index < MaximumCapacity) {
if (nobj->initializedLength() == nobj->capacity()) {
if (!nobj->growElements(cx, index + 1))
return false;
}
nobj->setInitializedLength(index + 1);
if (nobj->initElement(cx, index, desc.value())) {
if (nobj->length() <= index)
nobj->setLengthInt32(index + 1);
return result.succeed();
}
nobj->setInitializedLengthNoBarrier(index);
}
}
if (!convertToNative(cx, obj))
return false;
return DefineProperty(cx, obj, id, desc, result);
}
/* static */ bool
UnboxedArrayObject::obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp)
{
if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) {
*foundp = true;
return true;
}
RootedObject proto(cx, obj->staticPrototype());
if (!proto) {
*foundp = false;
return true;
}
return HasProperty(cx, proto, id, foundp);
}
/* static */ bool
UnboxedArrayObject::obj_getProperty(JSContext* cx, HandleObject obj, HandleValue receiver,
HandleId id, MutableHandleValue vp)
{
if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) {
if (JSID_IS_INT(id))
vp.set(obj->as<UnboxedArrayObject>().getElement(JSID_TO_INT(id)));
else
vp.set(Int32Value(obj->as<UnboxedArrayObject>().length()));
return true;
}
RootedObject proto(cx, obj->staticPrototype());
if (!proto) {
vp.setUndefined();
return true;
}
return GetProperty(cx, proto, receiver, id, vp);
}
/* static */ bool
UnboxedArrayObject::obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
HandleValue receiver, ObjectOpResult& result)
{
if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) {
if (receiver.isObject() && obj == &receiver.toObject()) {
if (JSID_IS_INT(id)) {
if (obj->as<UnboxedArrayObject>().setElement(cx, JSID_TO_INT(id), v))
return result.succeed();
} else {
uint32_t len;
if (!CanonicalizeArrayLengthValue(cx, v, &len))
return false;
UnboxedArrayObject* nobj = &obj->as<UnboxedArrayObject>();
if (len < nobj->initializedLength()) {
nobj->setInitializedLength(len);
nobj->shrinkElements(cx, len);
}
nobj->setLength(cx, len);
return result.succeed();
}
if (!convertToNative(cx, obj))
return false;
return SetProperty(cx, obj, id, v, receiver, result);
}
return SetPropertyByDefining(cx, id, v, receiver, result);
}
return SetPropertyOnProto(cx, obj, id, v, receiver, result);
}
/* static */ bool
UnboxedArrayObject::obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id,
MutableHandle<PropertyDescriptor> desc)
{
if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) {
if (JSID_IS_INT(id)) {
desc.value().set(obj->as<UnboxedArrayObject>().getElement(JSID_TO_INT(id)));
desc.setAttributes(JSPROP_ENUMERATE);
} else {
desc.value().set(Int32Value(obj->as<UnboxedArrayObject>().length()));
desc.setAttributes(JSPROP_PERMANENT);
}
desc.object().set(obj);
return true;
}
desc.object().set(nullptr);
return true;
}
/* static */ bool
UnboxedArrayObject::obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id,
ObjectOpResult& result)
{
if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) {
size_t initlen = obj->as<UnboxedArrayObject>().initializedLength();
if (JSID_IS_INT(id) && JSID_TO_INT(id) == int32_t(initlen - 1)) {
obj->as<UnboxedArrayObject>().setInitializedLength(initlen - 1);
obj->as<UnboxedArrayObject>().shrinkElements(cx, initlen - 1);
return result.succeed();
}
}
if (!convertToNative(cx, obj))
return false;
return DeleteProperty(cx, obj, id, result);
}
/* static */ bool
UnboxedArrayObject::obj_enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties,
bool enumerableOnly)
{
for (size_t i = 0; i < obj->as<UnboxedArrayObject>().initializedLength(); i++) {
if (!properties.append(INT_TO_JSID(i)))
return false;
}
if (!enumerableOnly && !properties.append(NameToId(cx->names().length)))
return false;
return true;
}
static const ClassOps UnboxedArrayObjectClassOps = {
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* getProperty */
nullptr, /* setProperty */
nullptr, /* enumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
UnboxedArrayObject::finalize,
nullptr, /* call */
nullptr, /* hasInstance */
nullptr, /* construct */
UnboxedArrayObject::trace,
};
static const ClassExtension UnboxedArrayObjectClassExtension = {
nullptr, /* weakmapKeyDelegateOp */
UnboxedArrayObject::objectMoved
};
static const ObjectOps UnboxedArrayObjectObjectOps = {
UnboxedArrayObject::obj_lookupProperty,
UnboxedArrayObject::obj_defineProperty,
UnboxedArrayObject::obj_hasProperty,
UnboxedArrayObject::obj_getProperty,
UnboxedArrayObject::obj_setProperty,
UnboxedArrayObject::obj_getOwnPropertyDescriptor,
UnboxedArrayObject::obj_deleteProperty,
nullptr, /* getElements */
UnboxedArrayObject::obj_enumerate,
nullptr /* funToString */
};
const Class UnboxedArrayObject::class_ = {
"Array",
Class::NON_NATIVE |
JSCLASS_SKIP_NURSERY_FINALIZE |
JSCLASS_BACKGROUND_FINALIZE,
&UnboxedArrayObjectClassOps,
JS_NULL_CLASS_SPEC,
&UnboxedArrayObjectClassExtension,
&UnboxedArrayObjectObjectOps
};
/////////////////////////////////////////////////////////////////////
// API
/////////////////////////////////////////////////////////////////////
static 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);
}