Title: [137683] trunk/Source/_javascript_Core
Revision
137683
Author
[email protected]
Date
2012-12-13 16:32:55 -0800 (Thu, 13 Dec 2012)

Log Message

Support op_typeof in the DFG
https://bugs.webkit.org/show_bug.cgi?id=98898

Reviewed by Filip Pizlo.

Adds a TypeOf node to the DFG to support op_typeof.

* dfg/DFGAbstractState.cpp:
(JSC::DFG::AbstractState::execute):
  We try to determine the result early here, and substitute in a constant.
  Otherwise we leave the node intact, and set the result type to SpecString.
* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::parseBlock):
  Parse op_typeof
* dfg/DFGCSEPhase.cpp:
(JSC::DFG::CSEPhase::performNodeCSE):
  TypeOf nodes can be subjected to pure CSE
* dfg/DFGCapabilities.h:
(JSC::DFG::canCompileOpcode):
  We can handle typeof.
* dfg/DFGNodeType.h:
(DFG):
  Define the node.
* dfg/DFGOperations.cpp:
* dfg/DFGOperations.h:
  Add operationTypeOf to support the non-trivial cases.
* dfg/DFGPredictionPropagationPhase.cpp:
(JSC::DFG::PredictionPropagationPhase::propagate):
* dfg/DFGSpeculativeJIT32_64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* dfg/DFGSpeculativeJIT64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
  Actual codegen
* runtime/Operations.cpp:
(JSC::jsTypeStringForValue):
(JSC):
* runtime/Operations.h:
(JSC):
  Some refactoring to allow us to get the type string for an
  object without needing a callframe.

Modified Paths

Diff

Modified: trunk/Source/_javascript_Core/ChangeLog (137682 => 137683)


--- trunk/Source/_javascript_Core/ChangeLog	2012-12-14 00:30:25 UTC (rev 137682)
+++ trunk/Source/_javascript_Core/ChangeLog	2012-12-14 00:32:55 UTC (rev 137683)
@@ -1,3 +1,46 @@
+2012-13-11  Oliver Hunt  <[email protected]>
+
+        Support op_typeof in the DFG
+        https://bugs.webkit.org/show_bug.cgi?id=98898
+
+        Reviewed by Filip Pizlo.
+
+        Adds a TypeOf node to the DFG to support op_typeof. 
+
+        * dfg/DFGAbstractState.cpp:
+        (JSC::DFG::AbstractState::execute):
+          We try to determine the result early here, and substitute in a constant.
+          Otherwise we leave the node intact, and set the result type to SpecString.
+        * dfg/DFGByteCodeParser.cpp:
+        (JSC::DFG::ByteCodeParser::parseBlock):
+          Parse op_typeof
+        * dfg/DFGCSEPhase.cpp:
+        (JSC::DFG::CSEPhase::performNodeCSE):
+          TypeOf nodes can be subjected to pure CSE
+        * dfg/DFGCapabilities.h:
+        (JSC::DFG::canCompileOpcode):
+          We can handle typeof.
+        * dfg/DFGNodeType.h:
+        (DFG):
+          Define the node.
+        * dfg/DFGOperations.cpp:
+        * dfg/DFGOperations.h:
+          Add operationTypeOf to support the non-trivial cases.
+        * dfg/DFGPredictionPropagationPhase.cpp:
+        (JSC::DFG::PredictionPropagationPhase::propagate):
+        * dfg/DFGSpeculativeJIT32_64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * dfg/DFGSpeculativeJIT64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+          Actual codegen
+        * runtime/Operations.cpp:
+        (JSC::jsTypeStringForValue):
+        (JSC):
+        * runtime/Operations.h:
+        (JSC):
+          Some refactoring to allow us to get the type string for an
+          object without needing a callframe.
+
 2012-12-12  Filip Pizlo  <[email protected]>
 
         OSR exit compiler should emit code for resetting the execution counter that matches the logic of ExecutionCounter.cpp

Modified: trunk/Source/_javascript_Core/dfg/DFGAbstractState.cpp (137682 => 137683)


