Title: [95758] trunk/Source/_javascript_Core
Revision
95758
Author
fpi...@apple.com
Date
2011-09-22 15:42:54 -0700 (Thu, 22 Sep 2011)

Log Message

DFG JIT does not support to_primitive or strcat
https://bugs.webkit.org/show_bug.cgi?id=68582

Reviewed by Darin Adler.
        
This adds functional support for to_primitive and strcat. It focuses
on minimizing the amount of code emitted on to_primitive (if we know
that it is a primitive or can speculate cheaply, then we omit the
slow path) and on keeping the implementation of strcat simple while
leveraging whatever optimizations we have already. In particular,
unlike the Call and Construct nodes which require extending the size
of the DFG's callee registers, StrCat takes advantage of the fact
that no JS code can run while StrCat is in progress and uses a
scratch buffer, rather than the register file, to store the list of
values to concatenate. This was done mainly to keep the code simple,
but there are probably other benefits to keeping call frame sizes
down. Essentially, this patch ensures that the presence of an
op_strcat does not mess up any other optimizations we might do while
ensuring that if you do execute it, it'll work about as well as you'd
expect.
        
When combined with the previous patch for integer division, this is a
14% speed-up on Kraken. Without it, it would have been a 2% loss.

* assembler/AbstractMacroAssembler.h:
(JSC::AbstractMacroAssembler::TrustedImmPtr::TrustedImmPtr):
* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::parseBlock):
* dfg/DFGCapabilities.h:
(JSC::DFG::canCompileOpcode):
* dfg/DFGJITCodeGenerator.h:
(JSC::DFG::JITCodeGenerator::callOperation):
* dfg/DFGJITCompiler.cpp:
(JSC::DFG::JITCompiler::exitSpeculativeWithOSR):
* dfg/DFGNode.h:
* dfg/DFGOperations.cpp:
* dfg/DFGOperations.h:
* dfg/DFGPropagator.cpp:
(JSC::DFG::Propagator::propagateNodePredictions):
(JSC::DFG::Propagator::performNodeCSE):
* dfg/DFGSpeculativeJIT.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* runtime/JSGlobalData.cpp:
(JSC::JSGlobalData::JSGlobalData):
(JSC::JSGlobalData::~JSGlobalData):
* runtime/JSGlobalData.h:
(JSC::JSGlobalData::scratchBufferForSize):

Modified Paths

Diff

Modified: trunk/Source/_javascript_Core/ChangeLog (95757 => 95758)


--- trunk/Source/_javascript_Core/ChangeLog	2011-09-22 22:35:21 UTC (rev 95757)
+++ trunk/Source/_javascript_Core/ChangeLog	2011-09-22 22:42:54 UTC (rev 95758)
@@ -1,3 +1,53 @@
+2011-09-21  Filip Pizlo  <fpi...@apple.com>
+
+        DFG JIT does not support to_primitive or strcat
+        https://bugs.webkit.org/show_bug.cgi?id=68582
+
+        Reviewed by Darin Adler.
+        
+        This adds functional support for to_primitive and strcat. It focuses
+        on minimizing the amount of code emitted on to_primitive (if we know
+        that it is a primitive or can speculate cheaply, then we omit the
+        slow path) and on keeping the implementation of strcat simple while
+        leveraging whatever optimizations we have already. In particular,
+        unlike the Call and Construct nodes which require extending the size
+        of the DFG's callee registers, StrCat takes advantage of the fact
+        that no JS code can run while StrCat is in progress and uses a
+        scratch buffer, rather than the register file, to store the list of
+        values to concatenate. This was done mainly to keep the code simple,
+        but there are probably other benefits to keeping call frame sizes
+        down. Essentially, this patch ensures that the presence of an
+        op_strcat does not mess up any other optimizations we might do while
+        ensuring that if you do execute it, it'll work about as well as you'd
+        expect.
+        
+        When combined with the previous patch for integer division, this is a
+        14% speed-up on Kraken. Without it, it would have been a 2% loss.
+
+        * assembler/AbstractMacroAssembler.h:
+        (JSC::AbstractMacroAssembler::TrustedImmPtr::TrustedImmPtr):
+        * dfg/DFGByteCodeParser.cpp:
+        (JSC::DFG::ByteCodeParser::parseBlock):
+        * dfg/DFGCapabilities.h:
+        (JSC::DFG::canCompileOpcode):
+        * dfg/DFGJITCodeGenerator.h:
+        (JSC::DFG::JITCodeGenerator::callOperation):
+        * dfg/DFGJITCompiler.cpp:
+        (JSC::DFG::JITCompiler::exitSpeculativeWithOSR):
+        * dfg/DFGNode.h:
+        * dfg/DFGOperations.cpp:
+        * dfg/DFGOperations.h:
+        * dfg/DFGPropagator.cpp:
+        (JSC::DFG::Propagator::propagateNodePredictions):
+        (JSC::DFG::Propagator::performNodeCSE):
+        * dfg/DFGSpeculativeJIT.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * runtime/JSGlobalData.cpp:
+        (JSC::JSGlobalData::JSGlobalData):
+        (JSC::JSGlobalData::~JSGlobalData):
+        * runtime/JSGlobalData.h:
+        (JSC::JSGlobalData::scratchBufferForSize):
+
 2011-09-22  Filip Pizlo  <fpi...@apple.com>
 
         DFG JIT should support integer division

