Title: [89986] trunk/Source/_javascript_Core
Revision
89986
Author
[email protected]
Date
2011-06-28 20:18:58 -0700 (Tue, 28 Jun 2011)

Log Message

DFG JIT lacks array.length caching.
https://bugs.webkit.org/show_bug.cgi?id=63505

Patch by Filip Pizlo <[email protected]> on 2011-06-28
Reviewed by Gavin Barraclough.

* bytecode/StructureStubInfo.h:
* dfg/DFGJITCodeGenerator.cpp:
(JSC::DFG::JITCodeGenerator::cachedGetById):
(JSC::DFG::JITCodeGenerator::cachedPutById):
* dfg/DFGJITCodeGenerator.h:
(JSC::DFG::JITCodeGenerator::tryAllocate):
(JSC::DFG::JITCodeGenerator::selectScratchGPR):
(JSC::DFG::JITCodeGenerator::silentSpillAllRegisters):
* dfg/DFGJITCompiler.cpp:
(JSC::DFG::JITCompiler::compileFunction):
* dfg/DFGJITCompiler.h:
(JSC::DFG::JITCompiler::addPropertyAccess):
(JSC::DFG::JITCompiler::PropertyAccessRecord::PropertyAccessRecord):
* dfg/DFGRegisterBank.h:
(JSC::DFG::RegisterBank::tryAllocate):
* dfg/DFGRepatch.cpp:
(JSC::DFG::tryCacheGetByID):

Modified Paths

Diff

Modified: trunk/Source/_javascript_Core/ChangeLog (89985 => 89986)


--- trunk/Source/_javascript_Core/ChangeLog	2011-06-29 02:41:47 UTC (rev 89985)
+++ trunk/Source/_javascript_Core/ChangeLog	2011-06-29 03:18:58 UTC (rev 89986)
@@ -1,3 +1,28 @@
+2011-06-28  Filip Pizlo  <[email protected]>
+
+        Reviewed by Gavin Barraclough.
+
+        DFG JIT lacks array.length caching.
+        https://bugs.webkit.org/show_bug.cgi?id=63505
+
+        * bytecode/StructureStubInfo.h:
+        * dfg/DFGJITCodeGenerator.cpp:
+        (JSC::DFG::JITCodeGenerator::cachedGetById):
+        (JSC::DFG::JITCodeGenerator::cachedPutById):
+        * dfg/DFGJITCodeGenerator.h:
+        (JSC::DFG::JITCodeGenerator::tryAllocate):
+        (JSC::DFG::JITCodeGenerator::selectScratchGPR):
+        (JSC::DFG::JITCodeGenerator::silentSpillAllRegisters):
+        * dfg/DFGJITCompiler.cpp:
+        (JSC::DFG::JITCompiler::compileFunction):
+        * dfg/DFGJITCompiler.h:
+        (JSC::DFG::JITCompiler::addPropertyAccess):
+        (JSC::DFG::JITCompiler::PropertyAccessRecord::PropertyAccessRecord):
+        * dfg/DFGRegisterBank.h:
+        (JSC::DFG::RegisterBank::tryAllocate):
+        * dfg/DFGRepatch.cpp:
+        (JSC::DFG::tryCacheGetByID):
+
 2011-06-28  Pierre Rossi  <[email protected]>
 
         Reviewed by Eric Seidel.

Modified: trunk/Source/_javascript_Core/bytecode/StructureStubInfo.h (89985 => 89986)


