#include "vm/UnboxedObject-inl.h"
#include "mozilla/Maybe.h"
#include "mozilla/MemoryChecking.h"
#include "jit/BaselineIC.h"
#include "jit/ExecutableAllocator.h"
#include "jit/JitCommon.h"
#include "jit/Linker.h"
#include "gc/Nursery-inl.h"
#include "jit/MacroAssembler-inl.h"
#include "vm/JSObject-inl.h"
#include "vm/JSScript-inl.h"
#include "vm/Shape-inl.h"
#include "vm/TypeInference-inl.h"
using mozilla::ArrayLength;
using mozilla::PodCopy;
using namespace js;
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 ) {
if (newScript_ && writeBarrier) {
TypeNewScript::writeBarrierPre(newScript_);
}
newScript_ = newScript;
}
static const uintptr_t CLEAR_CONSTRUCTOR_CODE_TOKEN = 0x1;
bool UnboxedLayout::makeConstructorCode(JSContext* cx,
HandleObjectGroup group) {
gc::AutoSuppressGC suppress(cx);
using namespace jit;
if (!cx->realm()->ensureJitRealmExists(cx)) {
return false;
}
AutoSweepObjectGroup sweep(group);
UnboxedLayout& layout = group->unboxedLayout(sweep);
MOZ_ASSERT(!layout.constructorCode());
UnboxedPlainObject* templateObject =
UnboxedPlainObject::create(cx, group, TenuredObject);
if (!templateObject) {
return false;
}
JitContext jitContext(cx, nullptr);
StackMacroAssembler 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
MOZ_ASSERT(PseudoStackPointer64.Is(masm.GetStackPointer64()));
masm.Str(PseudoStackPointer64, vixl::MemOperand(sp, -16, vixl::PreIndex));
masm.initPseudoStackPtr();
#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);
if (ScratchDoubleReg.volatile_()) {
masm.push(ScratchDoubleReg);
}
Label failure, tenuredObject, allocated, unknownProperties;
masm.branch32(Assembler::NotEqual, newKindReg, Imm32(GenericObject),
&tenuredObject);
masm.load32(AbsoluteAddress(group->addressOfFlags()), scratch1);
masm.branchTest32(Assembler::NonZero, scratch1,
Imm32(OBJECT_FLAG_UNKNOWN_PROPERTIES), &unknownProperties);
masm.branchTest32(Assembler::NonZero, scratch1, Imm32(OBJECT_FLAG_PRE_TENURE),
&tenuredObject);
masm.bind(&unknownProperties);
TemplateObject templateObj(templateObject);
masm.createGCObject(object, scratch1, templateObj, gc::DefaultHeap, &failure,
false);
masm.jump(&allocated);
masm.bind(&tenuredObject);
masm.createGCObject(object, scratch1, templateObj, gc::TenuredHeap, &failure,
false);
Label postBarrier;
for (size_t i = 0; i < layout.properties().length(); i++) {
const UnboxedLayout::Property& property = layout.properties()[i];
if (!UnboxedTypeNeedsPostBarrier(property.type)) {
continue;
}
Address valueAddress(
propertiesReg, i * sizeof(IdValuePair) + offsetof(IdValuePair, value));
if (property.type == JSVAL_TYPE_OBJECT) {
Label notObject;
masm.branchTestObject(Assembler::NotEqual, valueAddress, ¬Object);
Register valueObject = masm.extractObject(valueAddress, scratch1);
masm.branchPtrInNurseryChunk(Assembler::Equal, valueObject, scratch2,
&postBarrier);
masm.bind(¬Object);
} else {
MOZ_ASSERT(property.type == JSVAL_TYPE_STRING);
Label notString;
masm.branchTestString(Assembler::NotEqual, valueAddress, ¬String);
masm.unboxString(valueAddress, scratch1);
masm.branchPtrInNurseryChunk(Assembler::Equal, scratch1, scratch2,
&postBarrier);
masm.bind(¬String);
}
}
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(sweep, 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, payloadReg,
&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);
}
if (ScratchDoubleReg.volatile_()) {
masm.pop(ScratchDoubleReg);
}
masm.PopRegsInMask(savedNonVolatileRegisters);
#ifdef JS_CODEGEN_ARM64
masm.Mov(sp, PseudoStackPointer64);
masm.Ldr(PseudoStackPointer64, vixl::MemOperand(sp, 16, vixl::PostIndex));
masm.Ret(vixl::lr);
#else
masm.abiret();
#endif
masm.bind(&failureStoreOther);
masm.initUnboxedObjectContents(object,
templateObject->layoutDontCheckGeneration());
masm.bind(&failure);
masm.movePtr(ImmWord(0), object);
masm.jump(&done);
masm.bind(&failureStoreObject);
{
Label isObject;
masm.branchTestObject(Assembler::Equal, valueOperand, &isObject);
masm.branchTestNull(Assembler::NotEqual, valueOperand, &failureStoreOther);
masm.bind(&isObject);
}
masm.initUnboxedObjectContents(object,
templateObject->layoutDontCheckGeneration());
masm.movePtr(ImmWord(CLEAR_CONSTRUCTOR_CODE_TOKEN), object);
masm.jump(&done);
Linker linker(masm, "UnboxedObject");
JitCode* code = linker.newCode(cx, CodeKind::Other);
if (!code) {
return false;
}
layout.setConstructorCode(code);
return true;
}
void UnboxedLayout::detachFromRealm() {
if (isInList()) {
remove();
}
}
static Value GetUnboxedValue(uint8_t* p, JSValueType type,
bool maybeUninitialized) {
switch (type) {
case JSVAL_TYPE_BOOLEAN:
if (maybeUninitialized) {
MOZ_MAKE_MEM_DEFINED(p, 1);
}
return BooleanValue(*p != 0);
case JSVAL_TYPE_INT32:
if (maybeUninitialized) {
MOZ_MAKE_MEM_DEFINED(p, sizeof(int32_t));
}
return Int32Value(*reinterpret_cast<int32_t*>(p));
case JSVAL_TYPE_DOUBLE: {
if (maybeUninitialized) {
MOZ_MAKE_MEM_DEFINED(p, sizeof(double));
}
double d = *reinterpret_cast<double*>(p);
if (maybeUninitialized) {
return DoubleValue(JS::CanonicalizeNaN(d));
}
return DoubleValue(d);
}
case JSVAL_TYPE_STRING:
return StringValue(*reinterpret_cast<JSString**>(p));
case JSVAL_TYPE_OBJECT:
return ObjectOrNullValue(*reinterpret_cast<JSObject**>(p));
default:
MOZ_CRASH("Invalid type for unboxed value");
}
}
static bool SetUnboxedValue(JSContext* cx, JSObject* unboxedObject, jsid id,
uint8_t* p, JSValueType type, const Value& v,
bool preBarrier) {
switch (type) {
case JSVAL_TYPE_BOOLEAN:
if (v.isBoolean()) {
*p = v.toBoolean();
return true;
}
return false;
case JSVAL_TYPE_INT32:
if (v.isInt32()) {
*reinterpret_cast<int32_t*>(p) = v.toInt32();
return true;
}
return false;
case JSVAL_TYPE_DOUBLE:
if (v.isNumber()) {
*reinterpret_cast<double*>(p) = v.toNumber();
return true;
}
return false;
case JSVAL_TYPE_STRING:
if (v.isString()) {
JSString** np = reinterpret_cast<JSString**>(p);
if (IsInsideNursery(v.toString()) && !IsInsideNursery(unboxedObject)) {
v.toString()->storeBuffer()->putWholeCell(unboxedObject);
}
if (preBarrier) {
JSString::writeBarrierPre(*np);
}
*np = v.toString();
return true;
}
return false;
case JSVAL_TYPE_OBJECT:
if (v.isObjectOrNull()) {
JSObject** np = reinterpret_cast<JSObject**>(p);
AddTypePropertyId(cx, unboxedObject, id, v);
JSObject* obj = v.toObjectOrNull();
if (IsInsideNursery(obj) && !IsInsideNursery(unboxedObject)) {
obj->storeBuffer()->putWholeCell(unboxedObject);
}
if (preBarrier) {
JSObject::writeBarrierPre(*np);
}
*np = obj;
return true;
}
return false;
default:
MOZ_CRASH("Invalid type for unboxed value");
}
}
bool UnboxedPlainObject::setValue(JSContext* 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,
true);
}
Value UnboxedPlainObject::getValue(const UnboxedLayout::Property& property,
bool maybeUninitialized ) {
uint8_t* p = &data_[property.offset];
return GetUnboxedValue(p, property.type, maybeUninitialized);
}
void UnboxedPlainObject::trace(JSTracer* trc, JSObject* obj) {
UnboxedPlainObject* uobj = &obj->as<UnboxedPlainObject>();
if (uobj->maybeExpando()) {
TraceManuallyBarrieredEdge(trc, uobj->addressOfExpando(),
"unboxed_expando");
}
const UnboxedLayout& layout = uobj->layoutDontCheckGeneration();
const int32_t* list = layout.traceList();
if (!list) {
return;
}
uint8_t* data = uobj->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++;
}
MOZ_ASSERT(*(list + 1) == -1);
}
UnboxedExpandoObject* UnboxedPlainObject::ensureExpando(
JSContext* cx, Handle<UnboxedPlainObject*> obj) {
if (obj->maybeExpando()) {
return obj->maybeExpando();
}
UnboxedExpandoObject* expando = NewObjectWithGivenProto<UnboxedExpandoObject>(
cx, nullptr, gc::AllocKind::OBJECT4);
if (!expando) {
return nullptr;
}
MarkObjectGroupUnknownProperties(cx, expando->group());
MOZ_ASSERT_IF(!IsInsideNursery(expando), !IsInsideNursery(obj));
if (IsInsideNursery(expando) && !IsInsideNursery(obj)) {
expando->storeBuffer()->putWholeCell(obj);
}
obj->setExpandoUnsafe(expando);
return expando;
}
bool UnboxedPlainObject::containsUnboxedOrExpandoProperty(JSContext* cx,
jsid id) const {
if (layoutDontCheckGeneration().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) {
AutoSweepObjectGroup sweepOld(oldGroup);
HeapTypeSet* typeProperty = oldGroup->maybeGetProperty(sweepOld, 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) {
Rooted<PlainObject*> obj(
cx, NewObjectWithGroup<PlainObject>(cx, group, layout.getAllocKind(),
TenuredObject));
if (!obj) {
return nullptr;
}
RootedId id(cx);
for (size_t i = 0; i < layout.properties().length(); i++) {
const UnboxedLayout::Property& property = layout.properties()[i];
id = NameToId(property.name);
Shape* shape = NativeObject::addDataProperty(
cx, obj, id, SHAPE_INVALID_SLOT, JSPROP_ENUMERATE);
if (!shape) {
return nullptr;
}
MOZ_ASSERT(shape->slot() == i);
MOZ_ASSERT(obj->slotSpan() == i + 1);
MOZ_ASSERT(!obj->inDictionaryMode());
}
return obj;
}
bool UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group) {
MOZ_ASSERT(cx->realm() == group->realm());
AutoEnterAnalysis enter(cx);
AutoSweepObjectGroup sweep(group);
UnboxedLayout& layout = group->unboxedLayout(sweep);
Rooted<TaggedProto> proto(cx, group->proto());
MOZ_ASSERT(!layout.nativeGroup());
RootedObjectGroup replacementGroup(cx);
if (layout.newScript()) {
replacementGroup = ObjectGroupRealm::makeGroup(cx, group->realm(),
&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::gcTracer.traceTypeNewScript(replacementGroup);
group->clearNewScript(cx, replacementGroup);
}
if (layout.allocationScript()) {
RootedScript script(cx, layout.allocationScript());
jsbytecode* pc = layout.allocationPc();
replacementGroup = ObjectGroupRealm::makeGroup(cx, group->realm(),
&PlainObject::class_, proto);
if (!replacementGroup) {
return false;
}
PlainObject* templateObject = &script->getObject(pc)->as<PlainObject>();
replacementGroup->addDefiniteProperties(cx, templateObject->lastProperty());
ObjectGroupRealm& realm = ObjectGroupRealm::get(group);
realm.replaceAllocationSiteGroup(script, pc, JSProto_Object,
replacementGroup);
if (script->hasICScript()) {
jit::ICEntry& entry =
script->icScript()->icEntryFromPCOffset(script->pcToOffset(pc));
jit::ICFallbackStub* fallback = entry.fallbackStub();
for (jit::ICStubIterator iter = fallback->beginChain(); !iter.atEnd();
iter++) {
iter.unlink(cx);
}
if (fallback->isNewObject_Fallback()) {
fallback->toNewObject_Fallback()->setTemplateObject(nullptr);
} else if (fallback->isNewArray_Fallback()) {
fallback->toNewArray_Fallback()->setTemplateGroup(replacementGroup);
}
}
}
size_t nfixed = gc::GetGCKindSlots(layout.getAllocKind());
RootedShape shape(cx, EmptyShape::getInitialShape(cx, &PlainObject::class_,
proto, nfixed, 0));
if (!shape) {
return false;
}
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));
shape = cx->zone()->propertyTree().getChild(cx, shape, child);
if (!shape) {
return false;
}
}
ObjectGroup* nativeGroup = ObjectGroupRealm::makeGroup(
cx, group->realm(), &PlainObject::class_, proto,
group->flags(sweep) & OBJECT_FLAG_DYNAMIC_MASK);
if (!nativeGroup) {
return false;
}
AutoSweepObjectGroup sweepNative(nativeGroup);
if (!group->unknownProperties(sweep)) {
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 (nativeGroup->unknownProperties(sweepNative)) {
break;
}
}
} else {
MOZ_ASSERT(nativeGroup->unknownProperties(sweepNative));
}
layout.nativeGroup_ = nativeGroup;
layout.nativeShape_ = shape;
layout.replacementGroup_ = replacementGroup;
nativeGroup->setOriginalUnboxedGroup(group);
group->markStateChange(sweep, cx);
return true;
}
NativeObject* UnboxedPlainObject::convertToNative(JSContext* cx,
JSObject* obj) {
const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layout();
UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando();
AutoRealm ar(cx, obj);
if (!layout.nativeGroup()) {
if (!UnboxedLayout::makeNativeGroup(cx, obj->group())) {
return nullptr;
}
if (obj->is<PlainObject>()) {
return &obj->as<PlainObject>();
}
}
AutoValueVector values(cx);
for (size_t i = 0; i < layout.properties().length(); i++) {
if (!values.append(obj->as<UnboxedPlainObject>().getValue(
layout.properties()[i], true))) {
return nullptr;
}
}
JSObject::writeBarrierPre(expando);
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) {
return &obj->as<PlainObject>();
}
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 nullptr;
}
}
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 nullptr;
}
}
}
::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 nullptr;
}
ObjectOpResult result;
if (!DefineProperty(cx, nobj, id, desc, result)) {
return nullptr;
}
MOZ_ASSERT(result.ok());
}
return nobj;
}
JS::Result<UnboxedObject*, JS::OOM&> UnboxedObject::createInternal(
JSContext* cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
js::HandleObjectGroup group) {
const js::Class* clasp = group->clasp();
MOZ_ASSERT(clasp == &UnboxedPlainObject::class_);
MOZ_ASSERT(CanBeFinalizedInBackground(kind, clasp));
kind = GetBackgroundAllocKind(kind);
debugCheckNewObject(group, nullptr, kind, heap);
JSObject* obj =
js::AllocateObject(cx, kind, 0, heap, clasp);
if (!obj) {
return cx->alreadyReportedOOM();
}
UnboxedObject* uobj = static_cast<UnboxedObject*>(obj);
uobj->initGroup(group);
MOZ_ASSERT(clasp->shouldDelayMetadataBuilder());
cx->realm()->setObjectPendingMetadata(cx, uobj);
js::gc::gcTracer.traceCreateObject(uobj);
return uobj;
}
UnboxedPlainObject* UnboxedPlainObject::create(JSContext* cx,
HandleObjectGroup group,
NewObjectKind newKind) {
AutoSetNewObjectMetadata metadata(cx);
MOZ_ASSERT(group->clasp() == &class_);
gc::AllocKind allocKind;
{
AutoSweepObjectGroup sweep(group);
allocKind = group->unboxedLayout(sweep).getAllocKind();
}
gc::InitialHeap heap = GetInitialHeap(newKind, &class_);
MOZ_ASSERT(newKind != SingletonObject);
JSObject* obj;
JS_TRY_VAR_OR_RETURN_NULL(cx, obj,
createInternal(cx, allocKind, heap, group));
UnboxedPlainObject* uobj = static_cast<UnboxedPlainObject*>(obj);
uobj->initExpando();
const int32_t* list = uobj->layout().traceList();
if (list) {
uint8_t* data = uobj->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++;
}
MOZ_ASSERT(*(list + 1) == -1);
}
return uobj;
}
JSObject* UnboxedPlainObject::createWithProperties(JSContext* cx,
HandleObjectGroup group,
NewObjectKind newKind,
IdValuePair* properties) {
MOZ_ASSERT(newKind == GenericObject || newKind == TenuredObject);
{
AutoSweepObjectGroup sweep(group);
UnboxedLayout& layout = group->unboxedLayout(sweep);
if (layout.constructorCode()) {
MOZ_ASSERT(!cx->helperThread());
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;
}
mozilla::Maybe<AutoSweepObjectGroup> sweep;
sweep.emplace(group);
UnboxedLayout& layout = group->unboxedLayout(*sweep);
for (size_t i = 0; i < layout.properties().length(); i++) {
if (!obj->setValue(cx, layout.properties()[i], properties[i].value)) {
sweep.reset();
return NewPlainObjectWithProperties(
cx, properties, layout.properties().length(), newKind);
}
}
#ifndef JS_CODEGEN_NONE
if (!cx->helperThread() && !group->unknownProperties(*sweep) &&
!layout.constructorCode() && cx->runtime()->jitSupportsFloatingPoint &&
jit::CanLikelyAllocateMoreExecutableMemory()) {
if (!UnboxedLayout::makeConstructorCode(cx, group)) {
return nullptr;
}
}
#endif
return obj;
}
bool UnboxedPlainObject::obj_lookupProperty(
JSContext* cx, HandleObject obj, HandleId id, MutableHandleObject objp,
MutableHandle<PropertyResult> propp) {
if (obj->as<UnboxedPlainObject>().containsUnboxedOrExpandoProperty(cx, id)) {
propp.setNonNativeProperty();
objp.set(obj);
return true;
}
RootedObject proto(cx, obj->staticPrototype());
if (!proto) {
objp.set(nullptr);
propp.setNotFound();
return true;
}
return LookupProperty(cx, proto, id, objp, propp);
}
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) {
if (obj->as<UnboxedPlainObject>().setValue(cx, *property, desc.value())) {
return result.succeed();
}
}
if (!convertToNative(cx, obj)) {
return false;
}
return DefineProperty(cx, obj, id, desc, result);
}
Rooted<UnboxedExpandoObject*> expando(
cx, ensureExpando(cx, obj.as<UnboxedPlainObject>()));
if (!expando) {
return false;
}
AddTypePropertyId(cx, obj, id, desc.value());
return DefineProperty(cx, expando, id, desc, result);
}
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);
}
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);
}
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)) {
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);
}
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;
}
bool UnboxedPlainObject::obj_deleteProperty(JSContext* cx, HandleObject obj,
HandleId id,
ObjectOpResult& result) {
if (!convertToNative(cx, obj)) {
return false;
}
return DeleteProperty(cx, obj, id, result);
}
bool UnboxedPlainObject::newEnumerate(JSContext* cx, HandleObject obj,
AutoIdVector& properties,
bool enumerableOnly) {
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,
nullptr,
nullptr,
UnboxedPlainObject::newEnumerate,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
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,
nullptr
};
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};
static bool UnboxedTypeIncludes(JSValueType supertype, JSValueType subtype) {
if (supertype == JSVAL_TYPE_DOUBLE && subtype == JSVAL_TYPE_INT32) {
return true;
}
if (supertype == JSVAL_TYPE_OBJECT && subtype == JSVAL_TYPE_NULL) {
return true;
}
return false;
}
static bool CombineUnboxedTypes(const Value& value, JSValueType* existing) {
JSValueType type =
value.isDouble() ? JSVAL_TYPE_DOUBLE : value.extractNonDoubleType();
if (*existing == JSVAL_TYPE_MAGIC || *existing == type ||
UnboxedTypeIncludes(type, *existing)) {
*existing = type;
return true;
}
if (UnboxedTypeIncludes(*existing, type)) {
return true;
}
return false;
}
static bool PropertiesAreSuperset(
const UnboxedLayout::PropertyVector& properties, UnboxedLayout* layout) {
for (size_t i = 0; i < layout->properties().length(); i++) {
const UnboxedLayout::Property& layoutProperty = layout->properties()[i];
bool found = false;
for (size_t j = 0; j < properties.length(); j++) {
if (layoutProperty.name == properties[j].name) {
found = (layoutProperty.type == properties[j].type);
break;
}
}
if (!found) {
return false;
}
}
return true;
}
static bool CombinePlainObjectProperties(
PlainObject* obj, Shape* templateShape,
UnboxedLayout::PropertyVector& properties) {
MOZ_ASSERT(gc::GetGCKindSlots(obj->asTenured().getAllocKind()) >=
Min(NativeObject::MAX_FIXED_SLOTS, templateShape->slotSpan()));
if (obj->lastProperty() != templateShape || obj->hasDynamicElements()) {
return false;
}
for (size_t i = 0; i < templateShape->slotSpan(); i++) {
Value val = obj->getSlot(i);
JSValueType& existing = properties[i].type;
if (!CombineUnboxedTypes(val, &existing)) {
return false;
}
}
return true;
}
static size_t ComputePlainObjectLayout(
JSContext* cx, ObjectGroupRealm& realm, Shape* templateShape,
UnboxedLayout::PropertyVector& properties) {
for (Shape::Range<NoGC> r(templateShape); !r.empty(); r.popFront()) {
size_t slot = r.front().slot();
MOZ_ASSERT(!properties[slot].name);
properties[slot].name = JSID_TO_ATOM(r.front().propid())->asPropertyName();
}
uint32_t offset = 0;
UnboxedLayout* bestExisting = nullptr;
for (UnboxedLayout* existing : realm.unboxedLayouts) {
if (PropertiesAreSuperset(properties, existing)) {
if (!bestExisting || existing->properties().length() >
bestExisting->properties().length()) {
bestExisting = existing;
}
}
}
if (bestExisting) {
for (size_t i = 0; i < bestExisting->properties().length(); i++) {
const UnboxedLayout::Property& existingProperty =
bestExisting->properties()[i];
for (size_t j = 0; j < templateShape->slotSpan(); j++) {
if (existingProperty.name == properties[j].name) {
MOZ_ASSERT(existingProperty.type == properties[j].type);
properties[j].offset = existingProperty.offset;
}
}
}
offset = bestExisting->size();
}
static const size_t typeSizes[] = {8, 4, 1};
for (size_t i = 0; i < ArrayLength(typeSizes); i++) {
size_t size = typeSizes[i];
for (size_t j = 0; j < templateShape->slotSpan(); j++) {
if (properties[j].offset != UINT32_MAX) {
continue;
}
JSValueType type = properties[j].type;
if (UnboxedTypeSize(type) == size) {
offset = JS_ROUNDUP(offset, size);
properties[j].offset = offset;
offset += size;
}
}
}
return offset;
}
static bool SetLayoutTraceList(JSContext* cx, UnboxedLayout* layout) {
Vector<int32_t, 8, SystemAllocPolicy> objectOffsets, stringOffsets;
for (size_t i = 0; i < layout->properties().length(); i++) {
const UnboxedLayout::Property& property = layout->properties()[i];
MOZ_ASSERT(property.offset != UINT32_MAX);
if (property.type == JSVAL_TYPE_OBJECT) {
if (!objectOffsets.append(property.offset)) {
return false;
}
} else if (property.type == JSVAL_TYPE_STRING) {
if (!stringOffsets.append(property.offset)) {
return false;
}
}
}
if (!objectOffsets.empty() || !stringOffsets.empty()) {
Vector<int32_t, 8, SystemAllocPolicy> entries;
if (!entries.appendAll(stringOffsets) || !entries.append(-1) ||
!entries.appendAll(objectOffsets) || !entries.append(-1) ||
!entries.append(-1)) {
return false;
}
int32_t* traceList = cx->zone()->pod_malloc<int32_t>(entries.length());
if (!traceList) {
return false;
}
PodCopy(traceList, entries.begin(), entries.length());
layout->setTraceList(traceList);
}
return true;
}
static inline Value NextValue(Handle<GCVector<Value>> values,
size_t* valueCursor) {
return values[(*valueCursor)++];
}
static bool GetValuesFromPreliminaryPlainObject(
PlainObject* obj, MutableHandle<GCVector<Value>> values) {
for (size_t i = 0; i < obj->slotSpan(); i++) {
if (!values.append(obj->getSlot(i))) {
return false;
}
}
return true;
}
void UnboxedPlainObject::fillAfterConvert(JSContext* cx,
Handle<GCVector<Value>> values,
size_t* valueCursor) {
initExpando();
memset(data(), 0, layout().size());
for (size_t i = 0; i < layout().properties().length(); i++) {
MOZ_ALWAYS_TRUE(
setValue(cx, layout().properties()[i], NextValue(values, valueCursor)));
}
}
bool js::TryConvertToUnboxedLayout(JSContext* cx, AutoEnterAnalysis& enter,
Shape* templateShape, ObjectGroup* group,
PreliminaryObjectArray* objects) {
MOZ_ASSERT(templateShape);
if (jit::JitOptions.disableUnboxedObjects) {
return true;
}
AutoSweepObjectGroup sweep(group);
MOZ_ASSERT(!templateShape->getObjectFlags());
if (group->runtimeFromAnyThread()->isSelfHostingGlobal(cx->global())) {
return true;
}
if (templateShape->slotSpan() == 0) {
return true;
}
UnboxedLayout::PropertyVector properties;
if (!properties.appendN(UnboxedLayout::Property(),
templateShape->slotSpan())) {
return false;
}
size_t objectCount = 0;
for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
JSObject* obj = objects->get(i);
if (!obj) {
continue;
}
if (obj->isSingleton() || obj->group() != group) {
return true;
}
objectCount++;
if (!CombinePlainObjectProperties(&obj->as<PlainObject>(), templateShape,
properties)) {
return true;
}
}
size_t layoutSize = 0;
if (objectCount <= 1) {
return true;
}
for (size_t i = 0; i < templateShape->slotSpan(); i++) {
if (UnboxedTypeSize(properties[i].type) == 0) {
return true;
}
}
for (Shape::Range<NoGC> r(templateShape); !r.empty(); r.popFront()) {
jsid id = r.front().propid();
uint32_t dummy;
if (!JSID_IS_ATOM(id) || JSID_TO_ATOM(id)->isIndex(&dummy)) {
return true;
}
}
ObjectGroupRealm& realm = ObjectGroupRealm::get(group);
layoutSize = ComputePlainObjectLayout(cx, realm, templateShape, properties);
if (UnboxedPlainObject::offsetOfData() + layoutSize >
JSObject::MAX_BYTE_SIZE) {
return true;
}
UniquePtr<UnboxedLayout>& layout = enter.unboxedLayoutToCleanUp;
MOZ_ASSERT(!layout);
layout = group->zone()->make_unique<UnboxedLayout>(group->zone());
if (!layout) {
return false;
}
if (!layout->initProperties(properties, layoutSize)) {
return false;
}
realm.unboxedLayouts.insertFront(layout.get());
if (!SetLayoutTraceList(cx, layout.get())) {
return false;
}
Shape* newShape = EmptyShape::getInitialShape(cx, &UnboxedPlainObject::class_,
group->proto(), 0);
if (!newShape) {
cx->recoverFromOutOfMemory();
return false;
}
Rooted<GCVector<Value>> values(cx, GCVector<Value>(cx));
for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
JSObject* obj = objects->get(i);
if (!obj) {
continue;
}
if (!GetValuesFromPreliminaryPlainObject(&obj->as<PlainObject>(),
&values)) {
cx->recoverFromOutOfMemory();
return false;
}
}
if (TypeNewScript* newScript = group->newScript(sweep)) {
layout->setNewScript(newScript);
}
for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
if (JSObject* obj = objects->get(i)) {
obj->as<NativeObject>().setLastPropertyMakeNonNative(newShape);
}
}
group->setClasp(&UnboxedPlainObject::class_);
group->setUnboxedLayout(layout.release());
size_t valueCursor = 0;
for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
JSObject* obj = objects->get(i);
if (!obj) {
continue;
}
obj->as<UnboxedPlainObject>().fillAfterConvert(cx, values, &valueCursor);
}
MOZ_ASSERT(valueCursor == values.length());
return true;
}