--- trunk/Source/_javascript_Core/dfg/DFGAbstractState.cpp	2012-12-14 00:30:25 UTC (rev 137682)
+++ trunk/Source/_javascript_Core/dfg/DFGAbstractState.cpp	2012-12-14 00:32:55 UTC (rev 137683)
@@ -31,6 +31,7 @@
 #include "CodeBlock.h"
 #include "DFGBasicBlock.h"
 #include "GetByIdStatus.h"
+#include "Operations.h"
 #include "PutByIdStatus.h"
 
 namespace JSC { namespace DFG {
@@ -707,6 +708,11 @@
             case IsString:
                 constantWasSet = trySetConstant(nodeIndex, jsBoolean(isJSString(child)));
                 break;
+            case IsObject:
+                if (child.isNull() || !child.isObject()) {
+                    constantWasSet = trySetConstant(nodeIndex, jsBoolean(child.isNull()));
+                    break;
+                }
             default:
                 constantWasSet = false;
                 break;
@@ -716,9 +722,64 @@
                 break;
             }
         }
+
         forNode(nodeIndex).set(SpecBoolean);
         break;
     }
+
+    case TypeOf: {
+        JSGlobalData* globalData = m_codeBlock->globalData();
+        JSValue child = forNode(node.child1()).value();
+        AbstractValue& abstractChild = forNode(node.child1());
+        if (child) {
+            JSValue typeString = jsTypeStringForValue(*globalData, m_codeBlock->globalObjectFor(node.codeOrigin), child);
+            if (trySetConstant(nodeIndex, typeString)) {
+                m_foundConstants = true;
+                break;
+            }
+        } else if (isNumberSpeculation(abstractChild.m_type)) {
+            if (trySetConstant(nodeIndex, globalData->smallStrings.numberString(globalData))) {
+                forNode(node.child1()).filter(SpecNumber);
+                m_foundConstants = true;
+                break;
+            }
+        } else if (isStringSpeculation(abstractChild.m_type)) {
+            if (trySetConstant(nodeIndex, globalData->smallStrings.stringString(globalData))) {
+                forNode(node.child1()).filter(SpecString);
+                m_foundConstants = true;
+                break;
+            }
+        } else if (isFinalObjectSpeculation(abstractChild.m_type) || isArraySpeculation(abstractChild.m_type) || isArgumentsSpeculation(abstractChild.m_type)) {
+            if (trySetConstant(nodeIndex, globalData->smallStrings.objectString(globalData))) {
+                forNode(node.child1()).filter(SpecFinalObject | SpecArray | SpecArguments);
+                m_foundConstants = true;
+                break;
+            }
+        } else if (isFunctionSpeculation(abstractChild.m_type)) {
+            if (trySetConstant(nodeIndex, globalData->smallStrings.functionString(globalData))) {
+                forNode(node.child1()).filter(SpecFunction);
+                m_foundConstants = true;
+                break;
+            }
+        } else if (isBooleanSpeculation(abstractChild.m_type)) {
+            if (trySetConstant(nodeIndex, globalData->smallStrings.booleanString(globalData))) {
+                forNode(node.child1()).filter(SpecBoolean);
+                m_foundConstants = true;
+                break;
+            }
+        } else {
+            Node& childNode = m_graph[node.child1()];
+            if (isCellSpeculation(childNode.prediction())) {
+                if (isStringSpeculation(childNode.prediction()))
+                    forNode(node.child1()).filter(SpecString);
+                else
+                    forNode(node.child1()).filter(SpecCell);
+                node.setCanExit(true);
+            }
+        }
+        forNode(nodeIndex).set(SpecString);
+        break;
+    }
             
     case CompareLess:
     case CompareLessEq:

Modified: trunk/Source/_javascript_Core/dfg/DFGByteCodeParser.cpp (137682 => 137683)


--- trunk/Source/_javascript_Core/dfg/DFGByteCodeParser.cpp	2012-12-14 00:30:25 UTC (rev 137682)
+++ trunk/Source/_javascript_Core/dfg/DFGByteCodeParser.cpp	2012-12-14 00:32:55 UTC (rev 137683)
@@ -3255,6 +3255,12 @@
             NEXT_OPCODE(op_new_func_exp);
         }
 