--- trunk/Source/_javascript_Core/bytecode/StructureStubInfo.h	2011-06-29 02:41:47 UTC (rev 89985)
+++ trunk/Source/_javascript_Core/bytecode/StructureStubInfo.h	2011-06-29 03:18:58 UTC (rev 89986)
@@ -132,8 +132,14 @@
 
         union {
             struct {
-                intptr_t deltaCheckToCall;
-                intptr_t deltaCallToLoadOrStore;
+                int8_t deltaCheckImmToCall;
+                int8_t deltaCallToStructCheck;
+                int8_t deltaCallToLoadOrStore;
+                int8_t deltaCallToSlowCase;
+                int8_t deltaCallToDone;
+                int8_t baseGPR;
+                int8_t valueGPR;
+                int8_t scratchGPR;
             } unset;
             struct {
                 WriteBarrierBase<Structure> baseObjectStructure;

Modified: trunk/Source/_javascript_Core/dfg/DFGJITCodeGenerator.cpp (89985 => 89986)


--- trunk/Source/_javascript_Core/dfg/DFGJITCodeGenerator.cpp	2011-06-29 02:41:47 UTC (rev 89985)
+++ trunk/Source/_javascript_Core/dfg/DFGJITCodeGenerator.cpp	2011-06-29 03:18:58 UTC (rev 89986)
@@ -322,11 +322,27 @@
 
 void JITCodeGenerator::cachedGetById(GPRReg baseGPR, GPRReg resultGPR, unsigned identifierNumber, JITCompiler::Jump slowPathTarget)
 {
+    GPRReg scratchGPR;
+    
+    if (resultGPR == baseGPR)
+        scratchGPR = tryAllocate();
+    else
+        scratchGPR = resultGPR;
+    
     JITCompiler::DataLabelPtr structureToCompare;
-    JITCompiler::Jump structureCheck = m_jit.branchPtrWithPatch(JITCompiler::Equal, JITCompiler::Address(baseGPR, JSCell::structureOffset()), structureToCompare, JITCompiler::TrustedImmPtr(reinterpret_cast<void*>(-1)));
+    JITCompiler::Jump structureCheck = m_jit.branchPtrWithPatch(JITCompiler::NotEqual, JITCompiler::Address(baseGPR, JSCell::structureOffset()), structureToCompare, JITCompiler::TrustedImmPtr(reinterpret_cast<void*>(-1)));
     
+    m_jit.loadPtr(JITCompiler::Address(baseGPR, JSObject::offsetOfPropertyStorage()), resultGPR);
+    JITCompiler::DataLabelCompact loadWithPatch = m_jit.loadPtrWithCompactAddressOffsetPatch(JITCompiler::Address(resultGPR, 0), resultGPR);
+    
+    JITCompiler::Jump done = m_jit.jump();
+
+    structureCheck.link(&m_jit);
+    
     if (slowPathTarget.isSet())
         slowPathTarget.link(&m_jit);
+    
+    JITCompiler::Label slowCase = m_jit.label();
 
     silentSpillAllRegisters(resultGPR, baseGPR);
     m_jit.move(baseGPR, GPRInfo::argumentGPR1);
@@ -335,19 +351,21 @@
     JITCompiler::Call functionCall = appendCallWithExceptionCheck(operationGetByIdOptimize);
     m_jit.move(GPRInfo::returnValueGPR, resultGPR);
     silentFillAllRegisters(resultGPR);
+    
+    done.link(&m_jit);
+    
+    JITCompiler::Label doneLabel = m_jit.label();
 
-    JITCompiler::Jump handledByC = m_jit.jump();
-    structureCheck.link(&m_jit);
-
-    m_jit.loadPtr(JITCompiler::Address(baseGPR, JSObject::offsetOfPropertyStorage()), resultGPR);
-    JITCompiler::DataLabelCompact loadWithPatch = m_jit.loadPtrWithCompactAddressOffsetPatch(JITCompiler::Address(resultGPR, 0), resultGPR);
-
-    intptr_t checkToCall = m_jit.differenceBetween(structureToCompare, functionCall);
-    intptr_t callToLoad = m_jit.differenceBetween(functionCall, loadWithPatch);
-
-    handledByC.link(&m_jit);
-
-    m_jit.addPropertyAccess(functionCall, checkToCall, callToLoad);
+    int8_t checkImmToCall = static_cast<int8_t>(m_jit.differenceBetween(structureToCompare, functionCall));
+    int8_t callToCheck = static_cast<int8_t>(m_jit.differenceBetween(functionCall, structureCheck));
+    int8_t callToLoad = static_cast<int8_t>(m_jit.differenceBetween(functionCall, loadWithPatch));
+    int8_t callToSlowCase = static_cast<int8_t>(m_jit.differenceBetween(functionCall, slowCase));
+    int8_t callToDone = static_cast<int8_t>(m_jit.differenceBetween(functionCall, doneLabel));
+    
+    m_jit.addPropertyAccess(functionCall, checkImmToCall, callToCheck, callToLoad, callToSlowCase, callToDone, static_cast<int8_t>(baseGPR), static_cast<int8_t>(resultGPR), static_cast<int8_t>(scratchGPR));
+    
+    if (scratchGPR != resultGPR && scratchGPR != InvalidGPRReg)
+        unlock(scratchGPR);
 }
 
 void JITCodeGenerator::cachedPutById(GPRReg baseGPR, GPRReg valueGPR, GPRReg scratchGPR, unsigned identifierNumber, PutKind putKind, JITCompiler::Jump slowPathTarget)
@@ -383,12 +401,13 @@
     m_jit.loadPtr(JITCompiler::Address(baseGPR, JSObject::offsetOfPropertyStorage()), scratchGPR);
     JITCompiler::DataLabel32 storeWithPatch = m_jit.storePtrWithAddressOffsetPatch(valueGPR, JITCompiler::Address(scratchGPR, 0));
 
-    intptr_t checkToCall = m_jit.differenceBetween(structureToCompare, functionCall);
-    intptr_t callToStore = m_jit.differenceBetween(functionCall, storeWithPatch);
+    int8_t checkImmToCall = static_cast<int8_t>(m_jit.differenceBetween(structureToCompare, functionCall));
+    int8_t callToCheck = static_cast<int8_t>(m_jit.differenceBetween(functionCall, structureCheck));
+    int8_t callToStore = static_cast<int8_t>(m_jit.differenceBetween(functionCall, storeWithPatch));
 
     handledByC.link(&m_jit);
 
-    m_jit.addPropertyAccess(functionCall, checkToCall, callToStore);
+    m_jit.addPropertyAccess(functionCall, checkImmToCall, callToCheck, callToStore);
 }
 
 #ifndef NDEBUG

Modified: trunk/Source/_javascript_Core/dfg/DFGJITCodeGenerator.h (89985 => 89986)


--- trunk/Source/_javascript_Core/dfg/DFGJITCodeGenerator.h	2011-06-29 02:41:47 UTC (rev 89985)
+++ trunk/Source/_javascript_Core/dfg/DFGJITCodeGenerator.h	2011-06-29 03:18:58 UTC (rev 89986)
@@ -124,6 +124,10 @@
             spill(spillMe);
         return specific;
     }
