1733 lines
60 KiB
C++
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, ¬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_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);
|
|
}
|