+        case op_typeof: {
+            set(currentInstruction[1].u.operand,
+                addToGraph(TypeOf, get(currentInstruction[2].u.operand)));
+            NEXT_OPCODE(op_typeof);
+        }
+
         default:
             // Parse failed! This should not happen because the capabilities checker
             // should have caught it.

Modified: trunk/Source/_javascript_Core/dfg/DFGCSEPhase.cpp (137682 => 137683)


--- trunk/Source/_javascript_Core/dfg/DFGCSEPhase.cpp	2012-12-14 00:30:25 UTC (rev 137682)
+++ trunk/Source/_javascript_Core/dfg/DFGCSEPhase.cpp	2012-12-14 00:32:55 UTC (rev 137683)
@@ -562,6 +562,7 @@
             case CreateThis:
             case AllocatePropertyStorage:
             case ReallocatePropertyStorage:
+            case TypeOf:
                 return NoNode;
                 
             case GetIndexedPropertyStorage:
@@ -1127,6 +1128,7 @@
         case SkipTopScope:
         case SkipScope:
         case GetScopeRegisters:
+        case TypeOf:
             setReplacement(pureCSE(node));
             break;
             

Modified: trunk/Source/_javascript_Core/dfg/DFGCapabilities.h (137682 => 137683)


--- trunk/Source/_javascript_Core/dfg/DFGCapabilities.h	2012-12-14 00:30:25 UTC (rev 137682)
+++ trunk/Source/_javascript_Core/dfg/DFGCapabilities.h	2012-12-14 00:32:55 UTC (rev 137683)
@@ -196,6 +196,7 @@
     case op_jneq_ptr:
     case op_put_to_base_variable:
     case op_put_to_base:
+    case op_typeof:
         return CanCompile;
         
     case op_call_varargs:

Modified: trunk/Source/_javascript_Core/dfg/DFGNodeType.h (137682 => 137683)


--- trunk/Source/_javascript_Core/dfg/DFGNodeType.h	2012-12-14 00:30:25 UTC (rev 137682)
+++ trunk/Source/_javascript_Core/dfg/DFGNodeType.h	2012-12-14 00:32:55 UTC (rev 137683)
@@ -210,6 +210,7 @@
     macro(IsString, NodeResultBoolean) \
     macro(IsObject, NodeResultBoolean) \
     macro(IsFunction, NodeResultBoolean) \
+    macro(TypeOf, NodeResultJS) \
     macro(LogicalNot, NodeResultBoolean) \
     macro(ToPrimitive, NodeResultJS | NodeMustGenerate | NodeClobbersWorld) \
     macro(StrCat, NodeResultJS | NodeMustGenerate | NodeHasVarArgs | NodeClobbersWorld) \

Modified: trunk/Source/_javascript_Core/dfg/DFGOperations.cpp (137682 => 137683)


--- trunk/Source/_javascript_Core/dfg/DFGOperations.cpp	2012-12-14 00:30:25 UTC (rev 137682)
+++ trunk/Source/_javascript_Core/dfg/DFGOperations.cpp	2012-12-14 00:32:55 UTC (rev 137683)
@@ -1370,6 +1370,11 @@
     return jsIsFunctionType(JSValue::decode(value));
 }
 