+    GPRReg tryAllocate()
+    {
+        return m_gprs.tryAllocate();
+    }
     FPRReg fprAllocate()
     {
         VirtualRegister spillMe;
@@ -151,6 +155,20 @@
         return info.registerFormat() == DataFormatDouble;
     }
 
+    static GPRReg selectScratchGPR(GPRReg preserve1 = InvalidGPRReg, GPRReg preserve2 = InvalidGPRReg, GPRReg preserve3 = InvalidGPRReg)
+    {
+        if (preserve1 != GPRInfo::regT0 && preserve2 != GPRInfo::regT0 && preserve3 != GPRInfo::regT0)
+            return GPRInfo::regT0;
+
+        if (preserve1 != GPRInfo::regT1 && preserve2 != GPRInfo::regT1 && preserve3 != GPRInfo::regT1)
+            return GPRInfo::regT1;
+
+        if (preserve1 != GPRInfo::regT2 && preserve2 != GPRInfo::regT2 && preserve3 != GPRInfo::regT2)
+            return GPRInfo::regT2;
+
+        return GPRInfo::regT3;
+    }
+
 protected:
     JITCodeGenerator(JITCompiler& jit, bool isSpeculative)
         : m_jit(jit)
@@ -244,18 +262,10 @@
             unboxDouble(canTrample, info.fpr());
         }
     }