Modified: trunk/Source/_javascript_Core/assembler/AbstractMacroAssembler.h (95757 => 95758)


--- trunk/Source/_javascript_Core/assembler/AbstractMacroAssembler.h	2011-09-22 22:35:21 UTC (rev 95757)
+++ trunk/Source/_javascript_Core/assembler/AbstractMacroAssembler.h	2011-09-22 22:42:54 UTC (rev 95758)
@@ -161,7 +161,20 @@
             : m_value(value)
         {
         }
+        
+        // This is only here so that TrustedImmPtr(0) does not confuse the C++
+        // overload handling rules.
+        explicit TrustedImmPtr(int value)
+            : m_value(0)
+        {
+            ASSERT_UNUSED(value, !value);
+        }
 
+        explicit TrustedImmPtr(size_t value)
+            : m_value(reinterpret_cast<void*>(value))
+        {
+        }
+
         intptr_t asIntptr()
         {
             return reinterpret_cast<intptr_t>(m_value);

Modified: trunk/Source/_javascript_Core/dfg/DFGByteCodeParser.cpp (95757 => 95758)


--- trunk/Source/_javascript_Core/dfg/DFGByteCodeParser.cpp	2011-09-22 22:35:21 UTC (rev 95757)
+++ trunk/Source/_javascript_Core/dfg/DFGByteCodeParser.cpp	2011-09-22 22:42:54 UTC (rev 95758)
@@ -968,6 +968,21 @@
             set(currentInstruction[1].u.operand, addToGraph(LogicalNot, value));
             NEXT_OPCODE(op_not);
         }
