JSObject wastes too much memory on unused property slots
authorfpizlo@apple.com <fpizlo@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 30 Jun 2012 00:25:01 +0000 (00:25 +0000)
committerfpizlo@apple.com <fpizlo@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 30 Jun 2012 00:25:01 +0000 (00:25 +0000)
https://bugs.webkit.org/show_bug.cgi?id=90255

Reviewed by Mark Hahnenberg.

This does a few things:

- JSNonFinalObject no longer has inline property storage.

- Initial out-of-line property storage size is 4 slots for JSNonFinalObject,
  or 2x the inline storage for JSFinalObject.

- Property storage is only reallocated if it needs to be. Previously, we
  would reallocate the property storage on any transition where the original
  structure said shouldGrowProperyStorage(), but this led to spurious
  reallocations when doing transitionless property adds and there are
  deleted property slots available. That in turn led to crashes, because we
  would switch to out-of-line storage even if the capacity matched the
  criteria for inline storage.

- Inline JSFunction allocation is killed off because we don't have a good
  way of inlining property storage allocation. This didn't hurt performance.
  Killing off code is better than fixing it if that code wasn't doing any
  good.

This looks like a 1% progression on V8.

* interpreter/Interpreter.cpp:
(JSC::Interpreter::privateExecute):
* jit/JIT.cpp:
(JSC::JIT::privateCompileSlowCases):
* jit/JIT.h:
* jit/JITInlineMethods.h:
(JSC::JIT::emitAllocateBasicJSObject):
(JSC):
* jit/JITOpcodes.cpp:
(JSC::JIT::emit_op_new_func):
(JSC):
(JSC::JIT::emit_op_new_func_exp):
* runtime/JSFunction.cpp:
(JSC::JSFunction::finishCreation):
* runtime/JSObject.h:
(JSC::JSObject::isUsingInlineStorage):
(JSObject):
(JSC::JSObject::finishCreation):
(JSC):
(JSC::JSNonFinalObject::hasInlineStorage):
(JSNonFinalObject):
(JSC::JSNonFinalObject::JSNonFinalObject):
(JSC::JSNonFinalObject::finishCreation):
(JSC::JSFinalObject::hasInlineStorage):
(JSC::JSFinalObject::finishCreation):
(JSC::JSObject::offsetOfInlineStorage):
(JSC::JSObject::setPropertyStorage):
(JSC::Structure::inlineStorageCapacity):
(JSC::Structure::isUsingInlineStorage):
(JSC::JSObject::putDirectInternal):
(JSC::JSObject::setStructureAndReallocateStorageIfNecessary):
(JSC::JSObject::putDirectWithoutTransition):
* runtime/Structure.cpp:
(JSC::Structure::Structure):
(JSC::nextPropertyStorageCapacity):
(JSC):
(JSC::Structure::growPropertyStorageCapacity):
(JSC::Structure::suggestedNewPropertyStorageSize):
* runtime/Structure.h:
(JSC::Structure::putWillGrowPropertyStorage):
(Structure):

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@121605 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/interpreter/Interpreter.cpp
Source/JavaScriptCore/jit/JIT.cpp
Source/JavaScriptCore/jit/JIT.h
Source/JavaScriptCore/jit/JITInlineMethods.h
Source/JavaScriptCore/jit/JITOpcodes.cpp
Source/JavaScriptCore/runtime/JSFunction.cpp
Source/JavaScriptCore/runtime/JSObject.h
Source/JavaScriptCore/runtime/Structure.cpp
Source/JavaScriptCore/runtime/Structure.h

