/*
- * Copyright (C) 2008 Apple Inc. All rights reserved.
+ * Copyright (C) 2008, 2009, 2012, 2013 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
#ifndef Structure_h
#define Structure_h
-#include "Identifier.h"
+#include "ClassInfo.h"
+#include "IndexingType.h"
+#include "JSCJSValue.h"
+#include "JSCell.h"
#include "JSType.h"
-#include "JSValue.h"
-#include "PropertyMapHashTable.h"
-#include "StructureChain.h"
+#include "PropertyName.h"
+#include "PropertyNameArray.h"
+#include "PropertyOffset.h"
+#include "Protect.h"
+#include "StructureRareData.h"
#include "StructureTransitionTable.h"
-#include "TypeInfo.h"
-#include "UString.h"
+#include "JSTypeInfo.h"
+#include "Watchpoint.h"
#include <wtf/PassRefPtr.h>
#include <wtf/RefCounted.h>
+#include <wtf/text/StringImpl.h>
-#ifndef NDEBUG
-#define DUMP_PROPERTYMAP_STATS 0
-#else
-#define DUMP_PROPERTYMAP_STATS 0
-#endif
namespace JSC {
- class PropertyNameArray;
- class PropertyNameArrayData;
-
- class Structure : public RefCounted<Structure> {
- public:
- friend class JIT;
- static PassRefPtr<Structure> create(JSValuePtr prototype, const TypeInfo& typeInfo)
- {
- return adoptRef(new Structure(prototype, typeInfo));
- }
+class LLIntOffsetsExtractor;
+class PropertyNameArray;
+class PropertyNameArrayData;
+class PropertyTable;
+class StructureChain;
+class SlotVisitor;
+class JSString;
- static void startIgnoringLeaks();
- static void stopIgnoringLeaks();
+// The out-of-line property storage capacity to use when first allocating out-of-line
+// storage. Note that all objects start out without having any out-of-line storage;
+// this comes into play only on the first property store that exhausts inline storage.
+static const unsigned initialOutOfLineCapacity = 4;
- static void dumpStatistics();
+// The factor by which to grow out-of-line storage when it is exhausted, after the
+// initial allocation.
+static const unsigned outOfLineGrowthFactor = 2;
- static PassRefPtr<Structure> addPropertyTransition(Structure*, const Identifier& propertyName, unsigned attributes, size_t& offset);
- static PassRefPtr<Structure> addPropertyTransitionToExistingStructure(Structure*, const Identifier& propertyName, unsigned attributes, size_t& offset);
- static PassRefPtr<Structure> removePropertyTransition(Structure*, const Identifier& propertyName, size_t& offset);
- static PassRefPtr<Structure> changePrototypeTransition(Structure*, JSValuePtr prototype);
- static PassRefPtr<Structure> getterSetterTransition(Structure*);
- static PassRefPtr<Structure> toDictionaryTransition(Structure*);
- static PassRefPtr<Structure> fromDictionaryTransition(Structure*);
+class Structure : public JSCell {
+public:
+ friend class StructureTransitionTable;
- ~Structure();
+ typedef JSCell Base;
- void mark()
- {
- if (!m_prototype.marked())
- m_prototype.mark();
- }
+ static Structure* create(VM&, JSGlobalObject*, JSValue prototype, const TypeInfo&, const ClassInfo*, IndexingType = NonArray, unsigned inlineCapacity = 0);
- // These should be used with caution.
- size_t addPropertyWithoutTransition(const Identifier& propertyName, unsigned attributes);
- size_t removePropertyWithoutTransition(const Identifier& propertyName);
- void setPrototypeWithoutTransition(JSValuePtr prototype) { m_prototype = prototype; }
-
- bool isDictionary() const { return m_isDictionary; }
+protected:
+ void finishCreation(VM& vm)
+ {
+ Base::finishCreation(vm);
+ ASSERT(m_prototype);
+ ASSERT(m_prototype.isObject() || m_prototype.isNull());
+ }
- const TypeInfo& typeInfo() const { return m_typeInfo; }
+ void finishCreation(VM& vm, CreatingEarlyCellTag)
+ {
+ Base::finishCreation(vm, this, CreatingEarlyCell);
+ ASSERT(m_prototype);
+ ASSERT(m_prototype.isNull());
+ ASSERT(!vm.structureStructure);
+ }
- JSValuePtr storedPrototype() const { return m_prototype; }
- JSValuePtr prototypeForLookup(ExecState*) const;
- StructureChain* prototypeChain(ExecState*) const;
+public:
+ static void dumpStatistics();
+
+ JS_EXPORT_PRIVATE static Structure* addPropertyTransition(VM&, Structure*, PropertyName, unsigned attributes, JSCell* specificValue, PropertyOffset&);
+ JS_EXPORT_PRIVATE static Structure* addPropertyTransitionToExistingStructure(Structure*, PropertyName, unsigned attributes, JSCell* specificValue, PropertyOffset&);
+ static Structure* removePropertyTransition(VM&, Structure*, PropertyName, PropertyOffset&);
+ JS_EXPORT_PRIVATE static Structure* changePrototypeTransition(VM&, Structure*, JSValue prototype);
+ JS_EXPORT_PRIVATE static Structure* despecifyFunctionTransition(VM&, Structure*, PropertyName);
+ static Structure* attributeChangeTransition(VM&, Structure*, PropertyName, unsigned attributes);
+ static Structure* toCacheableDictionaryTransition(VM&, Structure*);
+ static Structure* toUncacheableDictionaryTransition(VM&, Structure*);
+ static Structure* sealTransition(VM&, Structure*);
+ static Structure* freezeTransition(VM&, Structure*);
+ static Structure* preventExtensionsTransition(VM&, Structure*);
+ static Structure* nonPropertyTransition(VM&, Structure*, NonPropertyTransition);
+
+ bool isSealed(VM&);
+ bool isFrozen(VM&);
+ bool isExtensible() const { return !m_preventExtensions; }
+ bool didTransition() const { return m_didTransition; }
+ bool putWillGrowOutOfLineStorage();
+ JS_EXPORT_PRIVATE size_t suggestedNewOutOfLineStorageCapacity();
+
+ Structure* flattenDictionaryStructure(VM&, JSObject*);
+
+ static const bool needsDestruction = true;
+ static const bool hasImmortalStructure = true;
+ static void destroy(JSCell*);
+
+ // These should be used with caution.
+ JS_EXPORT_PRIVATE PropertyOffset addPropertyWithoutTransition(VM&, PropertyName, unsigned attributes, JSCell* specificValue);
+ PropertyOffset removePropertyWithoutTransition(VM&, PropertyName);
+ void setPrototypeWithoutTransition(VM& vm, JSValue prototype) { m_prototype.set(vm, this, prototype); }
+
+ bool isDictionary() const { return m_dictionaryKind != NoneDictionaryKind; }
+ bool isUncacheableDictionary() const { return m_dictionaryKind == UncachedDictionaryKind; }
- Structure* previousID() const { return m_previous.get(); }
+ bool propertyAccessesAreCacheable() { return m_dictionaryKind != UncachedDictionaryKind && !typeInfo().prohibitsPropertyCaching(); }
- void growPropertyStorageCapacity();
- size_t propertyStorageCapacity() const { return m_propertyStorageCapacity; }
- size_t propertyStorageSize() const { return m_propertyTable ? m_propertyTable->keyCount + (m_propertyTable->deletedOffsets ? m_propertyTable->deletedOffsets->size() : 0) : m_offset + 1; }
+ // Type accessors.
+ const TypeInfo& typeInfo() const { ASSERT(structure()->classInfo() == &s_info); return m_typeInfo; }
+ bool isObject() const { return typeInfo().isObject(); }
- size_t get(const Identifier& propertyName);
- size_t get(const Identifier& propertyName, unsigned& attributes);
- void getEnumerablePropertyNames(ExecState*, PropertyNameArray&, JSObject*);
+ IndexingType indexingType() const { return m_indexingType & AllArrayTypes; }
+ IndexingType indexingTypeIncludingHistory() const { return m_indexingType; }
+
+ bool mayInterceptIndexedAccesses() const
+ {
+ return !!(indexingTypeIncludingHistory() & MayHaveIndexedAccessors);
+ }
+
+ bool anyObjectInChainMayInterceptIndexedAccesses() const;
+
+ bool needsSlowPutIndexing() const;
+ NonPropertyTransition suggestedArrayStorageTransition() const;
+
+ JSGlobalObject* globalObject() const { return m_globalObject.get(); }
+ void setGlobalObject(VM& vm, JSGlobalObject* globalObject) { m_globalObject.set(vm, this, globalObject); }
+
+ JSValue storedPrototype() const { return m_prototype.get(); }
+ JSValue prototypeForLookup(ExecState*) const;
+ JSValue prototypeForLookup(JSGlobalObject*) const;
+ JSValue prototypeForLookup(CodeBlock*) const;
+ StructureChain* prototypeChain(VM&, JSGlobalObject*) const;
+ StructureChain* prototypeChain(ExecState*) const;
+ static void visitChildren(JSCell*, SlotVisitor&);
+
+ // Will just the prototype chain intercept this property access?
+ bool prototypeChainMayInterceptStoreTo(VM&, PropertyName);
+
+ bool transitionDidInvolveSpecificValue() const { return !!m_specificValueInPrevious; }
+
+ Structure* previousID() const
+ {
+ ASSERT(structure()->classInfo() == &s_info);
+ if (typeInfo().structureHasRareData())
+ return rareData()->previousID();
+ return previous();
+ }
+ bool transitivelyTransitionedFrom(Structure* structureToFind);
- bool hasGetterSetterProperties() const { return m_hasGetterSetterProperties; }
- void setHasGetterSetterProperties(bool hasGetterSetterProperties) { m_hasGetterSetterProperties = hasGetterSetterProperties; }
+ unsigned outOfLineCapacity() const
+ {
+ ASSERT(checkOffsetConsistency());
+
+ unsigned outOfLineSize = this->outOfLineSize();
- bool isEmpty() const { return m_propertyTable ? !m_propertyTable->keyCount : m_offset == noOffset; }
+ if (!outOfLineSize)
+ return 0;
- private:
- Structure(JSValuePtr prototype, const TypeInfo&);
+ if (outOfLineSize <= initialOutOfLineCapacity)
+ return initialOutOfLineCapacity;
- size_t put(const Identifier& propertyName, unsigned attributes);
- size_t remove(const Identifier& propertyName);
- void getEnumerableNamesFromPropertyTable(PropertyNameArray&);
- void getEnumerableNamesFromClassInfoTable(ExecState*, const ClassInfo*, PropertyNameArray&);
+ ASSERT(outOfLineSize > initialOutOfLineCapacity);
+ COMPILE_ASSERT(outOfLineGrowthFactor == 2, outOfLineGrowthFactor_is_two);
+ return WTF::roundUpToPowerOfTwo(outOfLineSize);
+ }
+ unsigned outOfLineSize() const
+ {
+ ASSERT(checkOffsetConsistency());
+ ASSERT(structure()->classInfo() == &s_info);
+
+ return numberOfOutOfLineSlotsForLastOffset(m_offset);
+ }
+ bool hasInlineStorage() const
+ {
+ return !!m_inlineCapacity;
+ }
+ unsigned inlineCapacity() const
+ {
+ return m_inlineCapacity;
+ }
+ unsigned inlineSize() const
+ {
+ return std::min<unsigned>(m_offset + 1, m_inlineCapacity);
+ }
+ unsigned totalStorageSize() const
+ {
+ return numberOfSlotsForLastOffset(m_offset, m_inlineCapacity);
+ }
+ unsigned totalStorageCapacity() const
+ {
+ ASSERT(structure()->classInfo() == &s_info);
+ return outOfLineCapacity() + inlineCapacity();
+ }
- void expandPropertyMapHashTable();
- void rehashPropertyMapHashTable();
- void rehashPropertyMapHashTable(unsigned newTableSize);
- void createPropertyMapHashTable();
- void createPropertyMapHashTable(unsigned newTableSize);
- void insertIntoPropertyMapHashTable(const PropertyMapEntry&);
- void checkConsistency();
+ PropertyOffset firstValidOffset() const
+ {
+ if (hasInlineStorage())
+ return 0;
+ return firstOutOfLineOffset;
+ }
+ PropertyOffset lastValidOffset() const
+ {
+ return m_offset;
+ }
+ bool isValidOffset(PropertyOffset offset) const
+ {
+ return offset >= firstValidOffset()
+ && offset <= lastValidOffset();
+ }
- PropertyMapHashTable* copyPropertyTable();
- void materializePropertyMap();
- void materializePropertyMapIfNecessary()
- {
- if (m_propertyTable || !m_previous)
- return;
- materializePropertyMap();
- }
+ bool masqueradesAsUndefined(JSGlobalObject* lexicalGlobalObject);
- void clearEnumerationCache();
+ PropertyOffset get(VM&, PropertyName);
+ PropertyOffset get(VM&, const WTF::String& name);
+ JS_EXPORT_PRIVATE PropertyOffset get(VM&, PropertyName, unsigned& attributes, JSCell*& specificValue);
- void* addressOfCount()
- {
- return &m_refCount;
- }
+ bool hasGetterSetterProperties() const { return m_hasGetterSetterProperties; }
+ bool hasReadOnlyOrGetterSetterPropertiesExcludingProto() const { return m_hasReadOnlyOrGetterSetterPropertiesExcludingProto; }
+ void setHasGetterSetterProperties(bool is__proto__)
+ {
+ m_hasGetterSetterProperties = true;
+ if (!is__proto__)
+ m_hasReadOnlyOrGetterSetterPropertiesExcludingProto = true;
+ }
+ void setContainsReadOnlyProperties()
+ {
+ m_hasReadOnlyOrGetterSetterPropertiesExcludingProto = true;
+ }
- signed char transitionCount() const
- {
- // Since the number of transitions is always the same as m_offset, we keep the size of Structure down by not storing both.
- return m_offset == noOffset ? 0 : m_offset + 1;
- }
+ bool hasNonEnumerableProperties() const { return m_hasNonEnumerableProperties; }
- bool isValid(ExecState*, StructureChain* cachedPrototypeChain) const;
+ bool isEmpty() const
+ {
+ ASSERT(checkOffsetConsistency());
+ return !JSC::isValidOffset(m_offset);
+ }
+
+ JS_EXPORT_PRIVATE void despecifyDictionaryFunction(VM&, PropertyName);
+ void disableSpecificFunctionTracking() { m_specificFunctionThrashCount = maxSpecificFunctionThrashCount; }
+
+ void setEnumerationCache(VM&, JSPropertyNameIterator* enumerationCache); // Defined in JSPropertyNameIterator.h.
+ JSPropertyNameIterator* enumerationCache(); // Defined in JSPropertyNameIterator.h.
+ void getPropertyNamesFromStructure(VM&, PropertyNameArray&, EnumerationMode);
+
+ JSString* objectToStringValue()
+ {
+ if (!typeInfo().structureHasRareData())
+ return 0;
+ return rareData()->objectToStringValue();
+ }
+
+ void setObjectToStringValue(VM& vm, const JSCell* owner, JSString* value)
+ {
+ if (!typeInfo().structureHasRareData())
+ allocateRareData(vm);
+ rareData()->setObjectToStringValue(vm, owner, value);
+ }
+
+ bool staticFunctionsReified()
+ {
+ return m_staticFunctionReified;
+ }
+
+ void setStaticFunctionsReified()
+ {
+ m_staticFunctionReified = true;
+ }
+
+ const ClassInfo* classInfo() const { return m_classInfo; }
+
+ static ptrdiff_t prototypeOffset()
+ {
+ return OBJECT_OFFSETOF(Structure, m_prototype);
+ }
- static const unsigned emptyEntryIndex = 0;
-
- static const signed char s_maxTransitionLength = 64;
+ static ptrdiff_t globalObjectOffset()
+ {
+ return OBJECT_OFFSETOF(Structure, m_globalObject);
+ }
- static const signed char noOffset = -1;
+ static ptrdiff_t typeInfoFlagsOffset()
+ {
+ return OBJECT_OFFSETOF(Structure, m_typeInfo) + TypeInfo::flagsOffset();
+ }
+
+ static ptrdiff_t typeInfoTypeOffset()
+ {
+ return OBJECT_OFFSETOF(Structure, m_typeInfo) + TypeInfo::typeOffset();
+ }
+
+ static ptrdiff_t classInfoOffset()
+ {
+ return OBJECT_OFFSETOF(Structure, m_classInfo);
+ }
+
+ static ptrdiff_t indexingTypeOffset()
+ {
+ return OBJECT_OFFSETOF(Structure, m_indexingType);
+ }
+
+ static Structure* createStructure(VM&);
+
+ bool transitionWatchpointSetHasBeenInvalidated() const
+ {
+ return m_transitionWatchpointSet.hasBeenInvalidated();
+ }
+
+ bool transitionWatchpointSetIsStillValid() const
+ {
+ return m_transitionWatchpointSet.isStillValid();
+ }
+
+ void addTransitionWatchpoint(Watchpoint* watchpoint) const
+ {
+ ASSERT(transitionWatchpointSetIsStillValid());
+ m_transitionWatchpointSet.add(watchpoint);
+ }
+
+ void notifyTransitionFromThisStructure() const
+ {
+ m_transitionWatchpointSet.notifyWrite();
+ }
+
+ static JS_EXPORTDATA const ClassInfo s_info;
- TypeInfo m_typeInfo;
+private:
+ friend class LLIntOffsetsExtractor;
- JSValuePtr m_prototype;
- mutable RefPtr<StructureChain> m_cachedPrototypeChain;
+ JS_EXPORT_PRIVATE Structure(VM&, JSGlobalObject*, JSValue prototype, const TypeInfo&, const ClassInfo*, IndexingType, unsigned inlineCapacity);
+ Structure(VM&);
+ Structure(VM&, const Structure*);
- RefPtr<Structure> m_previous;
- RefPtr<UString::Rep> m_nameInPrevious;
+ static Structure* create(VM&, const Structure*);
+
+ typedef enum {
+ NoneDictionaryKind = 0,
+ CachedDictionaryKind = 1,
+ UncachedDictionaryKind = 2
+ } DictionaryKind;
+ static Structure* toDictionaryTransition(VM&, Structure*, DictionaryKind);
+
+ PropertyOffset putSpecificValue(VM&, PropertyName, unsigned attributes, JSCell* specificValue);
+ PropertyOffset remove(PropertyName);
+
+ void createPropertyMap(VM&, unsigned keyCount = 0);
+ void checkConsistency();
+
+ bool despecifyFunction(VM&, PropertyName);
+ void despecifyAllFunctions(VM&);
+
+ WriteBarrier<PropertyTable>& propertyTable();
+ PropertyTable* takePropertyTableOrCloneIfPinned(VM&, Structure* owner);
+ PropertyTable* copyPropertyTable(VM&, Structure* owner);
+ PropertyTable* copyPropertyTableForPinning(VM&, Structure* owner);
+ JS_EXPORT_PRIVATE void materializePropertyMap(VM&);
+ void materializePropertyMapIfNecessary(VM& vm)
+ {
+ ASSERT(structure()->classInfo() == &s_info);
+ ASSERT(checkOffsetConsistency());
+ if (!propertyTable() && previousID())
+ materializePropertyMap(vm);
+ }
+ void materializePropertyMapIfNecessaryForPinning(VM& vm)
+ {
+ ASSERT(structure()->classInfo() == &s_info);
+ checkOffsetConsistency();
+ if (!propertyTable())
+ materializePropertyMap(vm);
+ }
- union {
- Structure* singleTransition;
- StructureTransitionTable* table;
- } m_transitions;
+ void setPreviousID(VM& vm, Structure* transition, Structure* structure)
+ {
+ if (typeInfo().structureHasRareData())
+ rareData()->setPreviousID(vm, transition, structure);
+ else
+ m_previousOrRareData.set(vm, transition, structure);
+ }
- RefPtr<PropertyNameArrayData> m_cachedPropertyNameArrayData;
+ void clearPreviousID()
+ {
+ if (typeInfo().structureHasRareData())
+ rareData()->clearPreviousID();
+ else
+ m_previousOrRareData.clear();
+ }
- PropertyMapHashTable* m_propertyTable;
+ int transitionCount() const
+ {
+ // Since the number of transitions is always the same as m_offset, we keep the size of Structure down by not storing both.
+ return numberOfSlotsForLastOffset(m_offset, m_inlineCapacity);
+ }
- size_t m_propertyStorageCapacity;
- signed char m_offset;
+ bool isValid(JSGlobalObject*, StructureChain* cachedPrototypeChain) const;
+ bool isValid(ExecState*, StructureChain* cachedPrototypeChain) const;
+
+ void pin();
- bool m_isDictionary : 1;
- bool m_isPinnedPropertyTable : 1;
- bool m_hasGetterSetterProperties : 1;
- bool m_usingSingleTransitionSlot : 1;
- unsigned m_attributesInPrevious : 5;
- };
+ Structure* previous() const
+ {
+ ASSERT(!typeInfo().structureHasRareData());
+ return static_cast<Structure*>(m_previousOrRareData.get());
+ }
- inline size_t Structure::get(const Identifier& propertyName)
+ StructureRareData* rareData() const
{
- ASSERT(!propertyName.isNull());
+ ASSERT(typeInfo().structureHasRareData());
+ return static_cast<StructureRareData*>(m_previousOrRareData.get());
+ }
+
+ bool checkOffsetConsistency() const;
- materializePropertyMapIfNecessary();
- if (!m_propertyTable)
- return WTF::notFound;
+ void allocateRareData(VM&);
+ void cloneRareDataFrom(VM&, const Structure*);
- UString::Rep* rep = propertyName._ustring.rep();
+ static const int s_maxTransitionLength = 64;
- unsigned i = rep->computedHash();
+ static const unsigned maxSpecificFunctionThrashCount = 3;
+
+ WriteBarrier<JSGlobalObject> m_globalObject;
+ WriteBarrier<Unknown> m_prototype;
+ mutable WriteBarrier<StructureChain> m_cachedPrototypeChain;
-#if DUMP_PROPERTYMAP_STATS
- ++numProbes;
-#endif
+ WriteBarrier<JSCell> m_previousOrRareData;
- unsigned entryIndex = m_propertyTable->entryIndices[i & m_propertyTable->sizeMask];
- if (entryIndex == emptyEntryIndex)
- return WTF::notFound;
+ RefPtr<StringImpl> m_nameInPrevious;
+ WriteBarrier<JSCell> m_specificValueInPrevious;
- if (rep == m_propertyTable->entries()[entryIndex - 1].key)
- return m_propertyTable->entries()[entryIndex - 1].offset;
+ const ClassInfo* m_classInfo;
-#if DUMP_PROPERTYMAP_STATS
- ++numCollisions;
-#endif
+ StructureTransitionTable m_transitionTable;
- unsigned k = 1 | WTF::doubleHash(rep->computedHash());
+ // Should be accessed through propertyTable(). During GC, it may be set to 0 by another thread.
+ WriteBarrier<PropertyTable> m_propertyTableUnsafe;
- while (1) {
- i += k;
+ mutable InlineWatchpointSet m_transitionWatchpointSet;
-#if DUMP_PROPERTYMAP_STATS
- ++numRehashes;
-#endif
+ COMPILE_ASSERT(firstOutOfLineOffset < 256, firstOutOfLineOffset_fits);
- entryIndex = m_propertyTable->entryIndices[i & m_propertyTable->sizeMask];
- if (entryIndex == emptyEntryIndex)
- return WTF::notFound;
+ // m_offset does not account for anonymous slots
+ PropertyOffset m_offset;
- if (rep == m_propertyTable->entries()[entryIndex - 1].key)
- return m_propertyTable->entries()[entryIndex - 1].offset;
- }
- }
+ TypeInfo m_typeInfo;
+ IndexingType m_indexingType;
+
+ uint8_t m_inlineCapacity;
+ unsigned m_dictionaryKind : 2;
+ bool m_isPinnedPropertyTable : 1;
+ bool m_hasGetterSetterProperties : 1;
+ bool m_hasReadOnlyOrGetterSetterPropertiesExcludingProto : 1;
+ bool m_hasNonEnumerableProperties : 1;
+ unsigned m_attributesInPrevious : 14;
+ unsigned m_specificFunctionThrashCount : 2;
+ unsigned m_preventExtensions : 1;
+ unsigned m_didTransition : 1;
+ unsigned m_staticFunctionReified;
+};
} // namespace JSC