michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef vm_ObjectImpl_h michael@0: #define vm_ObjectImpl_h michael@0: michael@0: #include "mozilla/Assertions.h" michael@0: #include "mozilla/Attributes.h" michael@0: michael@0: #include michael@0: michael@0: #include "jsfriendapi.h" michael@0: #include "jsinfer.h" michael@0: #include "NamespaceImports.h" michael@0: michael@0: #include "gc/Barrier.h" michael@0: #include "gc/Heap.h" michael@0: #include "gc/Marking.h" michael@0: #include "js/Value.h" michael@0: #include "vm/NumericConversions.h" michael@0: #include "vm/Shape.h" michael@0: #include "vm/String.h" michael@0: michael@0: namespace js { michael@0: michael@0: class ObjectImpl; michael@0: class Nursery; michael@0: class Shape; michael@0: michael@0: /* michael@0: * To really poison a set of values, using 'magic' or 'undefined' isn't good michael@0: * enough since often these will just be ignored by buggy code (see bug 629974) michael@0: * in debug builds and crash in release builds. Instead, we use a safe-for-crash michael@0: * pointer. michael@0: */ michael@0: static MOZ_ALWAYS_INLINE void michael@0: Debug_SetValueRangeToCrashOnTouch(Value *beg, Value *end) michael@0: { michael@0: #ifdef DEBUG michael@0: for (Value *v = beg; v != end; ++v) michael@0: v->setObject(*reinterpret_cast(0x42)); michael@0: #endif michael@0: } michael@0: michael@0: static MOZ_ALWAYS_INLINE void michael@0: Debug_SetValueRangeToCrashOnTouch(Value *vec, size_t len) michael@0: { michael@0: #ifdef DEBUG michael@0: Debug_SetValueRangeToCrashOnTouch(vec, vec + len); michael@0: #endif michael@0: } michael@0: michael@0: static MOZ_ALWAYS_INLINE void michael@0: Debug_SetValueRangeToCrashOnTouch(HeapValue *vec, size_t len) michael@0: { michael@0: #ifdef DEBUG michael@0: Debug_SetValueRangeToCrashOnTouch((Value *) vec, len); michael@0: #endif michael@0: } michael@0: michael@0: static MOZ_ALWAYS_INLINE void michael@0: Debug_SetSlotRangeToCrashOnTouch(HeapSlot *vec, uint32_t len) michael@0: { michael@0: #ifdef DEBUG michael@0: Debug_SetValueRangeToCrashOnTouch((Value *) vec, len); michael@0: #endif michael@0: } michael@0: michael@0: static MOZ_ALWAYS_INLINE void michael@0: Debug_SetSlotRangeToCrashOnTouch(HeapSlot *begin, HeapSlot *end) michael@0: { michael@0: #ifdef DEBUG michael@0: Debug_SetValueRangeToCrashOnTouch((Value *) begin, end - begin); michael@0: #endif michael@0: } michael@0: michael@0: class ArrayObject; michael@0: michael@0: /* michael@0: * ES6 20130308 draft 8.4.2.4 ArraySetLength. michael@0: * michael@0: * |id| must be "length", |attrs| are the attributes to be used for the newly- michael@0: * changed length property, |value| is the value for the new length, and michael@0: * |setterIsStrict| indicates whether invalid changes will cause a TypeError michael@0: * to be thrown. michael@0: */ michael@0: template michael@0: extern bool michael@0: ArraySetLength(typename ExecutionModeTraits::ContextType cx, michael@0: Handle obj, HandleId id, michael@0: unsigned attrs, HandleValue value, bool setterIsStrict); michael@0: michael@0: /* michael@0: * Elements header used for all objects. The elements component of such objects michael@0: * offers an efficient representation for all or some of the indexed properties michael@0: * of the object, using a flat array of Values rather than a shape hierarchy michael@0: * stored in the object's slots. This structure is immediately followed by an michael@0: * array of elements, with the elements member in an object pointing to the michael@0: * beginning of that array (the end of this structure). michael@0: * See below for usage of this structure. michael@0: * michael@0: * The sets of properties represented by an object's elements and slots michael@0: * are disjoint. The elements contain only indexed properties, while the slots michael@0: * can contain both named and indexed properties; any indexes in the slots are michael@0: * distinct from those in the elements. If isIndexed() is false for an object, michael@0: * all indexed properties (if any) are stored in the dense elements. michael@0: * michael@0: * Indexes will be stored in the object's slots instead of its elements in michael@0: * the following case: michael@0: * - there are more than MIN_SPARSE_INDEX slots total and the load factor michael@0: * (COUNT / capacity) is less than 0.25 michael@0: * - a property is defined that has non-default property attributes. michael@0: * michael@0: * We track these pieces of metadata for dense elements: michael@0: * - The length property as a uint32_t, accessible for array objects with michael@0: * ArrayObject::{length,setLength}(). This is unused for non-arrays. michael@0: * - The number of element slots (capacity), gettable with michael@0: * getDenseElementsCapacity(). michael@0: * - The array's initialized length, accessible with michael@0: * getDenseElementsInitializedLength(). michael@0: * michael@0: * Holes in the array are represented by MagicValue(JS_ELEMENTS_HOLE) values. michael@0: * These indicate indexes which are not dense properties of the array. The michael@0: * property may, however, be held by the object's properties. michael@0: * michael@0: * The capacity and length of an object's elements are almost entirely michael@0: * unrelated! In general the length may be greater than, less than, or equal michael@0: * to the capacity. The first case occurs with |new Array(100)|. The length michael@0: * is 100, but the capacity remains 0 (indices below length and above capacity michael@0: * must be treated as holes) until elements between capacity and length are michael@0: * set. The other two cases are common, depending upon the number of elements michael@0: * in an array and the underlying allocator used for element storage. michael@0: * michael@0: * The only case in which the capacity and length of an object's elements are michael@0: * related is when the object is an array with non-writable length. In this michael@0: * case the capacity is always less than or equal to the length. This permits michael@0: * JIT code to optimize away the check for non-writable length when assigning michael@0: * to possibly out-of-range elements: such code already has to check for michael@0: * |index < capacity|, and fallback code checks for non-writable length. michael@0: * michael@0: * The initialized length of an object specifies the number of elements that michael@0: * have been initialized. All elements above the initialized length are michael@0: * holes in the object, and the memory for all elements between the initialized michael@0: * length and capacity is left uninitialized. The initialized length is some michael@0: * value less than or equal to both the object's length and the object's michael@0: * capacity. michael@0: * michael@0: * There is flexibility in exactly the value the initialized length must hold, michael@0: * e.g. if an array has length 5, capacity 10, completely empty, it is valid michael@0: * for the initialized length to be any value between zero and 5, as long as michael@0: * the in memory values below the initialized length have been initialized with michael@0: * a hole value. However, in such cases we want to keep the initialized length michael@0: * as small as possible: if the object is known to have no hole values below michael@0: * its initialized length, then it is "packed" and can be accessed much faster michael@0: * by JIT code. michael@0: * michael@0: * Elements do not track property creation order, so enumerating the elements michael@0: * of an object does not necessarily visit indexes in the order they were michael@0: * created. michael@0: */ michael@0: class ObjectElements michael@0: { michael@0: public: michael@0: enum Flags { michael@0: CONVERT_DOUBLE_ELEMENTS = 0x1, michael@0: michael@0: // Present only if these elements correspond to an array with michael@0: // non-writable length; never present for non-arrays. michael@0: NONWRITABLE_ARRAY_LENGTH = 0x2 michael@0: }; michael@0: michael@0: private: michael@0: friend class ::JSObject; michael@0: friend class ObjectImpl; michael@0: friend class ArrayObject; michael@0: friend class Nursery; michael@0: michael@0: template michael@0: friend bool michael@0: ArraySetLength(typename ExecutionModeTraits::ContextType cx, michael@0: Handle obj, HandleId id, michael@0: unsigned attrs, HandleValue value, bool setterIsStrict); michael@0: michael@0: /* See Flags enum above. */ michael@0: uint32_t flags; michael@0: michael@0: /* michael@0: * Number of initialized elements. This is <= the capacity, and for arrays michael@0: * is <= the length. Memory for elements above the initialized length is michael@0: * uninitialized, but values between the initialized length and the proper michael@0: * length are conceptually holes. michael@0: */ michael@0: uint32_t initializedLength; michael@0: michael@0: /* Number of allocated slots. */ michael@0: uint32_t capacity; michael@0: michael@0: /* 'length' property of array objects, unused for other objects. */ michael@0: uint32_t length; michael@0: michael@0: void staticAsserts() { michael@0: static_assert(sizeof(ObjectElements) == VALUES_PER_HEADER * sizeof(Value), michael@0: "Elements size and values-per-Elements mismatch"); michael@0: } michael@0: michael@0: bool shouldConvertDoubleElements() const { michael@0: return flags & CONVERT_DOUBLE_ELEMENTS; michael@0: } michael@0: void setShouldConvertDoubleElements() { michael@0: flags |= CONVERT_DOUBLE_ELEMENTS; michael@0: } michael@0: void clearShouldConvertDoubleElements() { michael@0: flags &= ~CONVERT_DOUBLE_ELEMENTS; michael@0: } michael@0: bool hasNonwritableArrayLength() const { michael@0: return flags & NONWRITABLE_ARRAY_LENGTH; michael@0: } michael@0: void setNonwritableArrayLength() { michael@0: flags |= NONWRITABLE_ARRAY_LENGTH; michael@0: } michael@0: michael@0: public: michael@0: MOZ_CONSTEXPR ObjectElements(uint32_t capacity, uint32_t length) michael@0: : flags(0), initializedLength(0), capacity(capacity), length(length) michael@0: {} michael@0: michael@0: HeapSlot *elements() { michael@0: return reinterpret_cast(uintptr_t(this) + sizeof(ObjectElements)); michael@0: } michael@0: static ObjectElements * fromElements(HeapSlot *elems) { michael@0: return reinterpret_cast(uintptr_t(elems) - sizeof(ObjectElements)); michael@0: } michael@0: michael@0: static int offsetOfFlags() { michael@0: return int(offsetof(ObjectElements, flags)) - int(sizeof(ObjectElements)); michael@0: } michael@0: static int offsetOfInitializedLength() { michael@0: return int(offsetof(ObjectElements, initializedLength)) - int(sizeof(ObjectElements)); michael@0: } michael@0: static int offsetOfCapacity() { michael@0: return int(offsetof(ObjectElements, capacity)) - int(sizeof(ObjectElements)); michael@0: } michael@0: static int offsetOfLength() { michael@0: return int(offsetof(ObjectElements, length)) - int(sizeof(ObjectElements)); michael@0: } michael@0: michael@0: static bool ConvertElementsToDoubles(JSContext *cx, uintptr_t elements); michael@0: michael@0: static const size_t VALUES_PER_HEADER = 2; michael@0: }; michael@0: michael@0: /* Shared singleton for objects with no elements. */ michael@0: extern HeapSlot *const emptyObjectElements; michael@0: michael@0: struct Class; michael@0: struct GCMarker; michael@0: struct ObjectOps; michael@0: class Shape; michael@0: michael@0: class NewObjectCache; michael@0: class TaggedProto; michael@0: michael@0: inline Value michael@0: ObjectValue(ObjectImpl &obj); michael@0: michael@0: #ifdef DEBUG michael@0: static inline bool michael@0: IsObjectValueInCompartment(js::Value v, JSCompartment *comp); michael@0: #endif michael@0: michael@0: /* michael@0: * ObjectImpl specifies the internal implementation of an object. (In contrast michael@0: * JSObject specifies an "external" interface, at the conceptual level of that michael@0: * exposed in ECMAScript.) michael@0: * michael@0: * The |shape_| member stores the shape of the object, which includes the michael@0: * object's class and the layout of all its properties. michael@0: * michael@0: * The |type_| member stores the type of the object, which contains its michael@0: * prototype object and the possible types of its properties. michael@0: * michael@0: * The rest of the object stores its named properties and indexed elements. michael@0: * These are stored separately from one another. Objects are followed by a michael@0: * variable-sized array of values for inline storage, which may be used by michael@0: * either properties of native objects (fixed slots), by elements (fixed michael@0: * elements), or by other data for certain kinds of objects, such as michael@0: * ArrayBufferObjects and TypedArrayObjects. michael@0: * michael@0: * Two native objects with the same shape are guaranteed to have the same michael@0: * number of fixed slots. michael@0: * michael@0: * Named property storage can be split between fixed slots and a dynamically michael@0: * allocated array (the slots member). For an object with N fixed slots, shapes michael@0: * with slots [0..N-1] are stored in the fixed slots, and the remainder are michael@0: * stored in the dynamic array. If all properties fit in the fixed slots, the michael@0: * 'slots' member is nullptr. michael@0: * michael@0: * Elements are indexed via the 'elements' member. This member can point to michael@0: * either the shared emptyObjectElements singleton, into the inline value array michael@0: * (the address of the third value, to leave room for a ObjectElements header; michael@0: * in this case numFixedSlots() is zero) or to a dynamically allocated array. michael@0: * michael@0: * Only certain combinations of slots and elements storage are possible. michael@0: * michael@0: * - For native objects, slots and elements may both be non-empty. The michael@0: * slots may be either names or indexes; no indexed property will be in both michael@0: * the slots and elements. michael@0: * michael@0: * - For non-native objects, slots and elements are both empty. michael@0: * michael@0: * The members of this class are currently protected; in the long run this will michael@0: * will change so that some members are private, and only certain methods that michael@0: * act upon them will be protected. michael@0: */ michael@0: class ObjectImpl : public gc::BarrieredCell michael@0: { michael@0: friend Zone *js::gc::BarrieredCell::zone() const; michael@0: friend Zone *js::gc::BarrieredCell::zoneFromAnyThread() const; michael@0: michael@0: protected: michael@0: /* michael@0: * Shape of the object, encodes the layout of the object's properties and michael@0: * all other information about its structure. See vm/Shape.h. michael@0: */ michael@0: HeapPtrShape shape_; michael@0: michael@0: /* michael@0: * The object's type and prototype. For objects with the LAZY_TYPE flag michael@0: * set, this is the prototype's default 'new' type and can only be used michael@0: * to get that prototype. michael@0: */ michael@0: HeapPtrTypeObject type_; michael@0: michael@0: HeapSlot *slots; /* Slots for object properties. */ michael@0: HeapSlot *elements; /* Slots for object elements. */ michael@0: michael@0: friend bool michael@0: ArraySetLength(JSContext *cx, Handle obj, HandleId id, unsigned attrs, michael@0: HandleValue value, bool setterIsStrict); michael@0: michael@0: private: michael@0: static void staticAsserts() { michael@0: static_assert(sizeof(ObjectImpl) == sizeof(shadow::Object), michael@0: "shadow interface must match actual implementation"); michael@0: static_assert(sizeof(ObjectImpl) % sizeof(Value) == 0, michael@0: "fixed slots after an object must be aligned"); michael@0: michael@0: static_assert(offsetof(ObjectImpl, shape_) == offsetof(shadow::Object, shape), michael@0: "shadow shape must match actual shape"); michael@0: static_assert(offsetof(ObjectImpl, type_) == offsetof(shadow::Object, type), michael@0: "shadow type must match actual type"); michael@0: static_assert(offsetof(ObjectImpl, slots) == offsetof(shadow::Object, slots), michael@0: "shadow slots must match actual slots"); michael@0: static_assert(offsetof(ObjectImpl, elements) == offsetof(shadow::Object, _1), michael@0: "shadow placeholder must match actual elements"); michael@0: } michael@0: michael@0: JSObject * asObjectPtr() { return reinterpret_cast(this); } michael@0: const JSObject * asObjectPtr() const { return reinterpret_cast(this); } michael@0: michael@0: friend inline Value ObjectValue(ObjectImpl &obj); michael@0: michael@0: /* These functions are public, and they should remain public. */ michael@0: michael@0: public: michael@0: TaggedProto getTaggedProto() const { michael@0: return type_->proto(); michael@0: } michael@0: michael@0: bool hasTenuredProto() const; michael@0: michael@0: const Class *getClass() const { michael@0: return type_->clasp(); michael@0: } michael@0: michael@0: static inline bool michael@0: isExtensible(ExclusiveContext *cx, Handle obj, bool *extensible); michael@0: michael@0: // Indicates whether a non-proxy is extensible. Don't call on proxies! michael@0: // This method really shouldn't exist -- but there are a few internal michael@0: // places that want it (JITs and the like), and it'd be a pain to mark them michael@0: // all as friends. michael@0: bool nonProxyIsExtensible() const { michael@0: MOZ_ASSERT(!isProxy()); michael@0: michael@0: // [[Extensible]] for ordinary non-proxy objects is an object flag. michael@0: return !lastProperty()->hasObjectFlag(BaseShape::NOT_EXTENSIBLE); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: bool isProxy() const; michael@0: #endif michael@0: michael@0: // Attempt to change the [[Extensible]] bit on |obj| to false. Callers michael@0: // must ensure that |obj| is currently extensible before calling this! michael@0: static bool michael@0: preventExtensions(JSContext *cx, Handle obj); michael@0: michael@0: HeapSlotArray getDenseElements() { michael@0: JS_ASSERT(isNative()); michael@0: return HeapSlotArray(elements); michael@0: } michael@0: const Value &getDenseElement(uint32_t idx) { michael@0: JS_ASSERT(isNative()); michael@0: MOZ_ASSERT(idx < getDenseInitializedLength()); michael@0: return elements[idx]; michael@0: } michael@0: bool containsDenseElement(uint32_t idx) { michael@0: JS_ASSERT(isNative()); michael@0: return idx < getDenseInitializedLength() && !elements[idx].isMagic(JS_ELEMENTS_HOLE); michael@0: } michael@0: uint32_t getDenseInitializedLength() { michael@0: JS_ASSERT(getClass()->isNative()); michael@0: return getElementsHeader()->initializedLength; michael@0: } michael@0: uint32_t getDenseCapacity() { michael@0: JS_ASSERT(getClass()->isNative()); michael@0: return getElementsHeader()->capacity; michael@0: } michael@0: michael@0: protected: michael@0: #ifdef DEBUG michael@0: void checkShapeConsistency(); michael@0: #else michael@0: void checkShapeConsistency() { } michael@0: #endif michael@0: michael@0: Shape * michael@0: replaceWithNewEquivalentShape(ThreadSafeContext *cx, michael@0: Shape *existingShape, Shape *newShape = nullptr); michael@0: michael@0: enum GenerateShape { michael@0: GENERATE_NONE, michael@0: GENERATE_SHAPE michael@0: }; michael@0: michael@0: bool setFlag(ExclusiveContext *cx, /*BaseShape::Flag*/ uint32_t flag, michael@0: GenerateShape generateShape = GENERATE_NONE); michael@0: bool clearFlag(ExclusiveContext *cx, /*BaseShape::Flag*/ uint32_t flag); michael@0: michael@0: bool toDictionaryMode(ThreadSafeContext *cx); michael@0: michael@0: private: michael@0: friend class Nursery; michael@0: michael@0: /* michael@0: * Get internal pointers to the range of values starting at start and michael@0: * running for length. michael@0: */ michael@0: void getSlotRangeUnchecked(uint32_t start, uint32_t length, michael@0: HeapSlot **fixedStart, HeapSlot **fixedEnd, michael@0: HeapSlot **slotsStart, HeapSlot **slotsEnd) michael@0: { michael@0: MOZ_ASSERT(start + length >= start); michael@0: michael@0: uint32_t fixed = numFixedSlots(); michael@0: if (start < fixed) { michael@0: if (start + length < fixed) { michael@0: *fixedStart = &fixedSlots()[start]; michael@0: *fixedEnd = &fixedSlots()[start + length]; michael@0: *slotsStart = *slotsEnd = nullptr; michael@0: } else { michael@0: uint32_t localCopy = fixed - start; michael@0: *fixedStart = &fixedSlots()[start]; michael@0: *fixedEnd = &fixedSlots()[start + localCopy]; michael@0: *slotsStart = &slots[0]; michael@0: *slotsEnd = &slots[length - localCopy]; michael@0: } michael@0: } else { michael@0: *fixedStart = *fixedEnd = nullptr; michael@0: *slotsStart = &slots[start - fixed]; michael@0: *slotsEnd = &slots[start - fixed + length]; michael@0: } michael@0: } michael@0: michael@0: void getSlotRange(uint32_t start, uint32_t length, michael@0: HeapSlot **fixedStart, HeapSlot **fixedEnd, michael@0: HeapSlot **slotsStart, HeapSlot **slotsEnd) michael@0: { michael@0: MOZ_ASSERT(slotInRange(start + length, SENTINEL_ALLOWED)); michael@0: getSlotRangeUnchecked(start, length, fixedStart, fixedEnd, slotsStart, slotsEnd); michael@0: } michael@0: michael@0: protected: michael@0: friend struct GCMarker; michael@0: friend class Shape; michael@0: friend class NewObjectCache; michael@0: michael@0: void invalidateSlotRange(uint32_t start, uint32_t length) { michael@0: #ifdef DEBUG michael@0: HeapSlot *fixedStart, *fixedEnd, *slotsStart, *slotsEnd; michael@0: getSlotRange(start, length, &fixedStart, &fixedEnd, &slotsStart, &slotsEnd); michael@0: Debug_SetSlotRangeToCrashOnTouch(fixedStart, fixedEnd); michael@0: Debug_SetSlotRangeToCrashOnTouch(slotsStart, slotsEnd); michael@0: #endif /* DEBUG */ michael@0: } michael@0: michael@0: void initializeSlotRange(uint32_t start, uint32_t count); michael@0: michael@0: /* michael@0: * Initialize a flat array of slots to this object at a start slot. The michael@0: * caller must ensure that are enough slots. michael@0: */ michael@0: void initSlotRange(uint32_t start, const Value *vector, uint32_t length); michael@0: michael@0: /* michael@0: * Copy a flat array of slots to this object at a start slot. Caller must michael@0: * ensure there are enough slots in this object. michael@0: */ michael@0: void copySlotRange(uint32_t start, const Value *vector, uint32_t length); michael@0: michael@0: #ifdef DEBUG michael@0: enum SentinelAllowed { michael@0: SENTINEL_NOT_ALLOWED, michael@0: SENTINEL_ALLOWED michael@0: }; michael@0: michael@0: /* michael@0: * Check that slot is in range for the object's allocated slots. michael@0: * If sentinelAllowed then slot may equal the slot capacity. michael@0: */ michael@0: bool slotInRange(uint32_t slot, SentinelAllowed sentinel = SENTINEL_NOT_ALLOWED) const; michael@0: #endif michael@0: michael@0: /* michael@0: * Minimum size for dynamically allocated slots in normal Objects. michael@0: * ArrayObjects don't use this limit and can have a lower slot capacity, michael@0: * since they normally don't have a lot of slots. michael@0: */ michael@0: static const uint32_t SLOT_CAPACITY_MIN = 8; michael@0: michael@0: HeapSlot *fixedSlots() const { michael@0: return reinterpret_cast(uintptr_t(this) + sizeof(ObjectImpl)); michael@0: } michael@0: michael@0: /* michael@0: * These functions are currently public for simplicity; in the long run michael@0: * it may make sense to make at least some of them private. michael@0: */ michael@0: michael@0: public: michael@0: Shape * lastProperty() const { michael@0: MOZ_ASSERT(shape_); michael@0: return shape_; michael@0: } michael@0: michael@0: bool generateOwnShape(ThreadSafeContext *cx, js::Shape *newShape = nullptr) { michael@0: return replaceWithNewEquivalentShape(cx, lastProperty(), newShape); michael@0: } michael@0: michael@0: JSCompartment *compartment() const { michael@0: return lastProperty()->base()->compartment(); michael@0: } michael@0: michael@0: bool isNative() const { michael@0: return lastProperty()->isNative(); michael@0: } michael@0: michael@0: types::TypeObject *type() const { michael@0: MOZ_ASSERT(!hasLazyType()); michael@0: return typeRaw(); michael@0: } michael@0: michael@0: types::TypeObject *typeRaw() const { michael@0: return type_; michael@0: } michael@0: michael@0: uint32_t numFixedSlots() const { michael@0: return reinterpret_cast(this)->numFixedSlots(); michael@0: } michael@0: michael@0: /* michael@0: * Whether this is the only object which has its specified type. This michael@0: * object will have its type constructed lazily as needed by analysis. michael@0: */ michael@0: bool hasSingletonType() const { michael@0: return !!type_->singleton(); michael@0: } michael@0: michael@0: /* michael@0: * Whether the object's type has not been constructed yet. If an object michael@0: * might have a lazy type, use getType() below, otherwise type(). michael@0: */ michael@0: bool hasLazyType() const { michael@0: return type_->lazy(); michael@0: } michael@0: michael@0: uint32_t slotSpan() const { michael@0: if (inDictionaryMode()) michael@0: return lastProperty()->base()->slotSpan(); michael@0: return lastProperty()->slotSpan(); michael@0: } michael@0: michael@0: /* Compute dynamicSlotsCount() for this object. */ michael@0: uint32_t numDynamicSlots() const { michael@0: return dynamicSlotsCount(numFixedSlots(), slotSpan(), getClass()); michael@0: } michael@0: michael@0: michael@0: Shape *nativeLookup(ExclusiveContext *cx, jsid id); michael@0: Shape *nativeLookup(ExclusiveContext *cx, PropertyName *name) { michael@0: return nativeLookup(cx, NameToId(name)); michael@0: } michael@0: michael@0: bool nativeContains(ExclusiveContext *cx, jsid id) { michael@0: return nativeLookup(cx, id) != nullptr; michael@0: } michael@0: bool nativeContains(ExclusiveContext *cx, PropertyName* name) { michael@0: return nativeLookup(cx, name) != nullptr; michael@0: } michael@0: bool nativeContains(ExclusiveContext *cx, Shape* shape) { michael@0: return nativeLookup(cx, shape->propid()) == shape; michael@0: } michael@0: michael@0: /* Contextless; can be called from parallel code. */ michael@0: Shape *nativeLookupPure(jsid id); michael@0: Shape *nativeLookupPure(PropertyName *name) { michael@0: return nativeLookupPure(NameToId(name)); michael@0: } michael@0: michael@0: bool nativeContainsPure(jsid id) { michael@0: return nativeLookupPure(id) != nullptr; michael@0: } michael@0: bool nativeContainsPure(PropertyName* name) { michael@0: return nativeContainsPure(NameToId(name)); michael@0: } michael@0: bool nativeContainsPure(Shape* shape) { michael@0: return nativeLookupPure(shape->propid()) == shape; michael@0: } michael@0: michael@0: const JSClass *getJSClass() const { michael@0: return Jsvalify(getClass()); michael@0: } michael@0: bool hasClass(const Class *c) const { michael@0: return getClass() == c; michael@0: } michael@0: const ObjectOps *getOps() const { michael@0: return &getClass()->ops; michael@0: } michael@0: michael@0: /* michael@0: * An object is a delegate if it is on another object's prototype or scope michael@0: * chain, and therefore the delegate might be asked implicitly to get or michael@0: * set a property on behalf of another object. Delegates may be accessed michael@0: * directly too, as may any object, but only those objects linked after the michael@0: * head of any prototype or scope chain are flagged as delegates. This michael@0: * definition helps to optimize shape-based property cache invalidation michael@0: * (see Purge{Scope,Proto}Chain in jsobj.cpp). michael@0: */ michael@0: bool isDelegate() const { michael@0: return lastProperty()->hasObjectFlag(BaseShape::DELEGATE); michael@0: } michael@0: michael@0: /* michael@0: * Return true if this object is a native one that has been converted from michael@0: * shared-immutable prototype-rooted shape storage to dictionary-shapes in michael@0: * a doubly-linked list. michael@0: */ michael@0: bool inDictionaryMode() const { michael@0: return lastProperty()->inDictionary(); michael@0: } michael@0: michael@0: const Value &getSlot(uint32_t slot) const { michael@0: MOZ_ASSERT(slotInRange(slot)); michael@0: uint32_t fixed = numFixedSlots(); michael@0: if (slot < fixed) michael@0: return fixedSlots()[slot]; michael@0: return slots[slot - fixed]; michael@0: } michael@0: michael@0: const HeapSlot *getSlotAddressUnchecked(uint32_t slot) const { michael@0: uint32_t fixed = numFixedSlots(); michael@0: if (slot < fixed) michael@0: return fixedSlots() + slot; michael@0: return slots + (slot - fixed); michael@0: } michael@0: michael@0: HeapSlot *getSlotAddressUnchecked(uint32_t slot) { michael@0: const ObjectImpl *obj = static_cast(this); michael@0: return const_cast(obj->getSlotAddressUnchecked(slot)); michael@0: } michael@0: michael@0: HeapSlot *getSlotAddress(uint32_t slot) { michael@0: /* michael@0: * This can be used to get the address of the end of the slots for the michael@0: * object, which may be necessary when fetching zero-length arrays of michael@0: * slots (e.g. for callObjVarArray). michael@0: */ michael@0: MOZ_ASSERT(slotInRange(slot, SENTINEL_ALLOWED)); michael@0: return getSlotAddressUnchecked(slot); michael@0: } michael@0: michael@0: const HeapSlot *getSlotAddress(uint32_t slot) const { michael@0: /* michael@0: * This can be used to get the address of the end of the slots for the michael@0: * object, which may be necessary when fetching zero-length arrays of michael@0: * slots (e.g. for callObjVarArray). michael@0: */ michael@0: MOZ_ASSERT(slotInRange(slot, SENTINEL_ALLOWED)); michael@0: return getSlotAddressUnchecked(slot); michael@0: } michael@0: michael@0: HeapSlot &getSlotRef(uint32_t slot) { michael@0: MOZ_ASSERT(slotInRange(slot)); michael@0: return *getSlotAddress(slot); michael@0: } michael@0: michael@0: const HeapSlot &getSlotRef(uint32_t slot) const { michael@0: MOZ_ASSERT(slotInRange(slot)); michael@0: return *getSlotAddress(slot); michael@0: } michael@0: michael@0: HeapSlot &nativeGetSlotRef(uint32_t slot) { michael@0: JS_ASSERT(isNative() && slot < slotSpan()); michael@0: return getSlotRef(slot); michael@0: } michael@0: const Value &nativeGetSlot(uint32_t slot) const { michael@0: JS_ASSERT(isNative() && slot < slotSpan()); michael@0: return getSlot(slot); michael@0: } michael@0: michael@0: void setSlot(uint32_t slot, const Value &value) { michael@0: MOZ_ASSERT(slotInRange(slot)); michael@0: MOZ_ASSERT(IsObjectValueInCompartment(value, compartment())); michael@0: getSlotRef(slot).set(this->asObjectPtr(), HeapSlot::Slot, slot, value); michael@0: } michael@0: michael@0: inline void setCrossCompartmentSlot(uint32_t slot, const Value &value) { michael@0: MOZ_ASSERT(slotInRange(slot)); michael@0: getSlotRef(slot).set(this->asObjectPtr(), HeapSlot::Slot, slot, value); michael@0: } michael@0: michael@0: void initSlot(uint32_t slot, const Value &value) { michael@0: MOZ_ASSERT(getSlot(slot).isUndefined()); michael@0: MOZ_ASSERT(slotInRange(slot)); michael@0: MOZ_ASSERT(IsObjectValueInCompartment(value, compartment())); michael@0: initSlotUnchecked(slot, value); michael@0: } michael@0: michael@0: void initCrossCompartmentSlot(uint32_t slot, const Value &value) { michael@0: MOZ_ASSERT(getSlot(slot).isUndefined()); michael@0: MOZ_ASSERT(slotInRange(slot)); michael@0: initSlotUnchecked(slot, value); michael@0: } michael@0: michael@0: void initSlotUnchecked(uint32_t slot, const Value &value) { michael@0: getSlotAddressUnchecked(slot)->init(this->asObjectPtr(), HeapSlot::Slot, slot, value); michael@0: } michael@0: michael@0: /* For slots which are known to always be fixed, due to the way they are allocated. */ michael@0: michael@0: HeapSlot &getFixedSlotRef(uint32_t slot) { michael@0: MOZ_ASSERT(slot < numFixedSlots()); michael@0: return fixedSlots()[slot]; michael@0: } michael@0: michael@0: const Value &getFixedSlot(uint32_t slot) const { michael@0: MOZ_ASSERT(slot < numFixedSlots()); michael@0: return fixedSlots()[slot]; michael@0: } michael@0: michael@0: void setFixedSlot(uint32_t slot, const Value &value) { michael@0: MOZ_ASSERT(slot < numFixedSlots()); michael@0: fixedSlots()[slot].set(this->asObjectPtr(), HeapSlot::Slot, slot, value); michael@0: } michael@0: michael@0: void initFixedSlot(uint32_t slot, const Value &value) { michael@0: MOZ_ASSERT(slot < numFixedSlots()); michael@0: fixedSlots()[slot].init(this->asObjectPtr(), HeapSlot::Slot, slot, value); michael@0: } michael@0: michael@0: /* michael@0: * Get the number of dynamic slots to allocate to cover the properties in michael@0: * an object with the given number of fixed slots and slot span. The slot michael@0: * capacity is not stored explicitly, and the allocated size of the slot michael@0: * array is kept in sync with this count. michael@0: */ michael@0: static uint32_t dynamicSlotsCount(uint32_t nfixed, uint32_t span, const Class *clasp); michael@0: michael@0: /* Memory usage functions. */ michael@0: size_t tenuredSizeOfThis() const { michael@0: return js::gc::Arena::thingSize(tenuredGetAllocKind()); michael@0: } michael@0: michael@0: /* Elements accessors. */ michael@0: michael@0: ObjectElements * getElementsHeader() const { michael@0: return ObjectElements::fromElements(elements); michael@0: } michael@0: michael@0: inline HeapSlot *fixedElements() const { michael@0: static_assert(2 * sizeof(Value) == sizeof(ObjectElements), michael@0: "when elements are stored inline, the first two " michael@0: "slots will hold the ObjectElements header"); michael@0: return &fixedSlots()[2]; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: bool canHaveNonEmptyElements(); michael@0: #endif michael@0: michael@0: void setFixedElements() { michael@0: JS_ASSERT(canHaveNonEmptyElements()); michael@0: this->elements = fixedElements(); michael@0: } michael@0: michael@0: inline bool hasDynamicElements() const { michael@0: /* michael@0: * Note: for objects with zero fixed slots this could potentially give michael@0: * a spurious 'true' result, if the end of this object is exactly michael@0: * aligned with the end of its arena and dynamic slots are allocated michael@0: * immediately afterwards. Such cases cannot occur for dense arrays michael@0: * (which have at least two fixed slots) and can only result in a leak. michael@0: */ michael@0: return !hasEmptyElements() && elements != fixedElements(); michael@0: } michael@0: michael@0: inline bool hasFixedElements() const { michael@0: return elements == fixedElements(); michael@0: } michael@0: michael@0: inline bool hasEmptyElements() const { michael@0: return elements == emptyObjectElements; michael@0: } michael@0: michael@0: /* michael@0: * Get a pointer to the unused data in the object's allocation immediately michael@0: * following this object, for use with objects which allocate a larger size michael@0: * class than they need and store non-elements data inline. michael@0: */ michael@0: inline void *fixedData(size_t nslots) const; michael@0: michael@0: /* GC support. */ michael@0: static ThingRootKind rootKind() { return THING_ROOT_OBJECT; } michael@0: michael@0: inline void privateWriteBarrierPre(void **oldval); michael@0: michael@0: void privateWriteBarrierPost(void **pprivate) { michael@0: #ifdef JSGC_GENERATIONAL michael@0: shadowRuntimeFromAnyThread()->gcStoreBufferPtr()->putCell(reinterpret_cast(pprivate)); michael@0: #endif michael@0: } michael@0: michael@0: void markChildren(JSTracer *trc); michael@0: michael@0: /* Private data accessors. */ michael@0: michael@0: inline void *&privateRef(uint32_t nfixed) const { /* XXX should be private, not protected! */ michael@0: /* michael@0: * The private pointer of an object can hold any word sized value. michael@0: * Private pointers are stored immediately after the last fixed slot of michael@0: * the object. michael@0: */ michael@0: MOZ_ASSERT(nfixed == numFixedSlots()); michael@0: MOZ_ASSERT(hasPrivate()); michael@0: HeapSlot *end = &fixedSlots()[nfixed]; michael@0: return *reinterpret_cast(end); michael@0: } michael@0: michael@0: bool hasPrivate() const { michael@0: return getClass()->hasPrivate(); michael@0: } michael@0: void *getPrivate() const { michael@0: return privateRef(numFixedSlots()); michael@0: } michael@0: void setPrivate(void *data) { michael@0: void **pprivate = &privateRef(numFixedSlots()); michael@0: privateWriteBarrierPre(pprivate); michael@0: *pprivate = data; michael@0: } michael@0: michael@0: void setPrivateGCThing(gc::Cell *cell) { michael@0: void **pprivate = &privateRef(numFixedSlots()); michael@0: privateWriteBarrierPre(pprivate); michael@0: *pprivate = reinterpret_cast(cell); michael@0: privateWriteBarrierPost(pprivate); michael@0: } michael@0: michael@0: void setPrivateUnbarriered(void *data) { michael@0: void **pprivate = &privateRef(numFixedSlots()); michael@0: *pprivate = data; michael@0: } michael@0: void initPrivate(void *data) { michael@0: privateRef(numFixedSlots()) = data; michael@0: } michael@0: michael@0: /* Access private data for an object with a known number of fixed slots. */ michael@0: inline void *getPrivate(uint32_t nfixed) const { michael@0: return privateRef(nfixed); michael@0: } michael@0: michael@0: /* GC Accessors */ michael@0: void setInitialSlots(HeapSlot *newSlots) { slots = newSlots; } michael@0: michael@0: /* JIT Accessors */ michael@0: static size_t offsetOfShape() { return offsetof(ObjectImpl, shape_); } michael@0: HeapPtrShape *addressOfShape() { return &shape_; } michael@0: michael@0: static size_t offsetOfType() { return offsetof(ObjectImpl, type_); } michael@0: HeapPtrTypeObject *addressOfType() { return &type_; } michael@0: michael@0: static size_t offsetOfElements() { return offsetof(ObjectImpl, elements); } michael@0: static size_t offsetOfFixedElements() { michael@0: return sizeof(ObjectImpl) + sizeof(ObjectElements); michael@0: } michael@0: michael@0: static size_t getFixedSlotOffset(size_t slot) { michael@0: return sizeof(ObjectImpl) + slot * sizeof(Value); michael@0: } michael@0: static size_t getPrivateDataOffset(size_t nfixed) { return getFixedSlotOffset(nfixed); } michael@0: static size_t offsetOfSlots() { return offsetof(ObjectImpl, slots); } michael@0: }; michael@0: michael@0: namespace gc { michael@0: michael@0: template <> michael@0: MOZ_ALWAYS_INLINE Zone * michael@0: BarrieredCell::zone() const michael@0: { michael@0: const ObjectImpl* obj = static_cast(this); michael@0: JS::Zone *zone = obj->shape_->zone(); michael@0: JS_ASSERT(CurrentThreadCanAccessZone(zone)); michael@0: return zone; michael@0: } michael@0: michael@0: template <> michael@0: MOZ_ALWAYS_INLINE Zone * michael@0: BarrieredCell::zoneFromAnyThread() const michael@0: { michael@0: const ObjectImpl* obj = static_cast(this); michael@0: return obj->shape_->zoneFromAnyThread(); michael@0: } michael@0: michael@0: // TypeScript::global uses 0x1 as a special value. michael@0: template<> michael@0: /* static */ inline bool michael@0: BarrieredCell::isNullLike(ObjectImpl *obj) michael@0: { michael@0: return IsNullTaggedPointer(obj); michael@0: } michael@0: michael@0: template<> michael@0: /* static */ inline void michael@0: BarrieredCell::writeBarrierPost(ObjectImpl *obj, void *addr) michael@0: { michael@0: #ifdef JSGC_GENERATIONAL michael@0: if (IsNullTaggedPointer(obj)) michael@0: return; michael@0: obj->shadowRuntimeFromAnyThread()->gcStoreBufferPtr()->putCell((Cell **)addr); michael@0: #endif michael@0: } michael@0: michael@0: template<> michael@0: /* static */ inline void michael@0: BarrieredCell::writeBarrierPostRelocate(ObjectImpl *obj, void *addr) michael@0: { michael@0: #ifdef JSGC_GENERATIONAL michael@0: obj->shadowRuntimeFromAnyThread()->gcStoreBufferPtr()->putRelocatableCell((Cell **)addr); michael@0: #endif michael@0: } michael@0: michael@0: template<> michael@0: /* static */ inline void michael@0: BarrieredCell::writeBarrierPostRemove(ObjectImpl *obj, void *addr) michael@0: { michael@0: #ifdef JSGC_GENERATIONAL michael@0: obj->shadowRuntimeFromAnyThread()->gcStoreBufferPtr()->removeRelocatableCell((Cell **)addr); michael@0: #endif michael@0: } michael@0: michael@0: } // namespace gc michael@0: michael@0: inline void michael@0: ObjectImpl::privateWriteBarrierPre(void **oldval) michael@0: { michael@0: #ifdef JSGC_INCREMENTAL michael@0: JS::shadow::Zone *shadowZone = this->shadowZoneFromAnyThread(); michael@0: if (shadowZone->needsBarrier()) { michael@0: if (*oldval && getClass()->trace) michael@0: getClass()->trace(shadowZone->barrierTracer(), this->asObjectPtr()); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: inline Value michael@0: ObjectValue(ObjectImpl &obj) michael@0: { michael@0: Value v; michael@0: v.setObject(*obj.asObjectPtr()); michael@0: return v; michael@0: } michael@0: michael@0: inline Handle michael@0: Downcast(Handle obj) michael@0: { michael@0: return Handle::fromMarkedLocation(reinterpret_cast(obj.address())); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: static inline bool michael@0: IsObjectValueInCompartment(js::Value v, JSCompartment *comp) michael@0: { michael@0: if (!v.isObject()) michael@0: return true; michael@0: return reinterpret_cast(&v.toObject())->compartment() == comp; michael@0: } michael@0: #endif michael@0: michael@0: } /* namespace js */ michael@0: michael@0: #endif /* vm_ObjectImpl_h */