+            
+        case op_to_primitive: {
+            NodeIndex value = get(currentInstruction[2].u.operand);
+            set(currentInstruction[1].u.operand, addToGraph(ToPrimitive, value));
+            NEXT_OPCODE(op_to_primitive);
+        }
+            
+        case op_strcat: {
+            int startOperand = currentInstruction[2].u.operand;
+            int numOperands = currentInstruction[3].u.operand;
+            for (int operandIdx = startOperand; operandIdx < startOperand + numOperands; ++operandIdx)
+                addVarArgChild(get(operandIdx));
+            set(currentInstruction[1].u.operand, addToGraph(Node::VarArg, StrCat, OpInfo(0), OpInfo(0)));
+            NEXT_OPCODE(op_strcat);
+        }
 
         case op_less: {
             NodeIndex op1 = get(currentInstruction[2].u.operand);

Modified: trunk/Source/_javascript_Core/dfg/DFGCapabilities.h (95757 => 95758)


--- trunk/Source/_javascript_Core/dfg/DFGCapabilities.h	2011-09-22 22:35:21 UTC (rev 95757)
+++ trunk/Source/_javascript_Core/dfg/DFGCapabilities.h	2011-09-22 22:42:54 UTC (rev 95758)
@@ -116,6 +116,8 @@
     case op_call_put_result:
     case op_resolve:
     case op_resolve_base:
+    case op_strcat:
+    case op_to_primitive:
     case op_throw:
     case op_throw_reference_error:
         return true;

Modified: trunk/Source/_javascript_Core/dfg/DFGJITCodeGenerator.h (95757 => 95758)


--- trunk/Source/_javascript_Core/dfg/DFGJITCodeGenerator.h	2011-09-22 22:35:21 UTC (rev 95757)
+++ trunk/Source/_javascript_Core/dfg/DFGJITCodeGenerator.h	2011-09-22 22:42:54 UTC (rev 95758)
@@ -889,6 +889,17 @@
     {
         callOperation((J_DFGOperation_EP)operation, result, identifier);
     }
+    void callOperation(J_DFGOperation_EPS operation, GPRReg result, void* pointer, size_t size)
+    {
+        ASSERT(isFlushed());
+
+        m_jit.move(JITCompiler::TrustedImmPtr(size), GPRInfo::argumentGPR2);
+        m_jit.move(JITCompiler::TrustedImmPtr(pointer), GPRInfo::argumentGPR1);
+        m_jit.move(GPRInfo::callFrameRegister, GPRInfo::argumentGPR0);
+
+        appendCallWithExceptionCheck(operation);
+        m_jit.move(GPRInfo::returnValueGPR, result);
+    }
     void callOperation(J_DFGOperation_EJP operation, GPRReg result, GPRReg arg1, void* pointer)
     {
         ASSERT(isFlushed());

Modified: trunk/Source/_javascript_Core/dfg/DFGJITCompiler.cpp (95757 => 95758)


--- trunk/Source/_javascript_Core/dfg/DFGJITCompiler.cpp	2011-09-22 22:35:21 UTC (rev 95757)
+++ trunk/Source/_javascript_Core/dfg/DFGJITCompiler.cpp	2011-09-22 22:42:54 UTC (rev 95758)
@@ -218,7 +218,7 @@
         }
     }
     
-    EncodedJSValue* scratchBuffer = static_cast<EncodedJSValue*>(globalData()->osrScratchBufferForSize(sizeof(EncodedJSValue) * (numberOfPoisonedVirtualRegisters + (numberOfDisplacedVirtualRegisters <= GPRInfo::numberOfRegisters ? 0 : numberOfDisplacedVirtualRegisters))));
+    EncodedJSValue* scratchBuffer = static_cast<EncodedJSValue*>(globalData()->scratchBufferForSize(sizeof(EncodedJSValue) * (numberOfPoisonedVirtualRegisters + (numberOfDisplacedVirtualRegisters <= GPRInfo::numberOfRegisters ? 0 : numberOfDisplacedVirtualRegisters))));
 
     // From here on, the code assumes that it is profitable to maximize the distance
     // between when something is computed and when it is stored.

Modified: trunk/Source/_javascript_Core/dfg/DFGNode.h (95757 => 95758)


--- trunk/Source/_javascript_Core/dfg/DFGNode.h	2011-09-22 22:35:21 UTC (rev 95757)
+++ trunk/Source/_javascript_Core/dfg/DFGNode.h	2011-09-22 22:42:54 UTC (rev 95758)
@@ -296,6 +296,8 @@
     macro(CheckHasInstance, NodeMustGenerate) \
     macro(InstanceOf, NodeResultBoolean) \
     macro(LogicalNot, NodeResultBoolean | NodeMightClobber) \
+    macro(ToPrimitive, NodeResultJS | NodeMustGenerate | NodeClobbersWorld) \
+    macro(StrCat, NodeResultJS | NodeMustGenerate | NodeHasVarArgs | NodeClobbersWorld) \
     \
     /* Block terminals. */\
     macro(Jump, NodeMustGenerate | NodeIsTerminal | NodeIsJump) \

Modified: trunk/Source/_javascript_Core/dfg/DFGOperations.cpp (95757 => 95758)


--- trunk/Source/_javascript_Core/dfg/DFGOperations.cpp	2011-09-22 22:35:21 UTC (rev 95757)
+++ trunk/Source/_javascript_Core/dfg/DFGOperations.cpp	2011-09-22 22:42:54 UTC (rev 95758)
@@ -674,6 +674,16 @@
     return JSValue::encode(base);
 }
 