index 271309a..b32685a 100644 (file)
@@ -1,3 +1,74 @@
+2012-06-29  Filip Pizlo  <fpizlo@apple.com>
+
+        JSObject wastes too much memory on unused property slots
+        https://bugs.webkit.org/show_bug.cgi?id=90255
+
+        Reviewed by Mark Hahnenberg.
+        
+        This does a few things:
+        
+        - JSNonFinalObject no longer has inline property storage.
+        
+        - Initial out-of-line property storage size is 4 slots for JSNonFinalObject,
+          or 2x the inline storage for JSFinalObject.
+        
+        - Property storage is only reallocated if it needs to be. Previously, we
+          would reallocate the property storage on any transition where the original
+          structure said shouldGrowProperyStorage(), but this led to spurious
+          reallocations when doing transitionless property adds and there are
+          deleted property slots available. That in turn led to crashes, because we
+          would switch to out-of-line storage even if the capacity matched the
+          criteria for inline storage.
+        
+        - Inline JSFunction allocation is killed off because we don't have a good
+          way of inlining property storage allocation. This didn't hurt performance.
+          Killing off code is better than fixing it if that code wasn't doing any
+          good.
+        
+        This looks like a 1% progression on V8.
+
+        * interpreter/Interpreter.cpp:
+        (JSC::Interpreter::privateExecute):
+        * jit/JIT.cpp:
+        (JSC::JIT::privateCompileSlowCases):
+        * jit/JIT.h:
+        * jit/JITInlineMethods.h:
+        (JSC::JIT::emitAllocateBasicJSObject):
+        (JSC):
+        * jit/JITOpcodes.cpp:
+        (JSC::JIT::emit_op_new_func):
+        (JSC):
+        (JSC::JIT::emit_op_new_func_exp):
+        * runtime/JSFunction.cpp:
+        (JSC::JSFunction::finishCreation):
+        * runtime/JSObject.h:
+        (JSC::JSObject::isUsingInlineStorage):
+        (JSObject):
+        (JSC::JSObject::finishCreation):
+        (JSC):
+        (JSC::JSNonFinalObject::hasInlineStorage):
+        (JSNonFinalObject):
+        (JSC::JSNonFinalObject::JSNonFinalObject):
+        (JSC::JSNonFinalObject::finishCreation):
+        (JSC::JSFinalObject::hasInlineStorage):
+        (JSC::JSFinalObject::finishCreation):
+        (JSC::JSObject::offsetOfInlineStorage):
+        (JSC::JSObject::setPropertyStorage):
+        (JSC::Structure::inlineStorageCapacity):
+        (JSC::Structure::isUsingInlineStorage):
+        (JSC::JSObject::putDirectInternal):
+        (JSC::JSObject::setStructureAndReallocateStorageIfNecessary):
+        (JSC::JSObject::putDirectWithoutTransition):
+        * runtime/Structure.cpp:
+        (JSC::Structure::Structure):
+        (JSC::nextPropertyStorageCapacity):
+        (JSC):
+        (JSC::Structure::growPropertyStorageCapacity):
+        (JSC::Structure::suggestedNewPropertyStorageSize):
+        * runtime/Structure.h:
+        (JSC::Structure::putWillGrowPropertyStorage):
+        (Structure):
+
 2012-06-28  Filip Pizlo  <fpizlo@apple.com>
 
         DFG recompilation heuristics should be based on count, not rate
index b8610e7..4fdd79a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, 2009, 2010 Apple Inc. All rights reserved.
+ * Copyright (C) 2008, 2009, 2010, 2012 Apple Inc. All rights reserved.
  * Copyright (C) 2008 Cameron Zwarich <cwzwarich@uwaterloo.ca>
  *
  * Redistribution and use in source and binary forms, with or without
@@ -3602,7 +3602,7 @@ skip_id_custom_self:
                         proto = asObject(proto)->structure()->prototypeForLookup(callFrame);
                     }
                 }
-                baseObject->transitionTo(*globalData, newStructure);
+                baseObject->setStructureAndReallocateStorageIfNecessary(*globalData, newStructure);
 
                 int value = vPC[3].u.operand;
                 unsigned offset = vPC[7].u.operand;
index 78b6ad3..d6a5b9d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, 2009 Apple Inc. All rights reserved.
+ * Copyright (C) 2008, 2009, 2012 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -472,8 +472,6 @@ void JIT::privateCompileSlowCases()
         DEFINE_SLOWCASE_OP(op_neq)
         DEFINE_SLOWCASE_OP(op_new_array)
         DEFINE_SLOWCASE_OP(op_new_object)
