DFG implementation of op_strcat should inline rope allocations
https://bugs.webkit.org/show_bug.cgi?id=112780

Reviewed by Oliver Hunt.
        
This gets rid of the StrCat node and adds a MakeRope node. The MakeRope node can
take either two or three operands, and allocates a rope string with either two or
three fibers. (The magic choice of three children for non-VarArg nodes happens to
match exactly with the magic choice of three fibers for rope strings.)
        
ValueAdd on KnownString is replaced with MakeRope with two children.
        
StrCat gets replaced by an appropriate sequence of MakeRope's.
        
MakeRope does not do the dynamic check to see if its children are empty strings.
This is replaced by a static check, instead. The downside is that we may use more
memory if the strings passed to MakeRope turn out to dynamically be empty. The
upside is that we do fewer checks in the cases where either the strings are not
empty, or where the strings are statically known to be empty. I suspect both of
those cases are more common, than the case where the string is dynamically empty.
        
This also results in some badness for X86. MakeRope needs six registers if it is
allocating a three-rope. We don't have six registers to spare on X86. Currently,
the code side-steps this problem by just never usign three-ropes in optimized
code on X86. All other architectures, including X86_64, don't have this problem.
        
This is a shocking speed-up. 9% progressions on both V8/splay and
SunSpider/date-format-xparb. 1% progression on V8v7 overall, and ~0.5% progression
on SunSpider. 2x speed-up on microbenchmarks that test op_strcat.

* dfg/DFGAbstractState.cpp:
(JSC::DFG::AbstractState::executeEffects):
* dfg/DFGAdjacencyList.h:
(AdjacencyList):
(JSC::DFG::AdjacencyList::removeEdge):
* dfg/DFGArgumentsSimplificationPhase.cpp:
(JSC::DFG::ArgumentsSimplificationPhase::removeArgumentsReferencingPhantomChild):
* dfg/DFGBackwardsPropagationPhase.cpp:
(JSC::DFG::BackwardsPropagationPhase::propagate):
* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::parseBlock):
* dfg/DFGCSEPhase.cpp:
(JSC::DFG::CSEPhase::putStructureStoreElimination):
(JSC::DFG::CSEPhase::eliminateIrrelevantPhantomChildren):
(JSC::DFG::CSEPhase::performNodeCSE):
* dfg/DFGDCEPhase.cpp:
(JSC::DFG::DCEPhase::eliminateIrrelevantPhantomChildren):
* dfg/DFGFixupPhase.cpp:
(JSC::DFG::FixupPhase::fixupNode):
(JSC::DFG::FixupPhase::createToString):
(JSC::DFG::FixupPhase::attemptToForceStringArrayModeByToStringConversion):
(JSC::DFG::FixupPhase::convertStringAddUse):
(FixupPhase):
(JSC::DFG::FixupPhase::convertToMakeRope):
(JSC::DFG::FixupPhase::fixupMakeRope):
(JSC::DFG::FixupPhase::attemptToMakeFastStringAdd):
* dfg/DFGNodeType.h:
(DFG):
* dfg/DFGOperations.cpp:
* dfg/DFGOperations.h:
* dfg/DFGPredictionPropagationPhase.cpp:
(JSC::DFG::PredictionPropagationPhase::propagate):
* dfg/DFGSpeculativeJIT.cpp:
(JSC::DFG::SpeculativeJIT::compileAdd):
(JSC::DFG::SpeculativeJIT::compileMakeRope):
(DFG):
* dfg/DFGSpeculativeJIT.h:
(JSC::DFG::SpeculativeJIT::callOperation):
(SpeculativeJIT):
(JSC::DFG::SpeculateCellOperand::SpeculateCellOperand):
(JSC::DFG::SpeculateCellOperand::~SpeculateCellOperand):
(JSC::DFG::SpeculateCellOperand::gpr):
(JSC::DFG::SpeculateCellOperand::use):
* dfg/DFGSpeculativeJIT32_64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* dfg/DFGSpeculativeJIT64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* runtime/JSString.h:
(JSRopeString):



git-svn-id: http://svn.webkit.org/repository/webkit/trunk@146382 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/JavaScriptCore/dfg/DFGAbstractState.cpp b/Source/JavaScriptCore/dfg/DFGAbstractState.cpp
index f4df241..a16259e 100644
--- a/Source/JavaScriptCore/dfg/DFGAbstractState.cpp
+++ b/Source/JavaScriptCore/dfg/DFGAbstractState.cpp
@@ -466,9 +466,6 @@
             else
                 forNode(node).set(SpecDouble);
             break;
