/* -*- 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/. */ #ifndef vm_UnboxedObject_h #define vm_UnboxedObject_h #include "jsgc.h" #include "jsobj.h" #include "vm/Runtime.h" #include "vm/TypeInference.h" namespace js { // Memory required for an unboxed value of a given type. Returns zero for types // which can't be used for unboxed objects. static inline size_t UnboxedTypeSize(JSValueType type) { switch (type) { case JSVAL_TYPE_BOOLEAN: return 1; case JSVAL_TYPE_INT32: return 4; case JSVAL_TYPE_DOUBLE: return 8; case JSVAL_TYPE_STRING: return sizeof(void*); case JSVAL_TYPE_OBJECT: return sizeof(void*); default: return 0; } } static inline bool UnboxedTypeNeedsPreBarrier(JSValueType type) { return type == JSVAL_TYPE_STRING || type == JSVAL_TYPE_OBJECT; } static inline bool UnboxedTypeNeedsPostBarrier(JSValueType type) { return type == JSVAL_TYPE_OBJECT; } // Class tracking information specific to unboxed objects. class UnboxedLayout : public mozilla::LinkedListElement { public: struct Property { PropertyName* name; uint32_t offset; JSValueType type; Property() : name(nullptr), offset(UINT32_MAX), type(JSVAL_TYPE_MAGIC) {} }; typedef Vector PropertyVector; private: // If objects in this group have ever been converted to native objects, // these store the corresponding native group and initial shape for such // objects. Type information for this object is reflected in nativeGroup. GCPtrObjectGroup nativeGroup_; GCPtrShape nativeShape_; // Any script/pc which the associated group is created for. GCPtrScript allocationScript_; jsbytecode* allocationPc_; // If nativeGroup is set and this object originally had a TypeNewScript or // was keyed to an allocation site, this points to the group which replaced // this one. This link is only needed to keep the replacement group from // being GC'ed. If it were GC'ed and a new one regenerated later, that new // group might have a different allocation kind from this group. GCPtrObjectGroup replacementGroup_; // The following members are only used for unboxed plain objects. // All properties on objects with this layout, in enumeration order. PropertyVector properties_; // Byte size of the data for objects with this layout. size_t size_; // Any 'new' script information associated with this layout. TypeNewScript* newScript_; // List for use in tracing objects with this layout. This has the same // structure as the trace list on a TypeDescr. int32_t* traceList_; // If this layout has been used to construct script or JSON constant // objects, this code might be filled in to more quickly fill in objects // from an array of values. GCPtrJitCode constructorCode_; // The following members are only used for unboxed arrays. // The type of array elements. JSValueType elementType_; public: UnboxedLayout() : nativeGroup_(nullptr), nativeShape_(nullptr), allocationScript_(nullptr), allocationPc_(nullptr), replacementGroup_(nullptr), size_(0), newScript_(nullptr), traceList_(nullptr), constructorCode_(nullptr), elementType_(JSVAL_TYPE_MAGIC) {} bool initProperties(const PropertyVector& properties, size_t size) { size_ = size; return properties_.appendAll(properties); } void initArray(JSValueType elementType) { elementType_ = elementType; } ~UnboxedLayout() { if (newScript_) newScript_->clear(); js_delete(newScript_); js_free(traceList_); nativeGroup_.init(nullptr); nativeShape_.init(nullptr); replacementGroup_.init(nullptr); constructorCode_.init(nullptr); } bool isArray() const { return elementType_ != JSVAL_TYPE_MAGIC; } void detachFromCompartment(); const PropertyVector& properties() const { return properties_; } TypeNewScript* newScript() const { return newScript_; } void setNewScript(TypeNewScript* newScript, bool writeBarrier = true); JSScript* allocationScript() const { return allocationScript_; } jsbytecode* allocationPc() const { return allocationPc_; } void setAllocationSite(JSScript* script, jsbytecode* pc) { allocationScript_ = script; allocationPc_ = pc; } const int32_t* traceList() const { return traceList_; } void setTraceList(int32_t* traceList) { traceList_ = traceList; } const Property* lookup(JSAtom* atom) const { for (size_t i = 0; i < properties_.length(); i++) { if (properties_[i].name == atom) return &properties_[i]; } return nullptr; } const Property* lookup(jsid id) const { if (JSID_IS_STRING(id)) return lookup(JSID_TO_ATOM(id)); return nullptr; } size_t size() const { return size_; } ObjectGroup* nativeGroup() const { return nativeGroup_; } Shape* nativeShape() const { return nativeShape_; } jit::JitCode* constructorCode() const { return constructorCode_; } void setConstructorCode(jit::JitCode* code) { constructorCode_ = code; } JSValueType elementType() const { return elementType_; } inline gc::AllocKind getAllocKind() const; void trace(JSTracer* trc); size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); static bool makeNativeGroup(JSContext* cx, ObjectGroup* group); static bool makeConstructorCode(JSContext* cx, HandleObjectGroup group); }; // Class for expando objects holding extra properties given to an unboxed plain // object. These objects behave identically to normal native plain objects, and // have a separate Class to distinguish them for memory usage reporting. class UnboxedExpandoObject : public NativeObject { public: static const Class class_; }; // Class for a plain object using an unboxed representation. The physical // layout of these objects is identical to that of an InlineTypedObject, though // these objects use an UnboxedLayout instead of a TypeDescr to keep track of // how their properties are stored. class UnboxedPlainObject : public JSObject { // Optional object which stores extra properties on this object. This is // not automatically barriered to avoid problems if the object is converted // to a native. See ensureExpando(). UnboxedExpandoObject* expando_; // Start of the inline data, which immediately follows the group and extra properties. uint8_t data_[1]; public: static const Class class_; static bool obj_lookupProperty(JSContext* cx, HandleObject obj, HandleId id, MutableHandleObject objp, MutableHandleShape propp); static bool obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id, Handle desc, ObjectOpResult& result); static bool obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp); static bool obj_getProperty(JSContext* cx, HandleObject obj, HandleValue receiver, HandleId id, MutableHandleValue vp); static bool obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v, HandleValue receiver, ObjectOpResult& result); static bool obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id, MutableHandle desc); static bool obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id, ObjectOpResult& result); static bool obj_enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties, bool enumerableOnly); static bool obj_watch(JSContext* cx, HandleObject obj, HandleId id, HandleObject callable); inline const UnboxedLayout& layout() const; const UnboxedLayout& layoutDontCheckGeneration() const { return group()->unboxedLayoutDontCheckGeneration(); } uint8_t* data() { return &data_[0]; } UnboxedExpandoObject* maybeExpando() const { return expando_; } void initExpando() { expando_ = nullptr; } // For use during GC. JSObject** addressOfExpando() { return reinterpret_cast(&expando_); } bool containsUnboxedOrExpandoProperty(ExclusiveContext* cx, jsid id) const; static UnboxedExpandoObject* ensureExpando(JSContext* cx, Handle obj); bool setValue(ExclusiveContext* cx, const UnboxedLayout::Property& property, const Value& v); Value getValue(const UnboxedLayout::Property& property, bool maybeUninitialized = false); static bool convertToNative(JSContext* cx, JSObject* obj); static UnboxedPlainObject* create(ExclusiveContext* cx, HandleObjectGroup group, NewObjectKind newKind); static JSObject* createWithProperties(ExclusiveContext* cx, HandleObjectGroup group, NewObjectKind newKind, IdValuePair* properties); void fillAfterConvert(ExclusiveContext* cx, Handle> values, size_t* valueCursor); static void trace(JSTracer* trc, JSObject* object); static size_t offsetOfExpando() { return offsetof(UnboxedPlainObject, expando_); } static size_t offsetOfData() { return offsetof(UnboxedPlainObject, data_[0]); } }; // Try to construct an UnboxedLayout for each of the preliminary objects, // provided they all match the template shape. If successful, converts the // preliminary objects and their group to the new unboxed representation. bool TryConvertToUnboxedLayout(ExclusiveContext* cx, AutoEnterAnalysis& enter, Shape* templateShape, ObjectGroup* group, PreliminaryObjectArray* objects); inline gc::AllocKind UnboxedLayout::getAllocKind() const { MOZ_ASSERT(size()); return gc::GetGCObjectKindForBytes(UnboxedPlainObject::offsetOfData() + size()); } // Class for an array object using an unboxed representation. class UnboxedArrayObject : public JSObject { // Elements pointer for the object. uint8_t* elements_; // The nominal array length. This always fits in an int32_t. uint32_t length_; // Value indicating the allocated capacity and initialized length of the // array. The top CapacityBits bits are an index into CapacityArray, which // indicates the elements capacity. The low InitializedLengthBits store the // initialized length of the array. uint32_t capacityIndexAndInitializedLength_; // If the elements are inline, they will point here. uint8_t inlineElements_[1]; public: static const uint32_t CapacityBits = 6; static const uint32_t CapacityShift = 26; static const uint32_t CapacityMask = uint32_t(-1) << CapacityShift; static const uint32_t InitializedLengthMask = (1 << CapacityShift) - 1; static const uint32_t MaximumCapacity = InitializedLengthMask; static const uint32_t MinimumDynamicCapacity = 8; static const uint32_t CapacityArray[]; // Capacity index which indicates the array's length is also its capacity. static const uint32_t CapacityMatchesLengthIndex = 0; private: static inline uint32_t computeCapacity(uint32_t index, uint32_t length) { if (index == CapacityMatchesLengthIndex) return length; return CapacityArray[index]; } static uint32_t chooseCapacityIndex(uint32_t capacity, uint32_t length); static uint32_t exactCapacityIndex(uint32_t capacity); public: static const Class class_; static bool obj_lookupProperty(JSContext* cx, HandleObject obj, HandleId id, MutableHandleObject objp, MutableHandleShape propp); static bool obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id, Handle desc, ObjectOpResult& result); static bool obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp); static bool obj_getProperty(JSContext* cx, HandleObject obj, HandleValue receiver, HandleId id, MutableHandleValue vp); static bool obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v, HandleValue receiver, ObjectOpResult& result); static bool obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id, MutableHandle desc); static bool obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id, ObjectOpResult& result); static bool obj_enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties, bool enumerableOnly); static bool obj_watch(JSContext* cx, HandleObject obj, HandleId id, HandleObject callable); inline const UnboxedLayout& layout() const; const UnboxedLayout& layoutDontCheckGeneration() const { return group()->unboxedLayoutDontCheckGeneration(); } JSValueType elementType() const { return layoutDontCheckGeneration().elementType(); } uint32_t elementSize() const { return UnboxedTypeSize(elementType()); } static bool convertToNative(JSContext* cx, JSObject* obj); static UnboxedArrayObject* create(ExclusiveContext* cx, HandleObjectGroup group, uint32_t length, NewObjectKind newKind, uint32_t maxLength = MaximumCapacity); static bool convertToNativeWithGroup(ExclusiveContext* cx, JSObject* obj, ObjectGroup* group, Shape* shape); bool convertInt32ToDouble(ExclusiveContext* cx, ObjectGroup* group); void fillAfterConvert(ExclusiveContext* cx, Handle> values, size_t* valueCursor); static void trace(JSTracer* trc, JSObject* object); static void objectMoved(JSObject* obj, const JSObject* old); static void finalize(FreeOp* fop, JSObject* obj); static size_t objectMovedDuringMinorGC(JSTracer* trc, JSObject* dst, JSObject* src, gc::AllocKind allocKind); uint8_t* elements() { return elements_; } bool hasInlineElements() const { return elements_ == &inlineElements_[0]; } uint32_t length() const { return length_; } uint32_t initializedLength() const { return capacityIndexAndInitializedLength_ & InitializedLengthMask; } uint32_t capacityIndex() const { return (capacityIndexAndInitializedLength_ & CapacityMask) >> CapacityShift; } uint32_t capacity() const { return computeCapacity(capacityIndex(), length()); } bool containsProperty(ExclusiveContext* cx, jsid id); bool setElement(ExclusiveContext* cx, size_t index, const Value& v); bool initElement(ExclusiveContext* cx, size_t index, const Value& v); void initElementNoTypeChange(size_t index, const Value& v); Value getElement(size_t index); template inline bool setElementSpecific(ExclusiveContext* cx, size_t index, const Value& v); template inline void setElementNoTypeChangeSpecific(size_t index, const Value& v); template inline bool initElementSpecific(ExclusiveContext* cx, size_t index, const Value& v); template inline void initElementNoTypeChangeSpecific(size_t index, const Value& v); template inline Value getElementSpecific(size_t index); template inline void triggerPreBarrier(size_t index); bool growElements(ExclusiveContext* cx, size_t cap); void shrinkElements(ExclusiveContext* cx, size_t cap); static uint32_t offsetOfElements() { return offsetof(UnboxedArrayObject, elements_); } static uint32_t offsetOfLength() { return offsetof(UnboxedArrayObject, length_); } static uint32_t offsetOfCapacityIndexAndInitializedLength() { return offsetof(UnboxedArrayObject, capacityIndexAndInitializedLength_); } static uint32_t offsetOfInlineElements() { return offsetof(UnboxedArrayObject, inlineElements_); } void setLengthInt32(uint32_t length) { MOZ_ASSERT(length <= INT32_MAX); length_ = length; } inline void setLength(ExclusiveContext* cx, uint32_t len); inline void setInitializedLength(uint32_t initlen); inline void setInitializedLengthNoBarrier(uint32_t initlen) { MOZ_ASSERT(initlen <= InitializedLengthMask); capacityIndexAndInitializedLength_ = (capacityIndexAndInitializedLength_ & CapacityMask) | initlen; } private: void setInlineElements() { elements_ = &inlineElements_[0]; } void setCapacityIndex(uint32_t index) { MOZ_ASSERT(index <= (CapacityMask >> CapacityShift)); capacityIndexAndInitializedLength_ = (index << CapacityShift) | initializedLength(); } }; } // namespace js namespace JS { template <> struct DeletePolicy : public js::GCManagedDeletePolicy {}; } /* namespace JS */ #endif /* vm_UnboxedObject_h */