-        DEFINE_SLOWCASE_OP(op_new_func)
-        DEFINE_SLOWCASE_OP(op_new_func_exp)
         DEFINE_SLOWCASE_OP(op_not)
         DEFINE_SLOWCASE_OP(op_nstricteq)
         DEFINE_SLOWCASE_OP(op_post_dec)
index 6d4c578..67aa185 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 Apple Inc. All rights reserved.
+ * Copyright (C) 2008, 2012 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -423,7 +423,6 @@ namespace JSC {
         template<typename ClassType, bool destructor, typename StructureType> void emitAllocateBasicJSObject(StructureType, RegisterID result, RegisterID storagePtr);
         void emitAllocateBasicStorage(size_t, RegisterID result, RegisterID storagePtr);
         template<typename T> void emitAllocateJSFinalObject(T structure, RegisterID result, RegisterID storagePtr);
-        void emitAllocateJSFunction(FunctionExecutable*, RegisterID scopeChain, RegisterID result, RegisterID storagePtr);
         void emitAllocateJSArray(unsigned valuesRegister, unsigned length, RegisterID cellResult, RegisterID storageResult, RegisterID storagePtr);
         
 #if ENABLE(VALUE_PROFILER)
@@ -750,8 +749,6 @@ namespace JSC {
         void emitSlow_op_to_jsnumber(Instruction*, Vector<SlowCaseEntry>::iterator&);
         void emitSlow_op_to_primitive(Instruction*, Vector<SlowCaseEntry>::iterator&);
         void emitSlow_op_urshift(Instruction*, Vector<SlowCaseEntry>::iterator&);
-        void emitSlow_op_new_func(Instruction*, Vector<SlowCaseEntry>::iterator&);
-        void emitSlow_op_new_func_exp(Instruction*, Vector<SlowCaseEntry>::iterator&);
         void emitSlow_op_new_array(Instruction*, Vector<SlowCaseEntry>::iterator&);
         
         void emitRightShift(Instruction*, bool isUnsigned);
index 40985ac..fdb9564 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 Apple Inc. All rights reserved.
+ * Copyright (C) 2008, 2012 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -429,8 +429,11 @@ template <typename ClassType, bool destructor, typename StructureType> inline vo
     storePtr(TrustedImmPtr(0), Address(result, JSObject::offsetOfInheritorID()));
 
     // initialize the object's property storage pointer
-    addPtr(TrustedImm32(sizeof(JSObject)), result, storagePtr);
-    storePtr(storagePtr, Address(result, ClassType::offsetOfPropertyStorage()));
+    if (ClassType::hasInlineStorage()) {
+        addPtr(TrustedImm32(sizeof(JSObject)), result, storagePtr);
+        storePtr(storagePtr, Address(result, ClassType::offsetOfPropertyStorage()));
+    } else
+        storePtr(TrustedImmPtr(0), Address(result, ClassType::offsetOfPropertyStorage()));
 }
 
 template <typename T> inline void JIT::emitAllocateJSFinalObject(T structure, RegisterID result, RegisterID scratch)
@@ -438,28 +441,6 @@ template <typename T> inline void JIT::emitAllocateJSFinalObject(T structure, Re
     emitAllocateBasicJSObject<JSFinalObject, false, T>(structure, result, scratch);
 }
 
-inline void JIT::emitAllocateJSFunction(FunctionExecutable* executable, RegisterID scopeChain, RegisterID result, RegisterID storagePtr)
-{
-    emitAllocateBasicJSObject<JSFunction, true>(TrustedImmPtr(m_codeBlock->globalObject()->namedFunctionStructure()), result, storagePtr);
-
-    // store the function's scope chain
-    storePtr(scopeChain, Address(result, JSFunction::offsetOfScopeChain()));
-
-    // store the function's executable member
-    storePtr(TrustedImmPtr(executable), Address(result, JSFunction::offsetOfExecutable()));
-
-    // clear the function's inheritorID
-    storePtr(TrustedImmPtr(0), Address(result, JSFunction::offsetOfCachedInheritorID()));
-
-    // store the function's name
-    ASSERT(executable->nameValue());
-    int functionNameOffset = sizeof(JSValue) * m_codeBlock->globalObject()->functionNameOffset();
-    storePtr(TrustedImmPtr(executable->nameValue()), Address(regT1, functionNameOffset + OBJECT_OFFSETOF(JSValue, u.asBits.payload)));
-#if USE(JSVALUE32_64)
-    store32(TrustedImm32(JSValue::CellTag), Address(regT1, functionNameOffset + OBJECT_OFFSETOF(JSValue, u.asBits.tag)));
-#endif
-}
-
 inline void JIT::emitAllocateBasicStorage(size_t size, RegisterID result, RegisterID storagePtr)
 {
     CopiedAllocator* allocator = &m_globalData->heap.storageAllocator();
index 2e448dd..d96cfd9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 Apple Inc. All rights reserved.
+ * Copyright (C) 2009, 2012 Apple Inc. All rights reserved.
  * Copyright (C) 2010 Patrick Gansterer <paroga@paroga.com>
  *
  * Redistribution and use in source and binary forms, with or without
@@ -1618,11 +1618,9 @@ void JIT::emit_op_new_func(Instruction* currentInstruction)
 #endif
     }
 
-    FunctionExecutable* executable = m_codeBlock->functionDecl(currentInstruction[2].u.operand);
-    emitGetFromCallFrameHeaderPtr(RegisterFile::ScopeChain, regT2);
-    emitAllocateJSFunction(executable, regT2, regT0, regT1);
-
-    emitStoreCell(dst, regT0);
+    JITStubCall stubCall(this, cti_op_new_func);
+    stubCall.addArgument(TrustedImmPtr(m_codeBlock->functionDecl(currentInstruction[2].u.operand)));
+    stubCall.call(dst);
 
     if (currentInstruction[3].u.operand) {
 #if USE(JSVALUE32_64)        
@@ -1634,44 +1632,13 @@ void JIT::emit_op_new_func(Instruction* currentInstruction)
     }
 }
 