-        case KnownStringUse:
-            forNode(node).set(m_graph.m_globalData.stringStructure.get());
-            break;
         default:
             RELEASE_ASSERT(node->op() == ValueAdd);
             clobberWorld(node->codeOrigin, indexInBlock);
@@ -477,6 +474,11 @@
         }
         break;
     }
+        
+    case MakeRope: {
+        forNode(node).set(m_graph.m_globalData.stringStructure.get());
+        break;
+    }
             
     case ArithSub: {
         JSValue left = forNode(node->child1()).value();
@@ -1101,10 +1103,6 @@
         break;
     }
             
-    case StrCat:
-        forNode(node).set(m_graph.m_globalData.stringStructure.get());
-        break;
-            
     case NewArray:
         node->setCanExit(true);
         forNode(node).set(m_graph.globalObjectFor(node->codeOrigin)->arrayStructureForIndexingTypeDuringAllocation(node->indexingType()));
diff --git a/Source/JavaScriptCore/dfg/DFGAdjacencyList.h b/Source/JavaScriptCore/dfg/DFGAdjacencyList.h
index ad7e225..d3f925d 100644
--- a/Source/JavaScriptCore/dfg/DFGAdjacencyList.h
+++ b/Source/JavaScriptCore/dfg/DFGAdjacencyList.h
@@ -116,9 +116,8 @@
         initialize();
     }
     
-    // Call this if you wish to remove an edge and the node treats the list of children
-    // as a "bag" - an unordered set where the index of the edge does not matter.
-    void removeEdgeFromBag(unsigned edgeIndex)
+    // Call this if you wish to remove an edge and the node treats the list of children.
+    void removeEdge(unsigned edgeIndex)
     {
         for (unsigned i = edgeIndex; i < Size - 1; ++i)
             setChild(i, child(i + 1));
diff --git a/Source/JavaScriptCore/dfg/DFGArgumentsSimplificationPhase.cpp b/Source/JavaScriptCore/dfg/DFGArgumentsSimplificationPhase.cpp
index 38c9bb1..cbab4e8 100644
--- a/Source/JavaScriptCore/dfg/DFGArgumentsSimplificationPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGArgumentsSimplificationPhase.cpp
@@ -833,14 +833,14 @@
                 && !m_createsArguments.contains(edge->codeOrigin.inlineCallFrame);
             if (!isDeadArgumentsRegister && !isAliasedArgumentsRegister)
                 break;
-            node->children.removeEdgeFromBag(edgeIndex);
+            node->children.removeEdge(edgeIndex);
             break;
         }
             
         case CreateArguments: { // Arises if we CSE two GetLocals to the arguments register and then CSE the second use of the GetLocal to the first.
             if (m_createsArguments.contains(edge->codeOrigin.inlineCallFrame))
                 break;
-            node->children.removeEdgeFromBag(edgeIndex);
+            node->children.removeEdge(edgeIndex);
             break;
         }
             
diff --git a/Source/JavaScriptCore/dfg/DFGBackwardsPropagationPhase.cpp b/Source/JavaScriptCore/dfg/DFGBackwardsPropagationPhase.cpp
index 0ed78f0..6609c1f 100644
--- a/Source/JavaScriptCore/dfg/DFGBackwardsPropagationPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGBackwardsPropagationPhase.cpp
@@ -328,11 +328,8 @@
             break;
         }
             
-        case StrCat: {
-            for (unsigned childIdx = node->firstChild();
-                childIdx < node->firstChild() + node->numChildren();
-                ++childIdx)
-                m_graph.m_varArgChildren[childIdx]->mergeFlags(NodeUsedAsNumber | NodeUsedAsOther);
+        case ToString: {
+            node->child1()->mergeFlags(NodeUsedAsNumber | NodeUsedAsOther);
             break;
         }
             
diff --git a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
index 634d1055c..f01036d 100644
--- a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
+++ b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
@@ -2352,9 +2352,31 @@
         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)));
+#if CPU(X86)
+            // X86 doesn't have enough registers to compile MakeRope with three arguments.
+            // Rather than try to be clever, we just make MakeRope dumber on this processor.
+            const unsigned maxRopeArguments = 2;
+#else
+            const unsigned maxRopeArguments = 3;
+#endif
+            Node* operands[AdjacencyList::Size];
+            unsigned indexInOperands = 0;
+            for (unsigned i = 0; i < AdjacencyList::Size; ++i)
+                operands[i] = 0;
+            for (int operandIdx = startOperand; operandIdx < startOperand + numOperands; ++operandIdx) {
+                if (indexInOperands == maxRopeArguments) {
+                    operands[0] = addToGraph(MakeRope, operands[0], operands[1], operands[2]);
+                    for (unsigned i = 1; i < AdjacencyList::Size; ++i)
+                        operands[i] = 0;
+                    indexInOperands = 1;
+                }
+                
+                ASSERT(indexInOperands < AdjacencyList::Size);
+                ASSERT(indexInOperands < maxRopeArguments);
+                operands[indexInOperands++] = addToGraph(ToString, get(operandIdx));
+            }
+            set(currentInstruction[1].u.operand,
+                addToGraph(MakeRope, operands[0], operands[1], operands[2]));
             NEXT_OPCODE(op_strcat);
         }
 