+JSCell* DFG_OPERATION operationTypeOf(ExecState* exec, JSCell* value)
+{
+    return jsTypeStringForValue(exec, JSValue(value)).asCell();
+}
+
 void DFG_OPERATION operationReallocateStorageAndFinishPut(ExecState* exec, JSObject* base, Structure* structure, PropertyOffset offset, EncodedJSValue value)
 {
     JSGlobalData& globalData = exec->globalData();

Modified: trunk/Source/_javascript_Core/dfg/DFGOperations.h (137682 => 137683)


--- trunk/Source/_javascript_Core/dfg/DFGOperations.h	2012-12-14 00:30:25 UTC (rev 137682)
+++ trunk/Source/_javascript_Core/dfg/DFGOperations.h	2012-12-14 00:32:55 UTC (rev 137683)
@@ -198,6 +198,7 @@
 double DFG_OPERATION operationFModOnInts(int32_t, int32_t) WTF_INTERNAL;
 size_t DFG_OPERATION operationIsObject(ExecState*, EncodedJSValue) WTF_INTERNAL;
 size_t DFG_OPERATION operationIsFunction(EncodedJSValue) WTF_INTERNAL;
+JSCell* DFG_OPERATION operationTypeOf(ExecState*, JSCell*) WTF_INTERNAL;
 void DFG_OPERATION operationReallocateStorageAndFinishPut(ExecState*, JSObject*, Structure*, PropertyOffset, EncodedJSValue) WTF_INTERNAL;
 char* DFG_OPERATION operationAllocatePropertyStorageWithInitialCapacity(ExecState*) WTF_INTERNAL;
 char* DFG_OPERATION operationAllocatePropertyStorage(ExecState*, size_t newSize) WTF_INTERNAL;

Modified: trunk/Source/_javascript_Core/dfg/DFGPredictionPropagationPhase.cpp (137682 => 137683)


--- trunk/Source/_javascript_Core/dfg/DFGPredictionPropagationPhase.cpp	2012-12-14 00:30:25 UTC (rev 137682)
+++ trunk/Source/_javascript_Core/dfg/DFGPredictionPropagationPhase.cpp	2012-12-14 00:32:55 UTC (rev 137683)
@@ -497,7 +497,13 @@
             changed |= mergeDefaultFlags(node);
             break;
         }
-            
+
+        case TypeOf: {
+            changed |= setPrediction(SpecString);
+            changed |= mergeDefaultFlags(node);
+            break;
+        }
+
         case GetById: {
             changed |= mergePrediction(node.getHeapPrediction());
             changed |= mergeDefaultFlags(node);

Modified: trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT32_64.cpp (137682 => 137683)


--- trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT32_64.cpp	2012-12-14 00:30:25 UTC (rev 137682)
+++ trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT32_64.cpp	2012-12-14 00:32:55 UTC (rev 137683)
@@ -4558,7 +4558,67 @@
         booleanResult(result.gpr(), m_compileIndex);
         break;
     }
+    case TypeOf: {
+        JSValueOperand value(this, node.child1());
+        GPRReg tagGPR = value.tagGPR();
+        GPRReg payloadGPR = value.payloadGPR();
+        GPRTemporary temp(this);
+        GPRReg tempGPR = temp.gpr();
+        GPRResult result(this);
+        GPRReg resultGPR = result.gpr();
+        JITCompiler::JumpList doneJumps;
 
+        flushRegisters();
+
+        JITCompiler::Jump isNotCell = m_jit.branch32(JITCompiler::NotEqual, tagGPR, JITCompiler::TrustedImm32(JSValue::CellTag));
+        Node& child = m_jit.graph()[node.child1()];
+        if (child.shouldSpeculateCell())
+            speculationCheck(BadType, JSValueRegs(tagGPR, payloadGPR), node.child1(), isNotCell);
+
+        if (!child.shouldSpeculateNonStringCell()) {
+            m_jit.loadPtr(JITCompiler::Address(payloadGPR, JSCell::structureOffset()), tempGPR);
+            JITCompiler::Jump notString = m_jit.branch8(JITCompiler::NotEqual, JITCompiler::Address(tempGPR, Structure::typeInfoTypeOffset()), TrustedImm32(StringType));
+            if (child.shouldSpeculateString())
+                speculationCheck(BadType, JSValueRegs(tagGPR, payloadGPR), node.child1(), notString);
+            m_jit.move(TrustedImmPtr(m_jit.globalData()->smallStrings.stringString(m_jit.globalData())), resultGPR);
+            doneJumps.append(m_jit.jump());
+            if (!child.shouldSpeculateString()) {
+                notString.link(&m_jit);
+                callOperation(operationTypeOf, resultGPR, payloadGPR);
+                doneJumps.append(m_jit.jump());
+            }
+        } else {
+            callOperation(operationTypeOf, resultGPR, payloadGPR);
+            doneJumps.append(m_jit.jump());
+        }
+
+        if (!child.shouldSpeculateCell()) {
+            isNotCell.link(&m_jit);
+
+            m_jit.add32(TrustedImm32(1), tagGPR, tempGPR);
+            JITCompiler::Jump notNumber = m_jit.branch32(JITCompiler::AboveOrEqual, tempGPR, JITCompiler::TrustedImm32(JSValue::LowestTag + 1));
+            m_jit.move(TrustedImmPtr(m_jit.globalData()->smallStrings.numberString(m_jit.globalData())), resultGPR);
+            doneJumps.append(m_jit.jump());
+            notNumber.link(&m_jit);
+
+            JITCompiler::Jump notUndefined = m_jit.branch32(JITCompiler::NotEqual, tagGPR, TrustedImm32(JSValue::UndefinedTag));
+            m_jit.move(TrustedImmPtr(m_jit.globalData()->smallStrings.undefinedString(m_jit.globalData())), resultGPR);
+            doneJumps.append(m_jit.jump());
+            notUndefined.link(&m_jit);
+
+            JITCompiler::Jump notNull = m_jit.branch32(JITCompiler::NotEqual, tagGPR, TrustedImm32(JSValue::NullTag));
+            m_jit.move(TrustedImmPtr(m_jit.globalData()->smallStrings.objectString(m_jit.globalData())), resultGPR);
+            doneJumps.append(m_jit.jump());
+            notNull.link(&m_jit);
+
+            // Only boolean left
+            m_jit.move(TrustedImmPtr(m_jit.globalData()->smallStrings.booleanString(m_jit.globalData())), resultGPR);
+        }
+        doneJumps.link(&m_jit);
+        cellResult(resultGPR, m_compileIndex);
+        break;
+    }
+
     case Phi:
     case Flush:
         break;

