Bug 20821: Cache property transitions to speed up object initialization
authoroliver@apple.com <oliver@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 14 Sep 2008 08:18:49 +0000 (08:18 +0000)
committeroliver@apple.com <oliver@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 14 Sep 2008 08:18:49 +0000 (08:18 +0000)
https://bugs.webkit.org/show_bug.cgi?id=20821

Reviewed by Cameron Zwarich.

Implement a transition cache to improve the performance of new properties
being added to objects.  This is extremely beneficial in constructors and
shows up as a 34% improvement on access-binary-trees in SunSpider (0.8%
overall)

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

13 files changed:
JavaScriptCore/ChangeLog
JavaScriptCore/VM/CTI.cpp
JavaScriptCore/VM/CTI.h
JavaScriptCore/VM/CodeBlock.cpp
JavaScriptCore/VM/CodeGenerator.cpp
JavaScriptCore/VM/Machine.cpp
JavaScriptCore/VM/Machine.h
JavaScriptCore/VM/Opcode.h
JavaScriptCore/kjs/JSObject.h
JavaScriptCore/kjs/PutPropertySlot.h
JavaScriptCore/kjs/StructureID.cpp
JavaScriptCore/kjs/StructureID.h
JavaScriptCore/masm/X86Assembler.h

index cb82422..4d5df98 100644 (file)
@@ -1,3 +1,58 @@
+2008-09-13  Oliver Hunt  <oliver@apple.com>
+
+        Reviewed by Cameron Zwarich.
+
+        Bug 20821: Cache property transitions to speed up object initialization
+        https://bugs.webkit.org/show_bug.cgi?id=20821
+
+        Implement a transition cache to improve the performance of new properties
+        being added to objects.  This is extremely beneficial in constructors and
+        shows up as a 34% improvement on access-binary-trees in SunSpider (0.8%
+        overall)
+
+        * VM/CTI.cpp:
+        (JSC::CTI::privateCompileMainPass):
+        (JSC::):
+        (JSC::transitionWillNeedStorageRealloc):
+        (JSC::CTI::privateCompilePutByIdTransition):
+        * VM/CTI.h:
+        (JSC::CTI::compilePutByIdTransition):
+        * VM/CodeBlock.cpp:
+        (JSC::printPutByIdOp):
+        (JSC::CodeBlock::printStructureIDs):
+        (JSC::CodeBlock::dump):
+        (JSC::CodeBlock::derefStructureIDs):
+        (JSC::CodeBlock::refStructureIDs):
+        * VM/CodeGenerator.cpp:
+        (JSC::CodeGenerator::emitPutById):
+        * VM/Machine.cpp:
+        (JSC::cachePrototypeChain):
+        (JSC::Machine::tryCachePutByID):
+        (JSC::Machine::tryCacheGetByID):
+        (JSC::Machine::privateExecute):
+        (JSC::Machine::tryCTICachePutByID):
+        (JSC::Machine::tryCTICacheGetByID):
+        * VM/Machine.h:
+        * VM/Opcode.h:
+        * kjs/JSObject.h:
+        (JSC::JSObject::putDirect):
+        (JSC::JSObject::transitionTo):
+        * kjs/PutPropertySlot.h:
+        (JSC::PutPropertySlot::PutPropertySlot):
+        (JSC::PutPropertySlot::wasTransition):
+        (JSC::PutPropertySlot::setWasTransition):
+        * kjs/StructureID.cpp:
+        (JSC::StructureID::transitionTo):
+        (JSC::StructureIDChain::StructureIDChain):
+        * kjs/StructureID.h:
+        (JSC::StructureID::previousID):
+        (JSC::StructureID::setCachedPrototypeChain):
+        (JSC::StructureID::cachedPrototypeChain):
+        (JSC::StructureID::propertyMap):
+        * masm/X86Assembler.h:
+        (JSC::X86Assembler::addl_i8m):
+        (JSC::X86Assembler::subl_i8m):
+
 2008-09-12  Cameron Zwarich  <cwzwarich@uwaterloo.ca>
 
         Reviewed by Maciej Stachowiak.
index 0a6d629..1ce9440 100644 (file)
@@ -584,7 +584,7 @@ void CTI::privateCompileMainPass()
             emitPutArg(X86::eax, 0); // leave the base in eax
             emitPutArg(X86::edx, 8); // leave the base in edx
             emitCall(i, Machine::cti_op_put_by_id);