diff --git a/Source/JavaScriptCore/dfg/DFGCSEPhase.cpp b/Source/JavaScriptCore/dfg/DFGCSEPhase.cpp
index 2b23e19..2cb6088 100644
--- a/Source/JavaScriptCore/dfg/DFGCSEPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGCSEPhase.cpp
@@ -566,7 +566,6 @@
             case NewFunctionExpression:
             case CreateActivation:
             case TearOffActivation:
-            case StrCat:
             case ToPrimitive:
             case NewRegexp:
             case NewArrayBuffer:
@@ -578,6 +577,7 @@
             case TypeOf:
             case ToString:
             case NewStringObject:
+            case MakeRope:
                 return 0;
                 
             case GetIndexedPropertyStorage:
@@ -967,7 +967,7 @@
 #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE)
             dataLog("   Eliminating edge @", m_currentNode->index(), " -> @", edge->index());
 #endif
-            node->children.removeEdgeFromBag(i--);
+            node->children.removeEdge(i--);
             m_changed = true;
         }
     }
@@ -1030,11 +1030,11 @@
 #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
+        // probably could, like MakeRope 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
+        // since MakeRope is trivially CSE-able. It's not trivially doable for
         // ToPrimitive, but we could change that with some speculations if we really
         // needed to.
         
diff --git a/Source/JavaScriptCore/dfg/DFGDCEPhase.cpp b/Source/JavaScriptCore/dfg/DFGDCEPhase.cpp
index 1b036ea..5cda110 100644
--- a/Source/JavaScriptCore/dfg/DFGDCEPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGDCEPhase.cpp
@@ -182,7 +182,7 @@
             if (!edge)
                 continue;
             if (edge.isProved() || edge.useKind() == UntypedUse)
-                node->children.removeEdgeFromBag(i--);
+                node->children.removeEdge(i--);
         }
     }
     
diff --git a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
index 39861fa..30318f3 100644
--- a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
@@ -175,6 +175,11 @@
             break;
         }
             
+        case MakeRope: {
+            fixupMakeRope(node);
+            break;
+        }
+            
         case ArithAdd:
         case ArithSub: {
             if (attemptToMakeIntegerAdd(node))
@@ -862,7 +867,6 @@
         case IsString:
         case IsObject:
         case IsFunction:
-        case StrCat:
         case CreateActivation:
         case TearOffActivation:
         case CreateArguments:
@@ -898,11 +902,11 @@
     }
     
     template<UseKind useKind>
-    Node* createToString(Node* node, Edge edge)
+    void createToString(Node* node, Edge& edge)
     {
-        return m_insertionSet.insertNode(
+        edge.setNode(m_insertionSet.insertNode(
             m_indexInBlock, SpecString, ToString, node->codeOrigin,
-            Edge(edge.node(), useKind));
+            Edge(edge.node(), useKind)));
     }
     
     template<UseKind useKind>
@@ -913,7 +917,7 @@
         if (!canOptimizeStringObjectAccess(node->codeOrigin))
             return;
         
-        node->child1().setNode(createToString<useKind>(node, node->child1()));
+        createToString<useKind>(node, node->child1());
         arrayMode = ArrayMode(Array::String);
     }
     
@@ -947,7 +951,34 @@
         // FIXME: We ought to be able to have a ToPrimitiveToString node.
         
         observeUseKindOnNode<useKind>(edge.node());
-        edge = Edge(createToString<useKind>(node, edge), KnownStringUse);
+        createToString<useKind>(node, edge);
+    }
+    
+    void convertToMakeRope(Node* node)
+    {
+        node->setOpAndDefaultFlags(MakeRope);
+        fixupMakeRope(node);
+    }
+    
+    void fixupMakeRope(Node* node)
+    {
+        for (unsigned i = 0; i < AdjacencyList::Size; ++i) {
+            Edge& edge = node->children.child(i);
+            if (!edge)
+                break;
+            edge.setUseKind(KnownStringUse);
+            if (!m_graph.isConstant(edge.node()))
+                continue;
+            JSString* string = jsCast<JSString*>(m_graph.valueOfJSConstant(edge.node()).asCell());
+            if (string->length())
+                continue;
+            node->children.removeEdge(i--);
+        }
+        
+        if (!node->child2()) {
+            ASSERT(!node->child3());
+            node->convertToIdentity();
+        }
     }
     
     template<UseKind leftUseKind>