Modified: trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT64.cpp (137682 => 137683)


--- trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT64.cpp	2012-12-14 00:30:25 UTC (rev 137682)
+++ trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT64.cpp	2012-12-14 00:32:55 UTC (rev 137683)
@@ -4069,7 +4069,7 @@
         cachedGetById(node.codeOrigin, baseGPR, resultGPR, node.identifierNumber(), notCell, DontSpill);
         
         jsValueResult(resultGPR, m_compileIndex, UseChildrenCalledExplicitly);
-        
+
         break;
     }
 
@@ -4484,6 +4484,64 @@
         break;
     }
 
+    case TypeOf: {
+        JSValueOperand value(this, node.child1());
+        GPRReg valueGPR = value.gpr();
+        GPRTemporary temp(this);
+        GPRReg tempGPR = temp.gpr();
+        GPRResult result(this);
+        GPRReg resultGPR = result.gpr();
+        JITCompiler::JumpList doneJumps;
+
+        flushRegisters();
+
+        JITCompiler::Jump isNotCell = m_jit.branchTest64(JITCompiler::NonZero, valueGPR, GPRInfo::tagMaskRegister);
+        Node& child = m_jit.graph()[node.child1()];
+        if (child.shouldSpeculateCell())
+            speculationCheck(BadType, JSValueSource(valueGPR), node.child1(), isNotCell);
+
+        if (!child.shouldSpeculateNonStringCell()) {
+            m_jit.loadPtr(JITCompiler::Address(valueGPR, JSCell::structureOffset()), tempGPR);
+            JITCompiler::Jump notString = m_jit.branch8(JITCompiler::NotEqual, JITCompiler::Address(tempGPR, Structure::typeInfoTypeOffset()), TrustedImm32(StringType));
+            if (child.shouldSpeculateString())
+                speculationCheck(BadType, JSValueSource(valueGPR), node.child1(), notString);
+            m_jit.move(TrustedImmPtr(m_jit.globalData()->smallStrings.stringString(m_jit.globalData())), resultGPR);
+            doneJumps.append(m_jit.jump());
+            if (!child.shouldSpeculateString()) {
+                notString.link(&m_jit);
+                callOperation(operationTypeOf, resultGPR, valueGPR);
+                doneJumps.append(m_jit.jump());
+            }
+        } else {
+            callOperation(operationTypeOf, resultGPR, valueGPR);
+            doneJumps.append(m_jit.jump());
+        }
+
+        if (!child.shouldSpeculateCell()) {
+            isNotCell.link(&m_jit);
+            JITCompiler::Jump notNumber = m_jit.branchTest64(JITCompiler::Zero, valueGPR, GPRInfo::tagTypeNumberRegister);
+            m_jit.move(TrustedImmPtr(m_jit.globalData()->smallStrings.numberString(m_jit.globalData())), resultGPR);
+            doneJumps.append(m_jit.jump());
+            notNumber.link(&m_jit);
+
+            JITCompiler::Jump notUndefined = m_jit.branch64(JITCompiler::NotEqual, valueGPR, JITCompiler::TrustedImm64(ValueUndefined));
+            m_jit.move(TrustedImmPtr(m_jit.globalData()->smallStrings.undefinedString(m_jit.globalData())), resultGPR);
+            doneJumps.append(m_jit.jump());
+            notUndefined.link(&m_jit);
+
+            JITCompiler::Jump notNull = m_jit.branch64(JITCompiler::NotEqual, valueGPR, JITCompiler::TrustedImm64(ValueNull));
+            m_jit.move(TrustedImmPtr(m_jit.globalData()->smallStrings.objectString(m_jit.globalData())), resultGPR);
+            doneJumps.append(m_jit.jump());
+            notNull.link(&m_jit);
+
+            // Only boolean left
+            m_jit.move(TrustedImmPtr(m_jit.globalData()->smallStrings.booleanString(m_jit.globalData())), resultGPR);
+        }
+        doneJumps.link(&m_jit);
+        cellResult(resultGPR, m_compileIndex);
+        break;
+    }
+
     case Flush:
     case Phi:
         break;