-void JIT::emitSlow_op_new_func(Instruction* currentInstruction, Vector<SlowCaseEntry>::iterator& iter)
-{
-    linkSlowCase(iter);
-    JITStubCall stubCall(this, cti_op_new_func);
-    stubCall.addArgument(TrustedImmPtr(m_codeBlock->functionDecl(currentInstruction[2].u.operand)));
-    stubCall.call(currentInstruction[1].u.operand);
-}
-
 void JIT::emit_op_new_func_exp(Instruction* currentInstruction)
 {
-    FunctionExecutable* executable = m_codeBlock->functionExpr(currentInstruction[2].u.operand);
-
-    // We only inline the allocation of a anonymous function expressions
-    // If we want to be able to allocate a named function expression, we would
-    // need to be able to do inline allocation of a JSStaticScopeObject.
-    if (executable->name().isNull()) {
-        emitGetFromCallFrameHeaderPtr(RegisterFile::ScopeChain, regT2);
-        emitAllocateJSFunction(executable, regT2, regT0, regT1);
-        emitStoreCell(currentInstruction[1].u.operand, regT0);
-        return;
-    }
-
     JITStubCall stubCall(this, cti_op_new_func_exp);
     stubCall.addArgument(TrustedImmPtr(m_codeBlock->functionExpr(currentInstruction[2].u.operand)));
     stubCall.call(currentInstruction[1].u.operand);
 }
 