@@ -961,6 +992,7 @@
         if (right->shouldSpeculateString()) {
             convertStringAddUse<leftUseKind>(node, left);
             convertStringAddUse<StringUse>(node, right);
+            convertToMakeRope(node);
             return true;
         }
         
@@ -968,6 +1000,7 @@
             && canOptimizeStringObjectAccess(node->codeOrigin)) {
             convertStringAddUse<leftUseKind>(node, left);
             convertStringAddUse<StringObjectUse>(node, right);
+            convertToMakeRope(node);
             return true;
         }
         
@@ -975,6 +1008,7 @@
             && canOptimizeStringObjectAccess(node->codeOrigin)) {
             convertStringAddUse<leftUseKind>(node, left);
             convertStringAddUse<StringOrStringObjectUse>(node, right);
+            convertToMakeRope(node);
             return true;
         }
         
diff --git a/Source/JavaScriptCore/dfg/DFGNodeType.h b/Source/JavaScriptCore/dfg/DFGNodeType.h
index 775928c..df7cd00 100644
--- a/Source/JavaScriptCore/dfg/DFGNodeType.h
+++ b/Source/JavaScriptCore/dfg/DFGNodeType.h
@@ -224,7 +224,7 @@
     macro(ToPrimitive, NodeResultJS | NodeMustGenerate | NodeClobbersWorld) \
     macro(ToString, NodeResultJS | NodeMustGenerate | NodeMightClobber) \
     macro(NewStringObject, NodeResultJS) \
-    macro(StrCat, NodeResultJS | NodeMustGenerate | NodeHasVarArgs | NodeClobbersWorld) \
+    macro(MakeRope, NodeResultJS) \
     \
     /* Nodes used for activations. Activation support works by having it anchored at */\
     /* epilgoues via TearOffActivation, and all CreateActivation nodes kept alive by */\
diff --git a/Source/JavaScriptCore/dfg/DFGOperations.cpp b/Source/JavaScriptCore/dfg/DFGOperations.cpp
index d48c6e2..b2025a6 100644
--- a/Source/JavaScriptCore/dfg/DFGOperations.cpp
+++ b/Source/JavaScriptCore/dfg/DFGOperations.cpp
@@ -1253,14 +1253,6 @@
     return JSValue::encode(JSValue::decode(value).toPrimitive(exec));
 }
 
-EncodedJSValue DFG_OPERATION operationStrCat(ExecState* exec, void* buffer, size_t size)
-{
-    JSGlobalData* globalData = &exec->globalData();
-    NativeCallFrameTracer tracer(globalData, exec);
-
-    return JSValue::encode(jsString(exec, static_cast<Register*>(buffer), size));
-}
-
 char* DFG_OPERATION operationNewArray(ExecState* exec, Structure* arrayStructure, void* buffer, size_t size)
 {
     JSGlobalData* globalData = &exec->globalData();
@@ -1571,16 +1563,22 @@
     return JSValue::decode(value).toString(exec);
 }
 
-JSCell* DFG_OPERATION operationStringAdd(ExecState* exec, JSString* left, JSString* right)
+JSCell* DFG_OPERATION operationMakeRope2(ExecState* exec, JSString* left, JSString* right)
 {
     JSGlobalData& globalData = exec->globalData();
     NativeCallFrameTracer tracer(&globalData, exec);
 
-    // Don't even bother calling jsString() because our fast path would have done whatever
-    // optimizations that function would have done.
     return JSRopeString::create(globalData, left, right);
 }
 
