diff options
Diffstat (limited to 'js/src/vm/UnboxedObject.cpp')
-rw-r--r-- | js/src/vm/UnboxedObject.cpp | 946 |
1 files changed, 946 insertions, 0 deletions
diff --git a/js/src/vm/UnboxedObject.cpp b/js/src/vm/UnboxedObject.cpp new file mode 100644 index 0000000000..2e017ca3bd --- /dev/null +++ b/js/src/vm/UnboxedObject.cpp @@ -0,0 +1,946 @@ +/* -*- 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); + + // 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()) { + 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, &PlainObject::class_, proto); + if (!replacementGroup) + return false; + + PlainObject* templateObject = &script->getObject(pc)->as<PlainObject>(); + replacementGroup->addDefiniteProperties(cx, templateObject->lastProperty()); + + cx->compartment()->objectGroups.replaceAllocationSiteGroup(script, pc, JSProto_Object, + 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 = gc::GetGCKindSlots(layout.getAllocKind()); + + RootedShape shape(cx, EmptyShape::getInitialShape(cx, &PlainObject::class_, proto, nfixed, 0)); + if (!shape) + return false; + + // 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, &PlainObject::class_, 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. + 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 +}; + +///////////////////////////////////////////////////////////////////// +// API +///////////////////////////////////////////////////////////////////// + +static inline Value +NextValue(Handle<GCVector<Value>> values, size_t* valueCursor) +{ + return 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))); +} |