+EncodedJSValue operationToPrimitive(ExecState* exec, EncodedJSValue value)
+{
+    return JSValue::encode(JSValue::decode(value).toPrimitive(exec));
+}
+
+EncodedJSValue operationStrCat(ExecState* exec, void* start, size_t size)
+{
+    return JSValue::encode(jsString(exec, static_cast<Register*>(start), size));
+}
+
 void operationThrowHasInstanceError(ExecState* exec, EncodedJSValue encodedBase)
 {
     JSValue base = JSValue::decode(encodedBase);

Modified: trunk/Source/_javascript_Core/dfg/DFGOperations.h (95757 => 95758)


--- trunk/Source/_javascript_Core/dfg/DFGOperations.h	2011-09-22 22:35:21 UTC (rev 95757)
+++ trunk/Source/_javascript_Core/dfg/DFGOperations.h	2011-09-22 22:42:54 UTC (rev 95758)
@@ -45,6 +45,7 @@
 typedef EncodedJSValue (*J_DFGOperation_EJP)(ExecState*, EncodedJSValue, void*);
 typedef EncodedJSValue (*J_DFGOperation_EJI)(ExecState*, EncodedJSValue, Identifier*);
 typedef EncodedJSValue (*J_DFGOperation_EP)(ExecState*, void*);
+typedef EncodedJSValue (*J_DFGOperation_EPS)(ExecState*, void*, size_t);
 typedef EncodedJSValue (*J_DFGOperation_EI)(ExecState*, Identifier*);
 typedef RegisterSizedBoolean (*Z_DFGOperation_EJ)(ExecState*, EncodedJSValue);
 typedef RegisterSizedBoolean (*Z_DFGOperation_EJJ)(ExecState*, EncodedJSValue, EncodedJSValue);
@@ -74,6 +75,8 @@
 EncodedJSValue operationResolve(ExecState*, Identifier*);
 EncodedJSValue operationResolveBase(ExecState*, Identifier*);
 EncodedJSValue operationResolveBaseStrictPut(ExecState*, Identifier*);
+EncodedJSValue operationToPrimitive(ExecState*, EncodedJSValue);
+EncodedJSValue operationStrCat(ExecState*, void* start, size_t);
 void operationThrowHasInstanceError(ExecState*, EncodedJSValue base);
 void operationPutByValStrict(ExecState*, EncodedJSValue encodedBase, EncodedJSValue encodedProperty, EncodedJSValue encodedValue);
 void operationPutByValNonStrict(ExecState*, EncodedJSValue encodedBase, EncodedJSValue encodedProperty, EncodedJSValue encodedValue);

Modified: trunk/Source/_javascript_Core/dfg/DFGPropagator.cpp (95757 => 95758)


--- trunk/Source/_javascript_Core/dfg/DFGPropagator.cpp	2011-09-22 22:35:21 UTC (rev 95757)
+++ trunk/Source/_javascript_Core/dfg/DFGPropagator.cpp	2011-09-22 22:42:54 UTC (rev 95758)
@@ -546,6 +546,31 @@
             changed |= setPrediction(makePrediction(PredictFinalObject, StrongPrediction));
             break;
         }
+            
+        case StrCat: {
+            changed |= setPrediction(makePrediction(PredictString, StrongPrediction));
+            break;
+        }
+            
+        case ToPrimitive: {
+            PredictedType child = m_predictions[node.child1()];
+            if (isStrongPrediction(child)) {
+                if (isObjectPrediction(child)) {
+                    // I'd love to fold this case into the case below, but I can't, because
+                    // removing PredictObjectMask from something that only has an object
+                    // prediction and nothing else means we have an ill-formed PredictedType
+                    // (strong predict-none). This should be killed once we remove all traces
+                    // of static (aka weak) predictions.
+                    changed |= mergePrediction(makePrediction(PredictString, StrongPrediction));
+                } else if (child & PredictObjectMask) {
+                    // Objects get turned into strings. So if the input has hints of objectness,
+                    // the output will have hinsts of stringiness.
+                    changed |= mergePrediction(mergePredictions(child & ~PredictObjectMask, makePrediction(PredictString, StrongPrediction)));
+                } else
+                    changed |= mergePrediction(child);
+            }
+            break;
+        }
 
 #ifndef NDEBUG
         // These get ignored because they don't return anything.