+JSCell* DFG_OPERATION operationMakeRope3(ExecState* exec, JSString* a, JSString* b, JSString* c)
+{
+    JSGlobalData& globalData = exec->globalData();
+    NativeCallFrameTracer tracer(&globalData, exec);
+
+    return JSRopeString::create(globalData, a, b, c);
+}
+
 double DFG_OPERATION operationFModOnInts(int32_t a, int32_t b)
 {
     return fmod(a, b);
diff --git a/Source/JavaScriptCore/dfg/DFGOperations.h b/Source/JavaScriptCore/dfg/DFGOperations.h
index ebcb497..f8c4907 100644
--- a/Source/JavaScriptCore/dfg/DFGOperations.h
+++ b/Source/JavaScriptCore/dfg/DFGOperations.h
@@ -88,6 +88,7 @@
 typedef JSCell* DFG_OPERATION (*C_DFGOperation_EJ)(ExecState*, EncodedJSValue);
 typedef JSCell* DFG_OPERATION (*C_DFGOperation_EJssSt)(ExecState*, JSString*, Structure*);
 typedef JSCell* DFG_OPERATION (*C_DFGOperation_EJssJss)(ExecState*, JSString*, JSString*);
+typedef JSCell* DFG_OPERATION (*C_DFGOperation_EJssJssJss)(ExecState*, JSString*, JSString*, JSString*);
 typedef JSCell* DFG_OPERATION (*C_DFGOperation_EOZ)(ExecState*, JSObject*, int32_t);
 typedef JSCell* DFG_OPERATION (*C_DFGOperation_ESt)(ExecState*, Structure*);
 typedef double DFG_OPERATION (*D_DFGOperation_DD)(double, double);
@@ -145,7 +146,6 @@
 EncodedJSValue DFG_OPERATION operationResolveBaseStrictPut(ExecState*, Identifier*, ResolveOperations*, PutToBaseOperation*) WTF_INTERNAL;
 EncodedJSValue DFG_OPERATION operationResolveGlobal(ExecState*, ResolveOperation*, JSGlobalObject*, Identifier*) WTF_INTERNAL;
 EncodedJSValue DFG_OPERATION operationToPrimitive(ExecState*, EncodedJSValue) WTF_INTERNAL;
-EncodedJSValue DFG_OPERATION operationStrCat(ExecState*, void*, size_t) WTF_INTERNAL;
 char* DFG_OPERATION operationNewArray(ExecState*, Structure*, void*, size_t) WTF_INTERNAL;
 char* DFG_OPERATION operationNewArrayBuffer(ExecState*, Structure*, size_t, size_t) WTF_INTERNAL;
 char* DFG_OPERATION operationNewEmptyArray(ExecState*, Structure*) WTF_INTERNAL;
@@ -218,7 +218,8 @@
 JSCell* DFG_OPERATION operationNewStringObject(ExecState*, JSString*, Structure*);
 JSCell* DFG_OPERATION operationToStringOnCell(ExecState*, JSCell*);
 JSCell* DFG_OPERATION operationToString(ExecState*, EncodedJSValue);
-JSCell* DFG_OPERATION operationStringAdd(ExecState*, JSString*, JSString*);
+JSCell* DFG_OPERATION operationMakeRope2(ExecState*, JSString*, JSString*);
+JSCell* DFG_OPERATION operationMakeRope3(ExecState*, JSString*, JSString*, JSString*);
 
 // This method is used to lookup an exception hander, keyed by faultLocation, which is
 // the return location from one of the calls out to one of the helper operations above.
diff --git a/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp b/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
index 8a4e0de..c32f846 100644
--- a/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
@@ -413,8 +413,8 @@
         }
         
         case StringCharAt:
-        case StrCat:
-        case ToString: {
+        case ToString:
+        case MakeRope: {
             changed |= setPrediction(SpecString);
             break;
         }
diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
index 912d09b..c913bc1 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
@@ -3094,61 +3094,6 @@
         return;
     }
         