-
+    
     void silentSpillAllRegisters(GPRReg exclude, GPRReg preserve1 = InvalidGPRReg, GPRReg preserve2 = InvalidGPRReg, GPRReg preserve3 = InvalidGPRReg)
     {
-        GPRReg canTrample = GPRInfo::regT0;
-        if (preserve1 != GPRInfo::regT0 && preserve2 != GPRInfo::regT0 && preserve3 != GPRInfo::regT0)
-            canTrample = GPRInfo::regT0;
-        else if (preserve1 != GPRInfo::regT1 && preserve2 != GPRInfo::regT1 && preserve3 != GPRInfo::regT1)
-            canTrample = GPRInfo::regT1;
-        else if (preserve1 != GPRInfo::regT2 && preserve2 != GPRInfo::regT2 && preserve3 != GPRInfo::regT2)
-            canTrample = GPRInfo::regT2;
-        else
-            canTrample = GPRInfo::regT3;
+        GPRReg canTrample = selectScratchGPR(preserve1, preserve2, preserve3);
         
         for (gpr_iterator iter = m_gprs.begin(); iter != m_gprs.end(); ++iter) {
             if (iter.name() != InvalidVirtualRegister)

Modified: trunk/Source/_javascript_Core/dfg/DFGJITCompiler.cpp (89985 => 89986)


--- trunk/Source/_javascript_Core/dfg/DFGJITCompiler.cpp	2011-06-29 02:41:47 UTC (rev 89985)
+++ trunk/Source/_javascript_Core/dfg/DFGJITCompiler.cpp	2011-06-29 03:18:58 UTC (rev 89986)
@@ -387,8 +387,14 @@
     for (unsigned i = 0; i < m_propertyAccesses.size(); ++i) {
         StructureStubInfo& info = m_codeBlock->structureStubInfo(i);
         info.callReturnLocation = linkBuffer.locationOf(m_propertyAccesses[i].m_functionCall);
-        info.u.unset.deltaCheckToCall = m_propertyAccesses[i].m_deltaCheckToCall;
+        info.u.unset.deltaCheckImmToCall = m_propertyAccesses[i].m_deltaCheckImmToCall;
+        info.u.unset.deltaCallToStructCheck = m_propertyAccesses[i].m_deltaCallToStructCheck;
         info.u.unset.deltaCallToLoadOrStore = m_propertyAccesses[i].m_deltaCallToLoadOrStore;
+        info.u.unset.deltaCallToSlowCase = m_propertyAccesses[i].m_deltaCallToSlowCase;
+        info.u.unset.deltaCallToDone = m_propertyAccesses[i].m_deltaCallToDone;
+        info.u.unset.baseGPR = m_propertyAccesses[i].m_baseGPR;
+        info.u.unset.valueGPR = m_propertyAccesses[i].m_valueGPR;
+        info.u.unset.scratchGPR = m_propertyAccesses[i].m_scratchGPR;
     }
 
     // FIXME: switch the register file check & arity check over to DFGOpertaion style calls, not JIT stubs.

Modified: trunk/Source/_javascript_Core/dfg/DFGJITCompiler.h (89985 => 89986)


--- trunk/Source/_javascript_Core/dfg/DFGJITCompiler.h	2011-06-29 02:41:47 UTC (rev 89985)
+++ trunk/Source/_javascript_Core/dfg/DFGJITCompiler.h	2011-06-29 03:18:58 UTC (rev 89986)
@@ -233,9 +233,9 @@
     void clearSamplingFlag(int32_t flag);
 #endif
 
-    void addPropertyAccess(JITCompiler::Call functionCall, intptr_t deltaCheckToCall, intptr_t deltaCallToLoadOrStore)
+    void addPropertyAccess(JITCompiler::Call functionCall, int16_t deltaCheckImmToCall, int16_t deltaCallToStructCheck, int16_t deltaCallToLoadOrStore, int16_t deltaCallToSlowCase = 0, int16_t deltaCallToDone = 0, int8_t baseGPR = 0, int8_t valueGPR = 0, int8_t scratchGPR = 0)
     {
-        m_propertyAccesses.append(PropertyAccessRecord(functionCall, deltaCheckToCall, deltaCallToLoadOrStore));
+        m_propertyAccesses.append(PropertyAccessRecord(functionCall, deltaCheckImmToCall, deltaCallToStructCheck, deltaCallToLoadOrStore, deltaCallToSlowCase, deltaCallToDone,  baseGPR, valueGPR, scratchGPR));
     }
 
 private:
@@ -259,16 +259,28 @@
     Vector<CallRecord> m_calls;
 
     struct PropertyAccessRecord {
-        PropertyAccessRecord(JITCompiler::Call functionCall, intptr_t deltaCheckToCall, intptr_t deltaCallToLoadOrStore)
+        PropertyAccessRecord(JITCompiler::Call functionCall, int8_t deltaCheckImmToCall, int8_t deltaCallToStructCheck, int8_t deltaCallToLoadOrStore, int8_t deltaCallToSlowCase, int8_t deltaCallToDone, int8_t baseGPR, int8_t valueGPR, int8_t scratchGPR)
             : m_functionCall(functionCall)
-            , m_deltaCheckToCall(deltaCheckToCall)
+            , m_deltaCheckImmToCall(deltaCheckImmToCall)
+            , m_deltaCallToStructCheck(deltaCallToStructCheck)
             , m_deltaCallToLoadOrStore(deltaCallToLoadOrStore)
+            , m_deltaCallToSlowCase(deltaCallToSlowCase)
+            , m_deltaCallToDone(deltaCallToDone)
+            , m_baseGPR(baseGPR)
+            , m_valueGPR(valueGPR)
+            , m_scratchGPR(scratchGPR)
         {
         }
 
         JITCompiler::Call m_functionCall;
-        intptr_t m_deltaCheckToCall;
-        intptr_t m_deltaCallToLoadOrStore;
+        int8_t m_deltaCheckImmToCall;
+        int8_t m_deltaCallToStructCheck;
+        int8_t m_deltaCallToLoadOrStore;
+        int8_t m_deltaCallToSlowCase;
+        int8_t m_deltaCallToDone;
+        int8_t m_baseGPR;
+        int8_t m_valueGPR;
+        int8_t m_scratchGPR;
     };
 
     Vector<PropertyAccessRecord, 4> m_propertyAccesses;

Modified: trunk/Source/_javascript_Core/dfg/DFGRegisterBank.h (89985 => 89986)


--- trunk/Source/_javascript_Core/dfg/DFGRegisterBank.h	2011-06-29 02:41:47 UTC (rev 89985)
+++ trunk/Source/_javascript_Core/dfg/DFGRegisterBank.h	2011-06-29 03:18:58 UTC (rev 89986)
@@ -79,6 +79,26 @@
     {
     }
 
+    // Attempt to allocate a register - this function finds an unlocked
+    // register, locks it, and returns it. If none can be found, this
+    // returns -1 (InvalidGPRReg or InvalidFPRReg).
+    RegID tryAllocate()
+    {
+        VirtualRegister ignored;
+        
+        for (uint32_t i = m_lastAllocated + 1; i < NUM_REGS; ++i) {
+            if (!m_data[i].lockCount && m_data[i].name == InvalidVirtualRegister)
+                return allocateInternal(i, ignored);
+        }
+        // Loop over the remaining entries.
+        for (uint32_t i = 0; i <= m_lastAllocated; ++i) {
+            if (!m_data[i].lockCount && m_data[i].name == InvalidVirtualRegister)
+                return allocateInternal(i, ignored);
+        }
+        
+        return (RegID)-1;
+    }
+
     // Allocate a register - this function finds an unlocked register,
     // locks it, and returns it. If any named registers exist, one
     // of these should be selected to be allocated. If all unlocked

Modified: trunk/Source/_javascript_Core/dfg/DFGRepatch.cpp (89985 => 89986)


--- trunk/Source/_javascript_Core/dfg/DFGRepatch.cpp	2011-06-29 02:41:47 UTC (rev 89985)
+++ trunk/Source/_javascript_Core/dfg/DFGRepatch.cpp	2011-06-29 03:18:58 UTC (rev 89986)
@@ -28,6 +28,8 @@
 
 #if ENABLE(DFG_JIT)
 
+#include "DFGJITCodeGenerator.h"
+#include "LinkBuffer.h"
 #include "RepatchBuffer.h"
 
 namespace JSC { namespace DFG {
@@ -46,22 +48,86 @@
     repatchBuffer.relink(stubInfo.callReturnLocation, slowPathFunction);
 
     // Patch the structure check & the offset of the load.
-    repatchBuffer.repatch(stubInfo.callReturnLocation.dataLabelPtrAtOffset(-(intptr_t)stubInfo.u.unset.deltaCheckToCall), structure);
+    repatchBuffer.repatch(stubInfo.callReturnLocation.dataLabelPtrAtOffset(-(intptr_t)stubInfo.u.unset.deltaCheckImmToCall), structure);
     if (compact)
         repatchBuffer.repatch(stubInfo.callReturnLocation.dataLabelCompactAtOffset(stubInfo.u.unset.deltaCallToLoadOrStore), sizeof(JSValue) * offset);
     else
         repatchBuffer.repatch(stubInfo.callReturnLocation.dataLabel32AtOffset(stubInfo.u.unset.deltaCallToLoadOrStore), sizeof(JSValue) * offset);
 }
 
-static bool tryCacheGetByID(ExecState* exec, JSValue baseValue, const Identifier&, const PropertySlot& slot, StructureStubInfo& stubInfo)
+static bool tryCacheGetByID(ExecState* exec, JSValue baseValue, const Identifier& propertyName, const PropertySlot& slot, StructureStubInfo& stubInfo)
 {
     // FIXME: Write a test that proves we need to check for recursion here just
     // like the interpreter does, then add a check for recursion.
 
     CodeBlock* codeBlock = exec->codeBlock();
     JSGlobalData* globalData = &exec->globalData();
+    
+    if (isJSArray(globalData, baseValue) && propertyName == exec->propertyNames().length) {
+        GPRReg baseGPR = static_cast<GPRReg>(stubInfo.u.unset.baseGPR);
+        GPRReg resultGPR = static_cast<GPRReg>(stubInfo.u.unset.valueGPR);
+        GPRReg scratchGPR = static_cast<GPRReg>(stubInfo.u.unset.scratchGPR);
+        bool needToRestoreScratch = false;
+        
+        MacroAssembler stubJit;
+        
+        if (scratchGPR == InvalidGPRReg) {
+            scratchGPR = JITCodeGenerator::selectScratchGPR(baseGPR, resultGPR);
+            stubJit.push(scratchGPR);
+            needToRestoreScratch = true;
+        }
+        
+        MacroAssembler::Jump failureCase1 = stubJit.branchPtr(MacroAssembler::NotEqual, MacroAssembler::Address(baseGPR), MacroAssembler::TrustedImmPtr(globalData->jsArrayVPtr));
+        
+        stubJit.loadPtr(MacroAssembler::Address(baseGPR, JSArray::storageOffset()), scratchGPR);
+        stubJit.load32(MacroAssembler::Address(scratchGPR, OBJECT_OFFSETOF(ArrayStorage, m_length)), scratchGPR);
+        MacroAssembler::Jump failureCase2 = stubJit.branch32(MacroAssembler::LessThan, scratchGPR, MacroAssembler::TrustedImm32(0));
+        
+        stubJit.orPtr(GPRInfo::tagTypeNumberRegister, scratchGPR, resultGPR);
 
-    // FIXME: should support length access for Array & String.
+        MacroAssembler::Jump success, fail;
+        
+        if (needToRestoreScratch) {
+            stubJit.pop(scratchGPR);
+            
+            success = stubJit.jump();
+            
+            // link failure cases here, so we can pop scratchGPR, and then jump back.
+            failureCase1.link(&stubJit);
+            failureCase2.link(&stubJit);
+            
+            stubJit.pop(scratchGPR);
+            
+            fail = stubJit.jump();
+        } else
+            success = stubJit.jump();
+        
+        LinkBuffer patchBuffer(*globalData, &stubJit, codeBlock->executablePool());
+        
+        CodeLocationLabel slowCaseBegin = stubInfo.callReturnLocation.labelAtOffset(stubInfo.u.unset.deltaCallToSlowCase);
+        
+        patchBuffer.link(success, stubInfo.callReturnLocation.labelAtOffset(stubInfo.u.unset.deltaCallToDone));
+        
+        if (needToRestoreScratch)
+            patchBuffer.link(fail, slowCaseBegin);
+        else {
+            // link failure cases directly back to normal path
+            patchBuffer.link(failureCase1, slowCaseBegin);
+            patchBuffer.link(failureCase2, slowCaseBegin);
+        }
+        
+        CodeLocationLabel entryLabel = patchBuffer.finalizeCodeAddendum();
+        stubInfo.stubRoutine = entryLabel;
+        
+        CodeLocationLabel hotPathBegin = stubInfo.hotPathBegin;
+        RepatchBuffer repatchBuffer(codeBlock);
+        repatchBuffer.relink(stubInfo.callReturnLocation.jumpAtOffset(stubInfo.u.unset.deltaCallToStructCheck), entryLabel);
+        repatchBuffer.relink(stubInfo.callReturnLocation, operationGetById);
+        
+        return true;
+    }
+    
+    // FIXME: should support length access for String.
 
     // FIXME: Cache property access for immediates.
     if (!baseValue.isCell())
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo.cgi/webkit-changes

Reply via email to