@@ -1010,7 +1035,16 @@
 #if ENABLE(DFG_DEBUG_PROPAGATION_VERBOSE)
         printf("   %s @%u: ", Graph::opName(m_graph[m_compileIndex].op), m_compileIndex);
 #endif
-
+        
+        // NOTE: there are some nodes that we deliberately don't CSE even though we
+        // probably could, like StrCat and ToPrimitive. That's because there is no
+        // evidence that doing CSE on these nodes would result in a performance
+        // progression. Hence considering these nodes in CSE would just mean that this
+        // code does more work with no win. Of course, we may want to reconsider this,
+        // since StrCat is trivially CSE-able. It's not trivially doable for
+        // ToPrimitive, but we could change that with some speculations if we really
+        // needed to.
+        
         switch (node.op) {
         
         // Handle the pure nodes. These nodes never have any side-effects.

Modified: trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT.cpp (95757 => 95758)


--- trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT.cpp	2011-09-22 22:35:21 UTC (rev 95757)
+++ trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT.cpp	2011-09-22 22:42:54 UTC (rev 95758)
@@ -1569,6 +1569,99 @@
         terminateSpeculativeExecution();
         break;
     }
+        
+    case ToPrimitive: {
+        if (shouldSpeculateInteger(node.child1())) {
+            // It's really profitable to speculate integer, since it's really cheap,
+            // it means we don't have to do any real work, and we emit a lot less code.
+            
+            SpeculateIntegerOperand op1(this, node.child1());
+            GPRTemporary result(this, op1);
+            
+            m_jit.move(op1.gpr(), result.gpr());
+            if (op1.format() == DataFormatInteger)
+                m_jit.orPtr(GPRInfo::tagTypeNumberRegister, result.gpr());
+            
+            jsValueResult(result.gpr(), m_compileIndex);
+            break;
+        }
+        
+        // FIXME: Add string speculation here.
+        
+        bool wasPrimitive = isKnownNumeric(node.child1()) || isKnownBoolean(node.child1());
+        
+        JSValueOperand op1(this, node.child1());
+        GPRTemporary result(this, op1);
+        
+        GPRReg op1GPR = op1.gpr();
+        GPRReg resultGPR = result.gpr();
+        
+        op1.use();
+        
+        if (wasPrimitive)
+            m_jit.move(op1GPR, resultGPR);
+        else {
+            MacroAssembler::JumpList alreadyPrimitive;
+            
+            alreadyPrimitive.append(m_jit.branchTestPtr(MacroAssembler::NonZero, op1GPR, GPRInfo::tagMaskRegister));
+            alreadyPrimitive.append(m_jit.branchPtr(MacroAssembler::Equal, MacroAssembler::Address(op1GPR), MacroAssembler::TrustedImmPtr(m_jit.globalData()->jsStringVPtr)));
+            
+            silentSpillAllRegisters(resultGPR);
+            m_jit.move(op1GPR, GPRInfo::argumentGPR1);
+            m_jit.move(GPRInfo::callFrameRegister, GPRInfo::argumentGPR0);
+            appendCallWithExceptionCheck(operationToPrimitive);
+            m_jit.move(GPRInfo::returnValueGPR, resultGPR);
+            silentFillAllRegisters(resultGPR);
+            
+            MacroAssembler::Jump done = m_jit.jump();
+            
+            alreadyPrimitive.link(&m_jit);
+            m_jit.move(op1GPR, resultGPR);
+            
+            done.link(&m_jit);
+        }
+        
+        jsValueResult(resultGPR, m_compileIndex, UseChildrenCalledExplicitly);
+        break;
+    }
+        
+    case StrCat: {
+        // We really don't want to grow the register file just to do a StrCat. Say we
+        // have 50 functions on the stack that all have a StrCat in them that has
+        // upwards of 10 operands. In the DFG this would mean that each one gets
+        // some random virtual register, and then to do the StrCat we'd need a second
+        // span of 10 operands just to have somewhere to copy the 10 operands to, where
+        // they'd be contiguous and we could easily tell the C code how to find them.
+        // Ugly! So instead we use the scratchBuffer infrastructure in JSGlobalData. That
+        // way, those 50 functions will share the same scratchBuffer for offloading their
+        // StrCat operands. It's about as good as we can do, unless we start doing
+        // virtual register coalescing to ensure that operands to StrCat get spilled
+        // in exactly the place where StrCat wants them, or else have the StrCat
+        // refer to those operands' SetLocal instructions to force them to spill in
+        // the right place. Basically, any way you cut it, the current approach
+        // probably has the best balance of performance and sensibility in the sense
+        // that it does not increase the complexity of the DFG JIT just to make StrCat
+        // fast and pretty.
+        
+        EncodedJSValue* buffer = static_cast<EncodedJSValue*>(m_jit.globalData()->scratchBufferForSize(sizeof(EncodedJSValue) * node.numChildren()));
+        
+        for (unsigned operandIdx = 0; operandIdx < node.numChildren(); ++operandIdx) {
+            JSValueOperand operand(this, m_jit.graph().m_varArgChildren[node.firstChild() + operandIdx]);
+            GPRReg opGPR = operand.gpr();
+            operand.use();
+            
+            m_jit.storePtr(opGPR, buffer + operandIdx);
+        }
+        
+        flushRegisters();
+        
+        GPRResult result(this);
+        
+        callOperation(operationStrCat, result.gpr(), buffer, node.numChildren());
+        
+        jsValueResult(result.gpr(), m_compileIndex, UseChildrenCalledExplicitly);
+        break;
+    }
 
     case ConvertThis: {
         SpeculateCellOperand thisValue(this, node.child1());

Modified: trunk/Source/_javascript_Core/runtime/JSGlobalData.cpp (95757 => 95758)


--- trunk/Source/_javascript_Core/runtime/JSGlobalData.cpp	2011-09-22 22:35:21 UTC (rev 95757)
+++ trunk/Source/_javascript_Core/runtime/JSGlobalData.cpp	2011-09-22 22:42:54 UTC (rev 95758)
@@ -186,7 +186,7 @@
     , interpreter(0)
     , heap(this, heapSize)
 #if ENABLE(DFG_JIT)
-    , sizeOfLastOSRScratchBuffer(0)
+    , sizeOfLastScratchBuffer(0)
 #endif
     , dynamicGlobalObject(0)
     , cachedUTCOffset(std::numeric_limits<double>::quiet_NaN())
@@ -352,8 +352,8 @@
 #endif
 
 #if ENABLE(DFG_JIT)
-    for (unsigned i = 0; i < osrScratchBuffers.size(); ++i)
-        fastFree(osrScratchBuffers[i]);
+    for (unsigned i = 0; i < scratchBuffers.size(); ++i)
+        fastFree(scratchBuffers[i]);
 #endif
 }
 