-    case KnownStringUse: {
-        SpeculateCellOperand op1(this, node->child1());
-        SpeculateCellOperand op2(this, node->child2());
-        GPRTemporary result(this);
-        GPRTemporary allocator(this);
-        GPRTemporary scratch(this);
-        
-        GPRReg op1GPR = op1.gpr();
-        GPRReg op2GPR = op2.gpr();
-        GPRReg resultGPR = result.gpr();
-        GPRReg allocatorGPR = allocator.gpr();
-        GPRReg scratchGPR = scratch.gpr();
-        
-        JITCompiler::Jump op1NotEmpty = m_jit.branchTest32(
-            JITCompiler::NonZero, JITCompiler::Address(op1GPR, JSString::offsetOfLength()));
-        
-        m_jit.move(op2GPR, resultGPR);
-        JITCompiler::Jump done1 = m_jit.jump();
-        
-        op1NotEmpty.link(&m_jit);
-        JITCompiler::Jump op2NotEmpty = m_jit.branchTest32(
-            JITCompiler::NonZero, JITCompiler::Address(op2GPR, JSString::offsetOfLength()));
-        
-        m_jit.move(op1GPR, resultGPR);
-        JITCompiler::Jump done2 = m_jit.jump();
-        
-        op2NotEmpty.link(&m_jit);
-        
-        JITCompiler::JumpList slowPath;
-        MarkedAllocator& markedAllocator = m_jit.globalData()->heap.allocatorForObjectWithImmortalStructureDestructor(sizeof(JSRopeString));
-        m_jit.move(TrustedImmPtr(&markedAllocator), allocatorGPR);
-        emitAllocateJSCell(resultGPR, allocatorGPR, TrustedImmPtr(m_jit.globalData()->stringStructure.get()), scratchGPR, slowPath);
-        
-        m_jit.storePtr(TrustedImmPtr(0), JITCompiler::Address(resultGPR, JSString::offsetOfValue()));
-        m_jit.storePtr(TrustedImmPtr(0), JITCompiler::Address(resultGPR, JSRopeString::offsetOfFibers() + sizeof(WriteBarrier<JSString>) * 2));
-        m_jit.storePtr(op1GPR, JITCompiler::Address(resultGPR, JSRopeString::offsetOfFibers()));
-        m_jit.storePtr(op2GPR, JITCompiler::Address(resultGPR, JSRopeString::offsetOfFibers() + sizeof(WriteBarrier<JSString>)));
-        m_jit.load32(JITCompiler::Address(op1GPR, JSString::offsetOfFlags()), scratchGPR);
-        m_jit.load32(JITCompiler::Address(op1GPR, JSString::offsetOfLength()), allocatorGPR);
-        m_jit.and32(JITCompiler::Address(op2GPR, JSString::offsetOfFlags()), scratchGPR);
-        m_jit.add32(JITCompiler::Address(op2GPR, JSString::offsetOfLength()), allocatorGPR);
-        m_jit.and32(JITCompiler::TrustedImm32(JSString::Is8Bit), scratchGPR);
-        m_jit.store32(scratchGPR, JITCompiler::Address(resultGPR, JSString::offsetOfFlags()));
-        m_jit.store32(allocatorGPR, JITCompiler::Address(resultGPR, JSString::offsetOfLength()));
-        
-        addSlowPathGenerator(slowPathCall(
-            slowPath, this, operationStringAdd, resultGPR, op1GPR, op2GPR));
-        
-        done1.link(&m_jit);
-        done2.link(&m_jit);
-        
-        cellResult(resultGPR, node);
-        return;
-    }
-        
     case UntypedUse: {
         RELEASE_ASSERT(node->op() == ValueAdd);
         compileValueAdd(node);
@@ -3161,6 +3106,71 @@
     }
 }
 