-void JIT::emitSlow_op_new_func_exp(Instruction* currentInstruction, Vector<SlowCaseEntry>::iterator& iter)
-{
-    FunctionExecutable* executable = m_codeBlock->functionExpr(currentInstruction[2].u.operand);
-    if (!executable->name().isNull())
-        return;
-    linkSlowCase(iter);
-    JITStubCall stubCall(this, cti_op_new_func_exp);
-    stubCall.addArgument(TrustedImmPtr(executable));
-    stubCall.call(currentInstruction[1].u.operand);
-}
-
 void JIT::emit_op_new_array(Instruction* currentInstruction)
 {
     int length = currentInstruction[3].u.operand;
index f2d9c81..0a65dee 100644 (file)
@@ -103,13 +103,16 @@ void JSFunction::finishCreation(ExecState* exec, NativeExecutable* executable, i
 
 void JSFunction::finishCreation(ExecState* exec, FunctionExecutable* executable, ScopeChainNode* scopeChainNode)
 {
-    Base::finishCreation(exec->globalData());
+    JSGlobalData& globalData = exec->globalData();
+    Base::finishCreation(globalData);
     ASSERT(inherits(&s_info));
 
     // Switching the structure here is only safe if we currently have the function structure!
     ASSERT(structure() == scopeChainNode->globalObject->functionStructure());
-    setStructure(exec->globalData(), scopeChainNode->globalObject->namedFunctionStructure());
-    putDirectOffset(exec->globalData(), scopeChainNode->globalObject->functionNameOffset(), executable->nameValue());
+    setStructureAndReallocateStorageIfNecessary(
+        globalData,
+        scopeChainNode->globalObject->namedFunctionStructure());
+    putDirectOffset(globalData, scopeChainNode->globalObject->functionNameOffset(), executable->nameValue());
 }
 
 Structure* JSFunction::cacheInheritorID(ExecState* exec)
index fdb708d..9417fe9 100644 (file)
@@ -79,6 +79,14 @@ namespace JSC {
         Accessor     = 1 << 5,  // property is a getter/setter
     };
 
+#if USE(JSVALUE32_64)
+#define JSFinalObject_inlineStorageCapacity 6
+#else
+#define JSFinalObject_inlineStorageCapacity 4
+#endif
+
+COMPILE_ASSERT((JSFinalObject_inlineStorageCapacity >= 0), final_storage_non_negative);
+
     class JSObject : public JSCell {
         friend class BatchedTransitionOptimizer;
         friend class JIT;
@@ -221,16 +229,25 @@ namespace JSC {
         void reifyStaticFunctionsForDelete(ExecState* exec);
 
         JS_EXPORT_PRIVATE PropertyStorage growPropertyStorage(JSGlobalData&, size_t oldSize, size_t newSize);
-        bool isUsingInlineStorage() const { return static_cast<const void*>(m_propertyStorage.get()) == static_cast<const void*>(this + 1); }
+        bool isUsingInlineStorage() const
+        {
+            bool result =
+                !m_propertyStorage.get()
+                || static_cast<const void*>(m_propertyStorage.get()) == static_cast<const void*>(this + 1);
+            ASSERT(result == structure()->isUsingInlineStorage());
+            return result;
+        }
         void setPropertyStorage(JSGlobalData&, PropertyStorage, Structure*);
+        
+        bool reallocateStorageIfNecessary(JSGlobalData&, unsigned oldCapacity, Structure*);
+        void setStructureAndReallocateStorageIfNecessary(JSGlobalData&, unsigned oldCapacity, Structure*);
+        void setStructureAndReallocateStorageIfNecessary(JSGlobalData&, Structure*);
 
         void* addressOfPropertyStorage()
         {
             return &m_propertyStorage;
         }
 
-        static const unsigned baseExternalStorageCapacity = 16;
-
         void flattenDictionaryObject(JSGlobalData& globalData)
         {
             structure()->flattenDictionaryStructure(globalData, this);
@@ -250,14 +267,13 @@ namespace JSC {
         static JS_EXPORTDATA const ClassInfo s_info;
 
     protected:
-        void finishCreation(JSGlobalData& globalData, PropertyStorage inlineStorage)
+        void finishCreation(JSGlobalData& globalData)
         {
             Base::finishCreation(globalData);
             ASSERT(inherits(&s_info));
-            ASSERT(structure()->propertyStorageCapacity() < baseExternalStorageCapacity);
+            ASSERT(structure()->isUsingInlineStorage());
             ASSERT(structure()->isEmpty());
             ASSERT(prototype().isNull() || Heap::heap(this) == Heap::heap(prototype()));
-            ASSERT_UNUSED(inlineStorage, static_cast<void*>(inlineStorage) == static_cast<void*>(this + 1));
             ASSERT(structure()->isObject());
             ASSERT(classInfo());
         }
@@ -316,16 +332,6 @@ namespace JSC {
     };
 
 
-#if USE(JSVALUE32_64)
-#define JSNonFinalObject_inlineStorageCapacity 4
-#define JSFinalObject_inlineStorageCapacity 6
-#else
-#define JSNonFinalObject_inlineStorageCapacity 2
-#define JSFinalObject_inlineStorageCapacity 4
-#endif
-
-COMPILE_ASSERT((JSFinalObject_inlineStorageCapacity >= JSNonFinalObject_inlineStorageCapacity), final_storage_is_at_least_as_large_as_non_final);
-
     // JSNonFinalObject is a type of JSObject that has some internal storage,
     // but also preserves some space in the collector cell for additional
     // data members in derived types.
@@ -340,22 +346,23 @@ COMPILE_ASSERT((JSFinalObject_inlineStorageCapacity >= JSNonFinalObject_inlineSt
             return Structure::create(globalData, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), &s_info);
         }
 
+        static bool hasInlineStorage()
+        {
+            return false;
+        }
+
     protected:
         explicit JSNonFinalObject(JSGlobalData& globalData, Structure* structure)
-            : JSObject(globalData, structure, m_inlineStorage)
+            : JSObject(globalData, structure, 0)
         {
         }
 
         void finishCreation(JSGlobalData& globalData)
         {
-            Base::finishCreation(globalData, m_inlineStorage);
-            ASSERT(!(OBJECT_OFFSETOF(JSNonFinalObject, m_inlineStorage) % sizeof(double)));
-            ASSERT(this->structure()->propertyStorageCapacity() == JSNonFinalObject_inlineStorageCapacity);
+            Base::finishCreation(globalData);
+            ASSERT(!this->structure()->propertyStorageCapacity());
             ASSERT(classInfo());
         }
-
-    private:
-        WriteBarrier<Unknown> m_inlineStorage[JSNonFinalObject_inlineStorageCapacity];
     };
 
     class JSFinalObject;
@@ -376,10 +383,14 @@ COMPILE_ASSERT((JSFinalObject_inlineStorageCapacity >= JSNonFinalObject_inlineSt
 
         static JS_EXPORTDATA const ClassInfo s_info;
 
+        static bool hasInlineStorage()
+        {
+            return true;
+        }
     protected:
         void finishCreation(JSGlobalData& globalData)
         {
-            Base::finishCreation(globalData, m_inlineStorage);
+            Base::finishCreation(globalData);
             ASSERT(!(OBJECT_OFFSETOF(JSFinalObject, m_inlineStorage) % sizeof(double)));
             ASSERT(this->structure()->propertyStorageCapacity() == JSFinalObject_inlineStorageCapacity);
             ASSERT(classInfo());
@@ -417,7 +428,6 @@ inline bool isJSFinalObject(JSValue value)
 
 inline size_t JSObject::offsetOfInlineStorage()
 {
-    ASSERT(OBJECT_OFFSETOF(JSFinalObject, m_inlineStorage) == OBJECT_OFFSETOF(JSNonFinalObject, m_inlineStorage));
     return OBJECT_OFFSETOF(JSFinalObject, m_inlineStorage);
 }
 
@@ -465,6 +475,8 @@ inline void JSObject::setPropertyStorage(JSGlobalData& globalData, PropertyStora
 {
     ASSERT(storage);
     ASSERT(structure);
+    ASSERT(!structure->isUsingInlineStorage()
+           || (classInfo() == &JSFinalObject::s_info && static_cast<void*>(storage) == static_cast<void*>(this + 1)));
     setStructure(globalData, structure);
     m_propertyStorage.set(globalData, this, storage);
 }
@@ -530,9 +542,17 @@ inline Structure* JSObject::inheritorID(JSGlobalData& globalData)
     return createInheritorID(globalData);
 }
 
+inline size_t Structure::inlineStorageCapacity() const
+{
+    if (classInfo() == &JSFinalObject::s_info)
+        return JSFinalObject_inlineStorageCapacity;
+    return 0;
+}
+
 inline bool Structure::isUsingInlineStorage() const
 {
-    return propertyStorageCapacity() < JSObject::baseExternalStorageCapacity;
+    ASSERT(propertyStorageCapacity() >= inlineStorageCapacity());
+    return propertyStorageCapacity() == inlineStorageCapacity();
 }
 
 inline bool JSCell::inherits(const ClassInfo* info) const
@@ -681,7 +701,7 @@ inline bool JSObject::putDirectInternal(JSGlobalData& globalData, PropertyName p
             return false;
 
         PropertyStorage newStorage = propertyStorage();
-        if (structure()->shouldGrowPropertyStorage())
+        if (structure()->putWillGrowPropertyStorage())
             newStorage = growPropertyStorage(globalData, structure()->propertyStorageCapacity(), structure()->suggestedNewPropertyStorageSize());
         offset = structure()->addPropertyWithoutTransition(globalData, propertyName, attributes, specificFunction);
         setPropertyStorage(globalData, newStorage, structure());
@@ -746,14 +766,11 @@ inline bool JSObject::putDirectInternal(JSGlobalData& globalData, PropertyName p
     if ((mode == PutModePut) && !isExtensible())
         return false;
 
-    PropertyStorage newStorage = propertyStorage();
-    if (structure()->shouldGrowPropertyStorage())
-        newStorage = growPropertyStorage(globalData, structure()->propertyStorageCapacity(), structure()->suggestedNewPropertyStorageSize());
-
     Structure* structure = Structure::addPropertyTransition(globalData, this->structure(), propertyName, attributes, specificFunction, offset);
-
+    
     ASSERT(offset < structure->propertyStorageCapacity());
-    setPropertyStorage(globalData, newStorage, structure);
+    setStructureAndReallocateStorageIfNecessary(globalData, structure);
+
     putDirectOffset(globalData, offset, value);
     // This is a new property; transitions with specific values are not currently cachable,
     // so leave the slot in an uncachable state.
@@ -762,6 +779,26 @@ inline bool JSObject::putDirectInternal(JSGlobalData& globalData, PropertyName p
     return true;
 }
 
+inline void JSObject::setStructureAndReallocateStorageIfNecessary(JSGlobalData& globalData, unsigned oldCapacity, Structure* newStructure)
+{
+    ASSERT(oldCapacity <= newStructure->propertyStorageCapacity());
+    
+    if (oldCapacity == newStructure->propertyStorageCapacity()) {
+        setStructure(globalData, newStructure);
+        return;
+    }
+    
+    PropertyStorage newStorage = growPropertyStorage(
+        globalData, oldCapacity, newStructure->suggestedNewPropertyStorageSize());
+    setPropertyStorage(globalData, newStorage, newStructure);
+}
+
+inline void JSObject::setStructureAndReallocateStorageIfNecessary(JSGlobalData& globalData, Structure* newStructure)
+{
+    setStructureAndReallocateStorageIfNecessary(
+        globalData, structure()->propertyStorageCapacity(), newStructure);
+}
+
 inline bool JSObject::putOwnDataProperty(JSGlobalData& globalData, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
 {
     ASSERT(value);
@@ -788,21 +825,13 @@ inline void JSObject::putDirectWithoutTransition(JSGlobalData& globalData, Prope
 {
     ASSERT(!value.isGetterSetter() && !(attributes & Accessor));
     PropertyStorage newStorage = propertyStorage();
-    if (structure()->shouldGrowPropertyStorage())
+    if (structure()->putWillGrowPropertyStorage())
         newStorage = growPropertyStorage(globalData, structure()->propertyStorageCapacity(), structure()->suggestedNewPropertyStorageSize());
     size_t offset = structure()->addPropertyWithoutTransition(globalData, propertyName, attributes, getCallableObject(value));
     setPropertyStorage(globalData, newStorage, structure());
     putDirectOffset(globalData, offset, value);
 }
 
-inline void JSObject::transitionTo(JSGlobalData& globalData, Structure* newStructure)
-{
-    PropertyStorage newStorage = propertyStorage();
-    if (structure()->propertyStorageCapacity() != newStructure->propertyStorageCapacity())
-        newStorage = growPropertyStorage(globalData, structure()->propertyStorageCapacity(), newStructure->propertyStorageCapacity());
-    setPropertyStorage(globalData, newStorage, newStructure);
-}
-
 inline JSValue JSObject::toPrimitive(ExecState* exec, PreferredPrimitiveType preferredType) const
 {
     return methodTable()->defaultValue(this, exec, preferredType);
index 5691261..a2585b0 100644 (file)
@@ -156,7 +156,7 @@ Structure::Structure(JSGlobalData& globalData, JSGlobalObject* globalObject, JSV
     , m_prototype(globalData, this, prototype)
     , m_classInfo(classInfo)
     , m_transitionWatchpointSet(InitializedWatching)
-    , m_propertyStorageCapacity(typeInfo.isFinalObject() ? JSFinalObject_inlineStorageCapacity : JSNonFinalObject_inlineStorageCapacity)
+    , m_propertyStorageCapacity(typeInfo.isFinalObject() ? JSFinalObject_inlineStorageCapacity : 0)
     , m_offset(noOffset)
     , m_dictionaryKind(NoneDictionaryKind)
     , m_isPinnedPropertyTable(false)
@@ -256,19 +256,21 @@ void Structure::materializePropertyMap(JSGlobalData& globalData)
     }
 }
 
+inline size_t nextPropertyStorageCapacity(size_t currentCapacity)
+{
+    if (!currentCapacity)
+        return 4;
+    return currentCapacity * 2;
+}
+
 void Structure::growPropertyStorageCapacity()
 {
-    if (isUsingInlineStorage())
-        m_propertyStorageCapacity = JSObject::baseExternalStorageCapacity;
-    else
-        m_propertyStorageCapacity *= 2;
+    m_propertyStorageCapacity = nextPropertyStorageCapacity(m_propertyStorageCapacity);
 }
 
 size_t Structure::suggestedNewPropertyStorageSize()
 {
-    if (isUsingInlineStorage())
-        return JSObject::baseExternalStorageCapacity;
-    return m_propertyStorageCapacity * 2;
+    return nextPropertyStorageCapacity(m_propertyStorageCapacity);
 }
  
 void Structure::despecifyDictionaryFunction(JSGlobalData& globalData, PropertyName propertyName)
index 448a81c..bb53c42 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, 2009 Apple Inc. All rights reserved.
+ * Copyright (C) 2008, 2009, 2012 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -103,7 +103,23 @@ namespace JSC {
         bool isFrozen(JSGlobalData&);
         bool isExtensible() const { return !m_preventExtensions; }
         bool didTransition() const { return m_didTransition; }
-        bool shouldGrowPropertyStorage() { return propertyStorageCapacity() == propertyStorageSize(); }
+        bool putWillGrowPropertyStorage()
+        {
+            ASSERT(propertyStorageCapacity() >= propertyStorageSize());
+            
+            if (!m_propertyTable) {
+                unsigned currentSize = static_cast<unsigned>(m_offset + 1);
+                ASSERT(propertyStorageCapacity() >= currentSize);
+                return currentSize == propertyStorageCapacity();
+            }
+            
+            ASSERT(propertyStorageCapacity() >= m_propertyTable->propertyStorageSize());
+            if (m_propertyTable->hasDeletedOffset())
+                return false;
+            
+            ASSERT(propertyStorageCapacity() >= m_propertyTable->size());
+            return m_propertyTable->size() == propertyStorageCapacity();
+        }
         JS_EXPORT_PRIVATE size_t suggestedNewPropertyStorageSize(); 
 
         Structure* flattenDictionaryStructure(JSGlobalData&, JSObject*);
@@ -139,6 +155,7 @@ namespace JSC {
         void growPropertyStorageCapacity();
         unsigned propertyStorageCapacity() const { ASSERT(structure()->classInfo() == &s_info); return m_propertyStorageCapacity; }
         unsigned propertyStorageSize() const { ASSERT(structure()->classInfo() == &s_info); return (m_propertyTable ? m_propertyTable->propertyStorageSize() : static_cast<unsigned>(m_offset + 1)); }
+        size_t inlineStorageCapacity() const;
         bool isUsingInlineStorage() const;
 
         size_t get(JSGlobalData&, PropertyName);