Modified: trunk/Source/_javascript_Core/runtime/Operations.cpp (137682 => 137683)


--- trunk/Source/_javascript_Core/runtime/Operations.cpp	2012-12-14 00:30:25 UTC (rev 137682)
+++ trunk/Source/_javascript_Core/runtime/Operations.cpp	2012-12-14 00:32:55 UTC (rev 137683)
@@ -56,9 +56,8 @@
     return jsNumber(p1.toNumber(callFrame) + p2.toNumber(callFrame));
 }
 
-JSValue jsTypeStringForValue(CallFrame* callFrame, JSValue v)
+JSValue jsTypeStringForValue(JSGlobalData& globalData, JSGlobalObject* globalObject, JSValue v)
 {
-    JSGlobalData& globalData = callFrame->globalData();
     if (v.isUndefined())
         return globalData.smallStrings.undefinedString(&globalData);
     if (v.isBoolean())
@@ -70,7 +69,7 @@
     if (v.isObject()) {
         // Return "undefined" for objects that should be treated
         // as null when doing comparisons.
-        if (asObject(v)->structure()->masqueradesAsUndefined(callFrame->lexicalGlobalObject()))
+        if (asObject(v)->structure()->masqueradesAsUndefined(globalObject))
             return globalData.smallStrings.undefinedString(&globalData);
         CallData callData;
         JSObject* object = asObject(v);
@@ -80,6 +79,11 @@
     return globalData.smallStrings.objectString(&globalData);
 }
 
+JSValue jsTypeStringForValue(CallFrame* callFrame, JSValue v)
+{
+    return jsTypeStringForValue(callFrame->globalData(), callFrame->lexicalGlobalObject(), v);
+}
+
 bool jsIsObjectType(CallFrame* callFrame, JSValue v)
 {
     if (!v.isCell())

Modified: trunk/Source/_javascript_Core/runtime/Operations.h (137682 => 137683)


--- trunk/Source/_javascript_Core/runtime/Operations.h	2012-12-14 00:30:25 UTC (rev 137682)
+++ trunk/Source/_javascript_Core/runtime/Operations.h	2012-12-14 00:32:55 UTC (rev 137683)
@@ -32,6 +32,7 @@
 
     NEVER_INLINE JSValue jsAddSlowCase(CallFrame*, JSValue, JSValue);
     JSValue jsTypeStringForValue(CallFrame*, JSValue);
+    JSValue jsTypeStringForValue(JSGlobalData&, JSGlobalObject*, JSValue);
     bool jsIsObjectType(CallFrame*, JSValue);
     bool jsIsFunctionType(JSValue);
 
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to