+void SpeculativeJIT::compileMakeRope(Node* node)
+{
+    ASSERT(node->child1().useKind() == KnownStringUse);
+    ASSERT(node->child2().useKind() == KnownStringUse);
+    ASSERT(!node->child3() || node->child3().useKind() == KnownStringUse);
+    
+    SpeculateCellOperand op1(this, node->child1());
+    SpeculateCellOperand op2(this, node->child2());
+    SpeculateCellOperand op3(this, node->child3());
+    GPRTemporary result(this);
+    GPRTemporary allocator(this);
+    GPRTemporary scratch(this);
+    
+    GPRReg opGPRs[3];
+    unsigned numOpGPRs;
+    opGPRs[0] = op1.gpr();
+    opGPRs[1] = op2.gpr();
+    if (node->child3()) {
+        opGPRs[2] = op3.gpr();
+        numOpGPRs = 3;
+    } else {
+        opGPRs[2] = InvalidGPRReg;
+        numOpGPRs = 2;
+    }
+    GPRReg resultGPR = result.gpr();
+    GPRReg allocatorGPR = allocator.gpr();
+    GPRReg scratchGPR = scratch.gpr();
+    
+    JITCompiler::JumpList slowPath;
+    MarkedAllocator& markedAllocator = m_jit.globalData()->heap.allocatorForObjectWithImmortalStructureDestructor(sizeof(JSRopeString));
+    m_jit.move(TrustedImmPtr(&markedAllocator), allocatorGPR);
+    emitAllocateJSCell(resultGPR, allocatorGPR, TrustedImmPtr(m_jit.globalData()->stringStructure.get()), scratchGPR, slowPath);
+        
+    m_jit.storePtr(TrustedImmPtr(0), JITCompiler::Address(resultGPR, JSString::offsetOfValue()));
+    for (unsigned i = 0; i < numOpGPRs; ++i)
+        m_jit.storePtr(opGPRs[i], JITCompiler::Address(resultGPR, JSRopeString::offsetOfFibers() + sizeof(WriteBarrier<JSString>) * i));
+    for (unsigned i = numOpGPRs; i < JSRopeString::s_maxInternalRopeLength; ++i)
+        m_jit.storePtr(TrustedImmPtr(0), JITCompiler::Address(resultGPR, JSRopeString::offsetOfFibers() + sizeof(WriteBarrier<JSString>) * i));
+    m_jit.load32(JITCompiler::Address(opGPRs[0], JSString::offsetOfFlags()), scratchGPR);
+    m_jit.load32(JITCompiler::Address(opGPRs[0], JSString::offsetOfLength()), allocatorGPR);
+    for (unsigned i = 1; i < numOpGPRs; ++i) {
+        m_jit.and32(JITCompiler::Address(opGPRs[i], JSString::offsetOfFlags()), scratchGPR);
+        m_jit.add32(JITCompiler::Address(opGPRs[i], JSString::offsetOfLength()), allocatorGPR);
+    }
+    m_jit.and32(JITCompiler::TrustedImm32(JSString::Is8Bit), scratchGPR);
+    m_jit.store32(scratchGPR, JITCompiler::Address(resultGPR, JSString::offsetOfFlags()));
+    m_jit.store32(allocatorGPR, JITCompiler::Address(resultGPR, JSString::offsetOfLength()));
+    
+    switch (numOpGPRs) {
+    case 2:
+        addSlowPathGenerator(slowPathCall(
+            slowPath, this, operationMakeRope2, resultGPR, opGPRs[0], opGPRs[1]));
+        break;
+    case 3:
+        addSlowPathGenerator(slowPathCall(
+            slowPath, this, operationMakeRope3, resultGPR, opGPRs[0], opGPRs[1], opGPRs[2]));
+        break;
+    default:
+        RELEASE_ASSERT_NOT_REACHED();
+        break;
+    }
+        
+    cellResult(resultGPR, node);
+}
+
 void SpeculativeJIT::compileArithSub(Node* node)
 {
     switch (node->binaryUseKind()) {
diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h
index ccb6812..d3736e2 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h
@@ -1098,6 +1098,11 @@
         m_jit.setupArgumentsWithExecState(arg1, arg2);
         return appendCallWithExceptionCheckSetResult(operation, result);
     }
+    JITCompiler::Call callOperation(C_DFGOperation_EJssJssJss operation, GPRReg result, GPRReg arg1, GPRReg arg2, GPRReg arg3)
+    {
+        m_jit.setupArgumentsWithExecState(arg1, arg2, arg3);
+        return appendCallWithExceptionCheckSetResult(operation, result);
+    }
     JITCompiler::Call callOperation(C_DFGOperation_EJ operation, GPRReg result, GPRReg arg1)
     {
         m_jit.setupArgumentsWithExecState(arg1);
@@ -1501,6 +1506,11 @@
         m_jit.setupArgumentsWithExecState(arg1, arg2);
         return appendCallWithExceptionCheckSetResult(operation, result);
     }
+    JITCompiler::Call callOperation(C_DFGOperation_EJssJssJss operation, GPRReg result, GPRReg arg1, GPRReg arg2, GPRReg arg3)
+    {
+        m_jit.setupArgumentsWithExecState(arg1, arg2, arg3);
+        return appendCallWithExceptionCheckSetResult(operation, result);
+    }
     JITCompiler::Call callOperation(C_DFGOperation_EJ operation, GPRReg result, GPRReg arg1Tag, GPRReg arg1Payload)
     {
         m_jit.setupArgumentsWithExecState(EABI_32BIT_DUMMY_ARG arg1Payload, arg1Tag);
@@ -2105,6 +2115,7 @@
     void compileDoubleAsInt32(Node*);
     void compileInt32ToDouble(Node*);
     void compileAdd(Node*);
+    void compileMakeRope(Node*);
     void compileArithSub(Node*);
     void compileArithNegate(Node*);
     void compileArithMul(Node*);
@@ -2887,6 +2898,8 @@
         , m_gprOrInvalid(InvalidGPRReg)
     {
         ASSERT(m_jit);
+        if (!edge)
+            return;
         ASSERT_UNUSED(mode, mode == ManualOperandSpeculation || (edge.useKind() == CellUse || edge.useKind() == KnownCellUse || edge.useKind() == ObjectUse || edge.useKind() == StringUse || edge.useKind() == KnownStringUse || edge.useKind() == StringObjectUse || edge.useKind() == StringOrStringObjectUse));
         if (jit->isFilled(node()))
             gpr();
@@ -2894,6 +2907,8 @@
 
     ~SpeculateCellOperand()
     {
+        if (!m_edge)
+            return;
         ASSERT(m_gprOrInvalid != InvalidGPRReg);
         m_jit->unlock(m_gprOrInvalid);
     }
@@ -2910,6 +2925,7 @@
 
     GPRReg gpr()
     {
+        ASSERT(m_edge);
         if (m_gprOrInvalid == InvalidGPRReg)
             m_gprOrInvalid = m_jit->fillSpeculateCell(edge());
         return m_gprOrInvalid;
@@ -2917,6 +2933,7 @@
     
     void use()
     {
+        ASSERT(m_edge);
         m_jit->use(node());
     }
 
diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
index ad1a896..da7cba1 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
@@ -2228,6 +2228,10 @@
         compileAdd(node);
         break;
 
+    case MakeRope:
+        compileMakeRope(node);
+        break;
+
     case ArithSub:
         compileArithSub(node);
         break;
@@ -3364,48 +3368,6 @@
         break;
     }
         
-    case StrCat: {
-        size_t scratchSize = sizeof(EncodedJSValue) * node->numChildren();
-        ScratchBuffer* scratchBuffer = m_jit.globalData()->scratchBufferForSize(scratchSize);
-        EncodedJSValue* buffer = scratchBuffer ? static_cast<EncodedJSValue*>(scratchBuffer->dataBuffer()) : 0;
-        
-        for (unsigned operandIdx = 0; operandIdx < node->numChildren(); ++operandIdx) {
-            JSValueOperand operand(this, m_jit.graph().m_varArgChildren[node->firstChild() + operandIdx]);
-            GPRReg opTagGPR = operand.tagGPR();
-            GPRReg opPayloadGPR = operand.payloadGPR();
-            operand.use();
-            
-            m_jit.store32(opTagGPR, reinterpret_cast<char*>(buffer + operandIdx) + OBJECT_OFFSETOF(EncodedValueDescriptor, asBits.tag));
-            m_jit.store32(opPayloadGPR, reinterpret_cast<char*>(buffer + operandIdx) + OBJECT_OFFSETOF(EncodedValueDescriptor, asBits.payload));
-        }
-        
-        flushRegisters();
-
-        if (scratchSize) {
-            GPRTemporary scratch(this);
-
-            // Tell GC mark phase how much of the scratch buffer is active during call.
-            m_jit.move(TrustedImmPtr(scratchBuffer->activeLengthPtr()), scratch.gpr());
-            m_jit.storePtr(TrustedImmPtr(scratchSize), scratch.gpr());
-        }
-
-        GPRResult resultPayload(this);
-        GPRResult2 resultTag(this);
-        
-        callOperation(operationStrCat, resultTag.gpr(), resultPayload.gpr(), static_cast<void *>(buffer), node->numChildren());
-
-        if (scratchSize) {
-            GPRTemporary scratch(this);
-
-            m_jit.move(TrustedImmPtr(scratchBuffer->activeLengthPtr()), scratch.gpr());
-            m_jit.storePtr(TrustedImmPtr(0), scratch.gpr());
-        }
-
-        // FIXME: make the callOperation above explicitly return a cell result, or jitAssert the tag is a cell tag.
-        cellResult(resultPayload.gpr(), node, UseChildrenCalledExplicitly);
-        break;
-    }
-
     case NewArray: {
         JSGlobalObject* globalObject = m_jit.graph().globalObjectFor(node->codeOrigin);
         if (!globalObject->isHavingABadTime() && !hasArrayStorage(node->indexingType())) {
diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
index f005294..1a31e2e 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
@@ -2165,6 +2165,10 @@
     case ArithAdd:
         compileAdd(node);
         break;
+        
+    case MakeRope:
+        compileMakeRope(node);
+        break;
 
     case ArithSub:
         compileArithSub(node);
@@ -3523,44 +3527,6 @@
         break;
     }
         
-    case StrCat: {
-        size_t scratchSize = sizeof(EncodedJSValue) * node->numChildren();
-        ScratchBuffer* scratchBuffer = m_jit.globalData()->scratchBufferForSize(scratchSize);
-        EncodedJSValue* buffer = scratchBuffer ? static_cast<EncodedJSValue*>(scratchBuffer->dataBuffer()) : 0;
-        
-        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.store64(opGPR, buffer + operandIdx);
-        }
-        
-        flushRegisters();
-
-        if (scratchSize) {
-            GPRTemporary scratch(this);
-
-            // Tell GC mark phase how much of the scratch buffer is active during call.
-            m_jit.move(TrustedImmPtr(scratchBuffer->activeLengthPtr()), scratch.gpr());
-            m_jit.storePtr(TrustedImmPtr(scratchSize), scratch.gpr());
-        }
-
-        GPRResult result(this);
-        
-        callOperation(operationStrCat, result.gpr(), static_cast<void *>(buffer), node->numChildren());
-
-        if (scratchSize) {
-            GPRTemporary scratch(this);
-
-            m_jit.move(TrustedImmPtr(scratchBuffer->activeLengthPtr()), scratch.gpr());
-            m_jit.storePtr(TrustedImmPtr(0), scratch.gpr());
-        }
-
-        cellResult(result.gpr(), node, UseChildrenCalledExplicitly);
-        break;
-    }
-        
     case NewArrayBuffer: {
         JSGlobalObject* globalObject = m_jit.graph().globalObjectFor(node->codeOrigin);
         IndexingType indexingType = node->indexingType();