-            i += 6;
+            i += 8;
             break;
         }
         case op_get_by_id: {
@@ -1304,6 +1304,7 @@ void CTI::privateCompileMainPass()
         case op_get_string_length:
         case op_put_by_id_generic:
         case op_put_by_id_replace:
+        case op_put_by_id_transition:
             ASSERT_NOT_REACHED();
         }
     }
@@ -1834,6 +1835,113 @@ void* CTI::privateCompilePutByIdReplace(StructureID* structureID, size_t cachedO
     return code;
 }
 
+extern "C" {
+
+static JSValue* SFX_CALL transitionObject(StructureID* newStructureID, size_t cachedOffset, JSObject* baseObject, JSValue* value)
+{
+    StructureID* oldStructureID = newStructureID->previousID();
+
+    baseObject->transitionTo(newStructureID);
+
+    if (oldStructureID->propertyMap().storageSize() == JSObject::inlineStorageCapacity)
+        baseObject->allocatePropertyStorage(oldStructureID->propertyMap().storageSize(), oldStructureID->propertyMap().size());
+
+    baseObject->putDirectOffset(cachedOffset, value);
+    return baseObject;
+}
+
+}
+
+static inline bool transitionWillNeedStorageRealloc(StructureID* oldStructureID, StructureID* newStructureID)
+{
+    if (oldStructureID->propertyMap().storageSize() == JSObject::inlineStorageCapacity)
+        return true;
+
+    if (oldStructureID->propertyMap().storageSize() < JSObject::inlineStorageCapacity)
+        return false;
+
+    if (oldStructureID->propertyMap().size() != newStructureID->propertyMap().size())
+        return true;
+
+    return false;
+}
+
+void* CTI::privateCompilePutByIdTransition(StructureID* oldStructureID, StructureID* newStructureID, size_t cachedOffset, StructureIDChain* sIDC)
+{
+    Vector<X86Assembler::JmpSrc, 16> failureCases;
+    // check eax is an object of the right StructureID.
+    m_jit.testl_i32r(JSImmediate::TagMask, X86::eax);
+    failureCases.append(m_jit.emitUnlinkedJne());
+    m_jit.cmpl_i32m(reinterpret_cast<uint32_t>(oldStructureID), OBJECT_OFFSET(JSCell, m_structureID), X86::eax);
+    failureCases.append(m_jit.emitUnlinkedJne());
+    Vector<X86Assembler::JmpSrc> successCases;
+
+    //  ecx = baseObject
+    m_jit.movl_mr(OBJECT_OFFSET(JSCell, m_structureID), X86::eax, X86::ecx);
+    // proto(ecx) = baseObject->structureID()->prototype()
+    m_jit.cmpl_i32m(ObjectType, OBJECT_OFFSET(StructureID, m_type), X86::ecx);
+    failureCases.append(m_jit.emitUnlinkedJne());
+    m_jit.movl_mr(OBJECT_OFFSET(StructureID, m_prototype), X86::ecx, X86::ecx);
+    
+    // ecx = baseObject->m_structureID
+    for (RefPtr<StructureID>* it = sIDC->head(); *it; ++it) {
+        // null check the prototype
+        m_jit.cmpl_i32r(reinterpret_cast<intptr_t> (jsNull()), X86::ecx);
+        successCases.append(m_jit.emitUnlinkedJe());
+
+        // Check the structure id
+        m_jit.cmpl_i32m(reinterpret_cast<uint32_t>(it->get()), OBJECT_OFFSET(JSCell, m_structureID), X86::ecx);
+        failureCases.append(m_jit.emitUnlinkedJne());
+        
+        m_jit.movl_mr(OBJECT_OFFSET(JSCell, m_structureID), X86::ecx, X86::ecx);
+        m_jit.cmpl_i32m(ObjectType, OBJECT_OFFSET(StructureID, m_type), X86::ecx);
+        failureCases.append(m_jit.emitUnlinkedJne());
+        m_jit.movl_mr(OBJECT_OFFSET(StructureID, m_prototype), X86::ecx, X86::ecx);
+    }
+
+    failureCases.append(m_jit.emitUnlinkedJne());
+    for (unsigned i = 0; i < successCases.size(); ++i)
+        m_jit.link(successCases[i], m_jit.label());
+
+    X86Assembler::JmpSrc callTarget;
+    // Fast case, don't need to do any heavy lifting, so don't bother making a call.
+    if (!transitionWillNeedStorageRealloc(oldStructureID, newStructureID)) {
+        // Assumes m_refCount can be decremented easily, refcount decrement is safe as 
+        // codeblock should ensure oldStructureID->m_refCount > 0
+        m_jit.subl_i8m(1, reinterpret_cast<void*>(oldStructureID));
+        m_jit.addl_i8m(1, reinterpret_cast<void*>(newStructureID));
+        m_jit.movl_i32m(reinterpret_cast<uint32_t>(newStructureID), OBJECT_OFFSET(JSCell, m_structureID), X86::eax);
+
+        // write the value
+        m_jit.movl_mr(OBJECT_OFFSET(JSObject, m_propertyStorage), X86::eax, X86::eax);
+        m_jit.movl_rm(X86::edx, cachedOffset * sizeof(JSValue*), X86::eax);
+    } else {
+        // Slow case transition -- we're going to need to quite a bit of work,
+        // so just make a call
+        m_jit.pushl_r(X86::edx);
+        m_jit.pushl_r(X86::eax);
+        m_jit.movl_i32r(cachedOffset, X86::eax);
+        m_jit.pushl_r(X86::eax);
+        m_jit.movl_i32r(reinterpret_cast<uint32_t>(newStructureID), X86::eax);
+        m_jit.pushl_r(X86::eax);
+        callTarget = m_jit.emitCall();
+        m_jit.addl_i32r(4 * sizeof(void*), X86::esp);
+    }
+    m_jit.ret();
+    void* code = m_jit.copy();
+    ASSERT(code);
+    
+    for (unsigned i = 0; i < failureCases.size(); ++i)
+        X86Assembler::link(code, failureCases[i], reinterpret_cast<void*>(Machine::cti_op_put_by_id_fail));
+
+    if (transitionWillNeedStorageRealloc(oldStructureID, newStructureID))
+        X86Assembler::link(code, callTarget, reinterpret_cast<void*>(transitionObject));
+    
+    m_codeBlock->structureIDAccessStubs.append(code);
+    
+    return code;
+}
+
 void* CTI::privateArrayLengthTrampoline()
 {
     // Check eax is an array
index 2f8f995..c426941 100644 (file)
@@ -259,6 +259,12 @@ namespace JSC {
             CTI cti(machine, exec, codeBlock);
             return cti.privateCompilePutByIdReplace(structureID, cachedOffset);
         }
+        
+        static void* compilePutByIdTransition(Machine* machine, ExecState* exec, CodeBlock* codeBlock, StructureID* oldStructureID, StructureID* newStructureID, size_t cachedOffset, StructureIDChain* sIDC)
+        {
+            CTI cti(machine, exec, codeBlock);
+            return cti.privateCompilePutByIdTransition(oldStructureID, newStructureID, cachedOffset, sIDC);
+        }
 
         static void* compileArrayLengthTrampoline(Machine* machine, ExecState* exec, CodeBlock* codeBlock)
         {
@@ -291,6 +297,7 @@ namespace JSC {
         void* privateCompileGetByIdProto(ExecState*, StructureID*, StructureID* prototypeStructureID, size_t cachedOffset);
         void* privateCompileGetByIdChain(ExecState*, StructureID*, StructureIDChain*, size_t count, size_t cachedOffset);
         void* privateCompilePutByIdReplace(StructureID*, size_t cachedOffset);
+        void* privateCompilePutByIdTransition(StructureID*, StructureID*, size_t cachedOffset, StructureIDChain*);
         void* privateArrayLengthTrampoline();
         void* privateStringLengthTrampoline();
 
index a8a2b6d..3803f25 100644 (file)
@@ -172,7 +172,7 @@ static void printPutByIdOp(int location, Vector<Instruction>::const_iterator& it
     int id0 = (++it)->u.operand;
     int r1 = (++it)->u.operand;
     printf("[%4d] %s\t %s, %s, %s\n", location, op, registerName(r0).c_str(), idName(id0, identifiers[id0]).c_str(), registerName(r1).c_str());
-    it += 2;
+    it += 4;
 }
 
 void CodeBlock::printStructureID(const char* name, const Instruction* vPC, int operand) const
@@ -198,6 +198,10 @@ void CodeBlock::printStructureIDs(const Instruction* vPC) const
         printf("  [%4d] %s: %s, %s\n", instructionOffset, "get_by_id_proto", pointerToSourceString(vPC[4].u.structureID).UTF8String().c_str(), pointerToSourceString(vPC[5].u.structureID).UTF8String().c_str());
         return;
     }
+    if (vPC[0].u.opcode == machine->getOpcode(op_put_by_id_transition)) {
+        printf("  [%4d] %s: %s, %s, %s\n", instructionOffset, "put_by_id_new", pointerToSourceString(vPC[4].u.structureID).UTF8String().c_str(), pointerToSourceString(vPC[5].u.structureID).UTF8String().c_str(), pointerToSourceString(vPC[6].u.structureIDChain).UTF8String().c_str());
+        return;
+    }
     if (vPC[0].u.opcode == machine->getOpcode(op_get_by_id_chain)) {
         printf("  [%4d] %s: %s, %s\n", instructionOffset, "get_by_id_chain", pointerToSourceString(vPC[4].u.structureID).UTF8String().c_str(), pointerToSourceString(vPC[5].u.structureIDChain).UTF8String().c_str());
         return;
@@ -592,6 +596,10 @@ void CodeBlock::dump(ExecState* exec, const Vector<Instruction>::const_iterator&
             printPutByIdOp(location, it, identifiers, "put_by_id_replace");
             break;
         }
+        case op_put_by_id_transition: {
+            printPutByIdOp(location, it, identifiers, "put_by_id_transition");
+            break;
+        }
         case op_put_by_id_generic: {
             printPutByIdOp(location, it, identifiers, "put_by_id_generic");
             break;
@@ -863,6 +871,12 @@ void CodeBlock::derefStructureIDs(Instruction* vPC) const
         vPC[5].u.structureIDChain->deref();
         return;
     }
+    if (vPC[0].u.opcode == machine->getOpcode(op_put_by_id_transition)) {
+        vPC[4].u.structureID->deref();
+        vPC[5].u.structureID->deref();
+        vPC[6].u.structureIDChain->deref();
+        return;
+    }
     if (vPC[0].u.opcode == machine->getOpcode(op_put_by_id_replace)) {
         vPC[4].u.structureID->deref();
         return;
@@ -890,6 +904,12 @@ void CodeBlock::refStructureIDs(Instruction* vPC) const
         vPC[5].u.structureIDChain->ref();
         return;
     }
+    if (vPC[0].u.opcode == machine->getOpcode(op_put_by_id_transition)) {
+        vPC[4].u.structureID->ref();
+        vPC[5].u.structureID->ref();
+        vPC[6].u.structureIDChain->ref();
+        return;
+    }
     if (vPC[0].u.opcode == machine->getOpcode(op_put_by_id_replace)) {
         vPC[4].u.structureID->ref();
         return;
index f66bd8f..08654ad 100644 (file)
@@ -836,6 +836,8 @@ RegisterID* CodeGenerator::emitPutById(RegisterID* base, const Identifier& prope
     instructions().append(value->index());
     instructions().append(0);
     instructions().append(0);
+    instructions().append(0);
+    instructions().append(0);
     return value;
 }
 
index 1755731..7bd6c6a 100644 (file)
@@ -1107,12 +1107,15 @@ static NEVER_INLINE ScopeChainNode* createExceptionScope(ExecState* exec, CodeBl
 
 static StructureIDChain* cachePrototypeChain(ExecState* exec, StructureID* structureID)
 {
-    RefPtr<StructureIDChain> chain = StructureIDChain::create(static_cast<JSObject*>(structureID->prototypeForLookup(exec))->structureID());
+    JSValue* prototype = structureID->prototypeForLookup(exec);
+    if (JSImmediate::isImmediate(prototype))
+        return 0;
+    RefPtr<StructureIDChain> chain = StructureIDChain::create(static_cast<JSObject*>(prototype)->structureID());
     structureID->setCachedPrototypeChain(chain.release());
     return structureID->cachedPrototypeChain();
 }
 
-NEVER_INLINE void Machine::tryCachePutByID(CodeBlock* codeBlock, Instruction* vPC, JSValue* baseValue, const PutPropertySlot& slot)
+NEVER_INLINE void Machine::tryCachePutByID(ExecState* exec, CodeBlock* codeBlock, Instruction* vPC, JSValue* baseValue, const PutPropertySlot& slot)
 {
     // Recursive invocation may already have specialized this instruction.
     if (vPC[0].u.opcode != getOpcode(op_put_by_id))
@@ -1126,12 +1129,6 @@ NEVER_INLINE void Machine::tryCachePutByID(CodeBlock* codeBlock, Instruction* vP
         vPC[0] = getOpcode(op_put_by_id_generic);
         return;
     }
-
-    // FIXME: Cache new property transitions, too.
-    if (slot.type() == PutPropertySlot::NewProperty) {
-        vPC[0] = getOpcode(op_put_by_id_generic);
-        return;
-    }
     
     JSCell* baseCell = static_cast<JSCell*>(baseValue);
     StructureID* structureID = baseCell->structureID();
@@ -1162,6 +1159,27 @@ NEVER_INLINE void Machine::tryCachePutByID(CodeBlock* codeBlock, Instruction* vP
         vPC[0] = getOpcode(op_put_by_id_generic);
         return;
     }
+
+    // StructureID transition, cache transition info
+    if (slot.type() == PutPropertySlot::NewProperty) {
+        vPC[0] = getOpcode(op_put_by_id_transition);
+        vPC[4] = structureID->previousID();
+        vPC[5] = structureID;
+        StructureIDChain* chain = structureID->cachedPrototypeChain();
+        if (!chain) {
+            chain = cachePrototypeChain(exec, structureID);
+            if (!chain) {
+                // This happens if someone has manually inserted null into the prototype chain
+                vPC[0] = getOpcode(op_put_by_id_generic);
+                return;
+            }
+        }
+        vPC[6] = chain;
+        vPC[7] = slot.cachedOffset();
+        codeBlock->refStructureIDs(vPC);
+        return;
+    }
+
     vPC[0] = getOpcode(op_put_by_id_replace);
     vPC[5] = slot.cachedOffset();
     codeBlock->refStructureIDs(vPC);
@@ -1282,6 +1300,7 @@ NEVER_INLINE void Machine::tryCacheGetByID(ExecState* exec, CodeBlock* codeBlock
     StructureIDChain* chain = structureID->cachedPrototypeChain();
     if (!chain)
         chain = cachePrototypeChain(exec, structureID);
+    ASSERT(chain);
 
     vPC[0] = getOpcode(op_get_by_id_chain);
     vPC[4] = structureID;
@@ -2404,7 +2423,7 @@ JSValue* Machine::privateExecute(ExecutionFlag flag, ExecState* exec, RegisterFi
         NEXT_OPCODE;
     }
     BEGIN_OPCODE(op_put_by_id) {
-        /* put_by_id base(r) property(id) value(r) nop(n) nop(n)
+        /* put_by_id base(r) property(id) value(r) nop(n) nop(n) nop(n) nop(n)
 
            Generic property access: Sets the property named by identifier
            property, belonging to register base, to register value.
@@ -2424,13 +2443,65 @@ JSValue* Machine::privateExecute(ExecutionFlag flag, ExecState* exec, RegisterFi
         baseValue->put(exec, ident, r[value].jsValue(exec), slot);
         VM_CHECK_EXCEPTION();
 
-        tryCachePutByID(codeBlock, vPC, baseValue, slot);
+        tryCachePutByID(exec, codeBlock, vPC, baseValue, slot);
 
-        vPC += 6;
+        vPC += 8;
+        NEXT_OPCODE;
+    }
+    BEGIN_OPCODE(op_put_by_id_transition) {
+        /* op_put_by_id_transition base(r) property(id) value(r) oldStructureID(sID) newStructureID(sID) structureIDChain(sIDc) offset(n)
+         
+           Cached property access: Attempts to set a new property with a cached transition
+           property named by identifier property, belonging to register base,
+           to register value. If the cache misses, op_put_by_id_transition
+           reverts to op_put_by_id_generic.
+         
+           Unlike many opcodes, this one does not write any output to
+           the register file.
+         */
+        int base = vPC[1].u.operand;
+        JSValue* baseValue = r[base].jsValue(exec);
+        
+        if (LIKELY(!JSImmediate::isImmediate(baseValue))) {
+            JSCell* baseCell = static_cast<JSCell*>(baseValue);
+            StructureID* oldStructureID = vPC[4].u.structureID;
+            StructureID* newStructureID = vPC[5].u.structureID;
+            
+            if (LIKELY(baseCell->structureID() == oldStructureID)) {
+                ASSERT(baseCell->isObject());
+                JSObject* baseObject = static_cast<JSObject*>(baseCell);
+
+                RefPtr<StructureID>* it = vPC[6].u.structureIDChain->head();
+
+                JSObject* proto = static_cast<JSObject*>(baseObject->structureID()->prototypeForLookup(exec));
+                while (!proto->isNull()) {
+                    if (UNLIKELY(proto->structureID() != (*it).get())) {
+                        uncachePutByID(codeBlock, vPC);
+                        NEXT_OPCODE;
+                    }
+                    ++it;
+                    proto = static_cast<JSObject*>(proto->structureID()->prototypeForLookup(exec));
+                }
+
+                baseObject->transitionTo(newStructureID);
+                if (oldStructureID->propertyMap().storageSize() == JSObject::inlineStorageCapacity)
+                    baseObject->allocatePropertyStorage(oldStructureID->propertyMap().storageSize(), oldStructureID->propertyMap().size());
+
+                int value = vPC[3].u.operand;
+                unsigned offset = vPC[7].u.operand;
+                ASSERT(baseObject->offsetForLocation(baseObject->getDirectLocation(codeBlock->identifiers[vPC[2].u.operand])) == offset);
+                baseObject->putDirectOffset(offset, r[value].jsValue(exec));
+
+                vPC += 8;
+                NEXT_OPCODE;
+            }
+        }
+        
+        uncachePutByID(codeBlock, vPC);
         NEXT_OPCODE;
     }
     BEGIN_OPCODE(op_put_by_id_replace) {
-        /* op_put_by_id_replace base(r) property(id) value(r) structureID(sID) offset(n)
+        /* op_put_by_id_replace base(r) property(id) value(r) structureID(sID) offset(n) nop(n) nop(n)
 
            Cached property access: Attempts to set a pre-existing, cached
            property named by identifier property, belonging to register base,
@@ -2456,7 +2527,7 @@ JSValue* Machine::privateExecute(ExecutionFlag flag, ExecState* exec, RegisterFi
                 ASSERT(baseObject->offsetForLocation(baseObject->getDirectLocation(codeBlock->identifiers[vPC[2].u.operand])) == offset);
                 baseObject->putDirectOffset(offset, r[value].jsValue(exec));
 
-                vPC += 6;
+                vPC += 8;
                 NEXT_OPCODE;
             }
         }
@@ -2465,7 +2536,7 @@ JSValue* Machine::privateExecute(ExecutionFlag flag, ExecState* exec, RegisterFi
         NEXT_OPCODE;
     }
     BEGIN_OPCODE(op_put_by_id_generic) {
-        /* op_put_by_id_generic base(r) property(id) value(r) nop(n) nop(n)
+        /* op_put_by_id_generic base(r) property(id) value(r) nop(n) nop(n) nop(n) nop(n)
 
            Generic property access: Sets the property named by identifier
            property, belonging to register base, to register value.
@@ -2484,7 +2555,7 @@ JSValue* Machine::privateExecute(ExecutionFlag flag, ExecState* exec, RegisterFi
         baseValue->put(exec, ident, r[value].jsValue(exec), slot);
         VM_CHECK_EXCEPTION();
 
-        vPC += 6;
+        vPC += 8;
         NEXT_OPCODE;
     }
     BEGIN_OPCODE(op_del_by_id) {
@@ -3556,12 +3627,6 @@ NEVER_INLINE void Machine::tryCTICachePutByID(ExecState* exec, CodeBlock* codeBl
         ctiRepatchCallByReturnAddress(returnAddress, (void*)cti_op_put_by_id_generic);
         return;
     }
-
-    // FIXME: Cache new property transitions, too.
-    if (slot.type() == PutPropertySlot::NewProperty) {
-        ctiRepatchCallByReturnAddress(returnAddress, (void*)cti_op_put_by_id_generic);
-        return;
-    }
     
     JSCell* baseCell = static_cast<JSCell*>(baseValue);
     StructureID* structureID = baseCell->structureID();
@@ -3584,6 +3649,28 @@ NEVER_INLINE void Machine::tryCTICachePutByID(ExecState* exec, CodeBlock* codeBl
         ctiRepatchCallByReturnAddress(returnAddress, (void*)cti_op_put_by_id_generic);
         return;
     }
+
+    // StructureID transition, cache transition info
+    if (slot.type() == PutPropertySlot::NewProperty) {
+        vPC[0] = getOpcode(op_put_by_id_transition);
+        vPC[4] = structureID->previousID();
+        vPC[5] = structureID;
+        StructureIDChain* chain = structureID->cachedPrototypeChain();
+        if (!chain) {
+            chain = cachePrototypeChain(exec, structureID);
+            if (!chain) {
+                // This happens if someone has manually inserted null into the prototype chain
+                vPC[0] = getOpcode(op_put_by_id_generic);
+                return;
+            }
+        }
+        vPC[6] = chain;
+        vPC[7] = slot.cachedOffset();
+        codeBlock->refStructureIDs(vPC);
+        ctiRepatchCallByReturnAddress(returnAddress, CTI::compilePutByIdTransition(this, exec, codeBlock, structureID->previousID(), structureID, slot.cachedOffset(), chain));
+        return;
+    }
+    
     vPC[0] = getOpcode(op_put_by_id_replace);
     vPC[4] = structureID;
     vPC[5] = slot.cachedOffset();
@@ -3713,6 +3800,7 @@ NEVER_INLINE void Machine::tryCTICacheGetByID(ExecState* exec, CodeBlock* codeBl
     if (!chain)
         chain = cachePrototypeChain(exec, structureID);
 
+    ASSERT(chain);
     vPC[0] = getOpcode(op_get_by_id_chain);
     vPC[4] = structureID;
     vPC[5] = chain;
index 075a988..4a3da7f 100644 (file)
@@ -254,7 +254,7 @@ namespace JSC {
         
         void tryCacheGetByID(ExecState*, CodeBlock*, Instruction* vPC, JSValue* baseValue, const Identifier& propertyName, const PropertySlot&);
         void uncacheGetByID(CodeBlock*, Instruction* vPC);
-        void tryCachePutByID(CodeBlock*, Instruction* vPC, JSValue* baseValue, const PutPropertySlot&);
+        void tryCachePutByID(ExecState* exec, CodeBlock*, Instruction* vPC, JSValue* baseValue, const PutPropertySlot&);
         void uncachePutByID(CodeBlock*, Instruction* vPC);
 
 #if ENABLE(CTI)
index 772733c..d8c6edd 100644 (file)
@@ -97,6 +97,7 @@ namespace JSC {
         macro(op_get_array_length) \
         macro(op_get_string_length) \
         macro(op_put_by_id) \
+        macro(op_put_by_id_transition) \
         macro(op_put_by_id_replace) \
         macro(op_put_by_id_generic) \
         macro(op_del_by_id) \
index 003fddc..db49841 100644 (file)
@@ -150,6 +150,8 @@ namespace JSC {
             return &m_propertyStorage[offset];
         }
 
+        void transitionTo(StructureID*);
+
         void removeDirect(const Identifier& propertyName);
         bool hasCustomProperties() { return !m_structureID->propertyMap().isEmpty(); }
         bool hasGetterSetterProperties() { return m_structureID->propertyMap().hasGetterSetterProperties(); }
@@ -178,6 +180,8 @@ namespace JSC {
         void allocatePropertyStorage(size_t oldSize, size_t newSize);
         bool usingInlineStorage() const { return m_propertyStorage == m_inlineStorage; }
 
+        static const size_t inlineStorageCapacity = 2;
+
     protected:
         bool getOwnPropertySlotForWrite(ExecState*, const Identifier&, PropertySlot&, bool& slotIsWriteable);
 
@@ -185,8 +189,6 @@ namespace JSC {
         const HashEntry* findPropertyHashEntry(ExecState*, const Identifier& propertyName) const;
         StructureID* createInheritorID();
 
-        static const size_t inlineStorageCapacity = 2;
-
         RefPtr<StructureID> m_inheritorID;
 
         PropertyStorage m_propertyStorage;        
@@ -389,23 +391,30 @@ inline void JSObject::putDirect(const Identifier& propertyName, JSValue* value,
          return;
      }
 
-     unsigned currentAttributes;
-     size_t offset = m_structureID->propertyMap().getOffset(propertyName, currentAttributes);
-     if (offset != WTF::notFound) {
-         if (checkReadOnly && currentAttributes & ReadOnly)
-             return;
-         m_propertyStorage[offset] = value;
-         slot.setExistingProperty(this, offset);
-         return;
-     }
+    unsigned currentAttributes;
+    size_t offset = m_structureID->propertyMap().getOffset(propertyName, currentAttributes);
+    if (offset != WTF::notFound) {
+        if (checkReadOnly && currentAttributes & ReadOnly)
+            return;
+        m_propertyStorage[offset] = value;
+        slot.setExistingProperty(this, offset);
+        return;
+    }
 
      if (m_structureID->propertyMap().storageSize() == inlineStorageCapacity)
          allocatePropertyStorage(m_structureID->propertyMap().storageSize(), m_structureID->propertyMap().size());
 
      RefPtr<StructureID> structureID = StructureID::addPropertyTransition(m_structureID, propertyName, value, attributes, this, slot, m_propertyStorage);
+     slot.setWasTransition(true);
      setStructureID(structureID.release());
 }
 
+inline void JSObject::transitionTo(StructureID* newStructureID)
+{
+    StructureID::transitionTo(m_structureID, newStructureID, this);
+    setStructureID(newStructureID);
+}
+
 inline JSValue* JSObject::toPrimitive(ExecState* exec, PreferredPrimitiveType preferredType) const
 {
     return defaultValue(exec, preferredType);
index fea6d99..1e2dfe9 100644 (file)
@@ -40,6 +40,7 @@ namespace JSC {
         PutPropertySlot()
             : m_type(Invalid)
             , m_base(0)
+            , m_wasTransition(false)
         {
         }
 
@@ -65,10 +66,13 @@ namespace JSC {
             ASSERT(isCacheable());
             return m_offset;
         }
-
+        
+        bool wasTransition() const { return m_wasTransition; }
+        void setWasTransition(bool wasTransition) { m_wasTransition = wasTransition; }
     private:
         Type m_type;
         JSObject* m_base;
+        bool m_wasTransition;
         size_t m_offset;
     };
 
index fbf7daa..0f35379 100644 (file)
@@ -47,6 +47,12 @@ namespace JSC {
     ASSERT(m_prototype->isObject() || m_prototype->isNull());
 }
 
+void StructureID::transitionTo(StructureID* oldStructureID, StructureID* newStructureID, JSObject* slotBase)
+{
+    if (!slotBase->usingInlineStorage() && oldStructureID->m_propertyMap.size() != newStructureID->m_propertyMap.size())
+        slotBase->allocatePropertyStorage(oldStructureID->m_propertyMap.size(), newStructureID->m_propertyMap.size());
+}
+
 PassRefPtr<StructureID> StructureID::addPropertyTransition(StructureID* structureID, const Identifier& propertyName, JSValue* value, unsigned attributes, JSObject* slotBase, PutPropertySlot& slot, PropertyStorage& propertyStorage)
 {
     ASSERT(!structureID->m_isDictionary);
@@ -139,7 +145,7 @@ StructureIDChain::StructureIDChain(StructureID* structureID)
         tmp = static_cast<JSCell*>(tmp->storedPrototype())->structureID();
     }
     
-    m_vector.set(new RefPtr<StructureID>[size]);
+    m_vector.set(new RefPtr<StructureID>[size + 1]);
 
     size_t i;
     for (i = 0; i < size - 1; ++i) {
@@ -147,6 +153,7 @@ StructureIDChain::StructureIDChain(StructureID* structureID)
         structureID = static_cast<JSObject*>(structureID->storedPrototype())->structureID();
     }
     m_vector[i] = structureID;
+    m_vector[i + 1] = 0;
 }
 
 } // namespace JSC
index 7637fa9..346aa7d 100644 (file)
@@ -72,6 +72,7 @@ namespace JSC {
 
     class StructureID : public RefCounted<StructureID> {
     public:
+        friend class CTI;
         static PassRefPtr<StructureID> create(JSValue* prototype, JSType type = ObjectType)
         {
             return adoptRef(new StructureID(prototype, type));
@@ -97,13 +98,17 @@ namespace JSC {
 
         JSValue* storedPrototype() const { return m_prototype; }
         JSValue* prototypeForLookup(ExecState*); 
-        
+
+        StructureID* previousID() const { return m_previous.get(); }
+
         void setCachedPrototypeChain(PassRefPtr<StructureIDChain> cachedPrototypeChain) { m_cachedPrototypeChain = cachedPrototypeChain; }
         StructureIDChain* cachedPrototypeChain() const { return m_cachedPrototypeChain.get(); }
 
         const PropertyMap& propertyMap() const { return m_propertyMap; }
         PropertyMap& propertyMap() { return m_propertyMap; }
 
+        static void transitionTo(StructureID* oldStructureID, StructureID* newStructureID, JSObject* slotBase);
+
     private:
         typedef std::pair<RefPtr<UString::Rep>, unsigned> TransitionTableKey;
         typedef HashMap<TransitionTableKey, StructureID*, TransitionTableHash, TransitionTableHashTraits> TransitionTable;
index bda055c..a0f80e0 100644 (file)
@@ -291,6 +291,13 @@ public:
         m_buffer->putByte(imm);
     }
 
+    void addl_i8m(int imm, void* addr)
+    {
+        m_buffer->putByte(OP_GROUP1_EvIb);
+        emitModRm_opm(GROUP1_OP_ADD, addr);
+        m_buffer->putByte(imm);
+    }
+
     void addl_i32r(int imm, RegisterID dst)
     {
         m_buffer->putByte(OP_GROUP1_EvIz);
@@ -396,6 +403,13 @@ public:
         emitModRm_opr(GROUP1_OP_SUB, dst);
         m_buffer->putByte(imm);
     }
+    
+    void subl_i8m(int imm, void* addr)
+    {
+        m_buffer->putByte(OP_GROUP1_EvIb);
+        emitModRm_opm(GROUP1_OP_SUB, addr);
+        m_buffer->putByte(imm);
+    }
 
     void subl_i32r(int imm, RegisterID dst)
     {