Modified: trunk/Source/_javascript_Core/runtime/JSGlobalData.h (95757 => 95758)


--- trunk/Source/_javascript_Core/runtime/JSGlobalData.h	2011-09-22 22:35:21 UTC (rev 95757)
+++ trunk/Source/_javascript_Core/runtime/JSGlobalData.h	2011-09-22 22:42:54 UTC (rev 95758)
@@ -233,25 +233,25 @@
         int64_t debugDataBuffer[64];
 #endif
 #if ENABLE(DFG_JIT)
-        Vector<void*> osrScratchBuffers;
-        size_t sizeOfLastOSRScratchBuffer;
+        Vector<void*> scratchBuffers;
+        size_t sizeOfLastScratchBuffer;
         
-        void* osrScratchBufferForSize(size_t size)
+        void* scratchBufferForSize(size_t size)
         {
             if (!size)
                 return 0;
             
-            if (size > sizeOfLastOSRScratchBuffer) {
+            if (size > sizeOfLastScratchBuffer) {
                 // Protect against a N^2 memory usage pathology by ensuring
                 // that at worst, we get a geometric series, meaning that the
                 // total memory usage is somewhere around
                 // max(scratch buffer size) * 4.
-                sizeOfLastOSRScratchBuffer = size * 2;
+                sizeOfLastScratchBuffer = size * 2;
                 
-                osrScratchBuffers.append(fastMalloc(sizeOfLastOSRScratchBuffer));
+                scratchBuffers.append(fastMalloc(sizeOfLastScratchBuffer));
             }
             
-            return osrScratchBuffers.last();
+            return scratchBuffers.last();
         }
 #endif
 #endif
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
http://lists.webkit.org/mailman/listinfo.cgi/webkit-changes

Reply via email to