DFG should inline binary string concatenations (i.e. ValueAdd with string children)
https://bugs.webkit.org/show_bug.cgi?id=112599

Reviewed by Oliver Hunt.
        
This does as advertised: if you do x + y where x and y are strings, you'll get
a fast inlined JSRopeString allocation (along with whatever checks are necessary).
It also does good things if either x or y (or both) are StringObjects, or some
other thing like StringOrStringObject. It also lays the groundwork for making this
fast if either x or y are numbers, or some other reasonably-cheap-to-convert
value.

* dfg/DFGAbstractState.cpp:
(JSC::DFG::AbstractState::executeEffects):
* dfg/DFGFixupPhase.cpp:
(JSC::DFG::FixupPhase::fixupNode):
(FixupPhase):
(JSC::DFG::FixupPhase::isStringObjectUse):
(JSC::DFG::FixupPhase::convertStringAddUse):
(JSC::DFG::FixupPhase::attemptToMakeFastStringAdd):
* dfg/DFGOperations.cpp:
* dfg/DFGOperations.h:
* dfg/DFGSpeculativeJIT.cpp:
(JSC::DFG::SpeculativeJIT::compileAdd):
* dfg/DFGSpeculativeJIT.h:
(JSC::DFG::SpeculativeJIT::callOperation):
(SpeculativeJIT):
(JSC::DFG::SpeculativeJIT::emitAllocateJSCell):
(JSC::DFG::SpeculativeJIT::emitAllocateJSObject):
* runtime/JSString.h:
(JSC::JSString::offsetOfFlags):
(JSString):
(JSRopeString):
(JSC::JSRopeString::offsetOfFibers):



git-svn-id: http://svn.webkit.org/repository/webkit/trunk@146164 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/JavaScriptCore/dfg/DFGAbstractState.cpp b/Source/JavaScriptCore/dfg/DFGAbstractState.cpp
index 74358ac..f4df241 100644
--- a/Source/JavaScriptCore/dfg/DFGAbstractState.cpp
+++ b/Source/JavaScriptCore/dfg/DFGAbstractState.cpp
@@ -466,6 +466,9 @@
             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);
diff --git a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
index 6e8d922..29472f0 100644
--- a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
@@ -148,6 +148,30 @@
                 fixDoubleEdge<NumberUse>(node->child2());
                 break;
             }
+            
+            // FIXME: Optimize for the case where one of the operands is the
+            // empty string. Also consider optimizing for the case where we don't
+            // believe either side is the emtpy string. Both of these things should
+            // be easy.
+            
+            if (node->child1()->shouldSpeculateString()
+                && attemptToMakeFastStringAdd<StringUse>(node, node->child1(), node->child2()))
+                break;
+            if (node->child2()->shouldSpeculateString()
+                && attemptToMakeFastStringAdd<StringUse>(node, node->child2(), node->child1()))
+                break;
+            if (node->child1()->shouldSpeculateStringObject()
+                && attemptToMakeFastStringAdd<StringObjectUse>(node, node->child1(), node->child2()))
+                break;
+            if (node->child2()->shouldSpeculateStringObject()
+                && attemptToMakeFastStringAdd<StringObjectUse>(node, node->child2(), node->child1()))
+                break;
+            if (node->child1()->shouldSpeculateStringOrStringObject()
+                && attemptToMakeFastStringAdd<StringOrStringObjectUse>(node, node->child1(), node->child2()))
+                break;
+            if (node->child2()->shouldSpeculateStringOrStringObject()
+                && attemptToMakeFastStringAdd<StringOrStringObjectUse>(node, node->child2(), node->child1()))
+                break;
             break;
         }
             
@@ -862,6 +886,74 @@
 #endif
     }
     
+    template<UseKind useKind>
+    bool isStringObjectUse()
+    {
+        switch (useKind) {
+        case StringObjectUse:
+        case StringOrStringObjectUse:
+            return true;
+        default:
+            return false;
+        }
+    }
+    
+    template<UseKind useKind>
+    void convertStringAddUse(Node* node, Edge& edge)
+    {
+        if (useKind == StringUse) {
+            // This preserves the binaryUseKind() invariant ot ValueAdd: ValueAdd's
+            // two edges will always have identical use kinds, which makes the
+            // decision process much easier.
+            observeUseKindOnNode<StringUse>(edge.node());
+            m_insertionSet.insertNode(
+                m_indexInBlock, SpecNone, Phantom, node->codeOrigin,
+                Edge(edge.node(), StringUse));
+            edge.setUseKind(KnownStringUse);
+            return;
+        }
+        
+        // FIXME: We ought to be able to have a ToPrimitiveToString node.
+        
+        observeUseKindOnNode<useKind>(edge.node());
+        edge = Edge(m_insertionSet.insertNode(
+            m_indexInBlock, SpecString, ToString, node->codeOrigin,
+            Edge(edge.node(), useKind)), KnownStringUse);
+    }
+    
+    template<UseKind leftUseKind>
+    bool attemptToMakeFastStringAdd(Node* node, Edge& left, Edge& right)
+    {
+        ASSERT(leftUseKind == StringUse || leftUseKind == StringObjectUse || leftUseKind == StringOrStringObjectUse);
+        
+        if (isStringObjectUse<leftUseKind>() && !canOptimizeStringObjectAccess(node->codeOrigin))
+            return false;
+        
+        if (right->shouldSpeculateString()) {
+            convertStringAddUse<leftUseKind>(node, left);
+            convertStringAddUse<StringUse>(node, right);
+            return true;
+        }
+        
+        if (right->shouldSpeculateStringObject()
+            && canOptimizeStringObjectAccess(node->codeOrigin)) {
+            convertStringAddUse<leftUseKind>(node, left);
+            convertStringAddUse<StringObjectUse>(node, right);
+            return true;
+        }
+        
+        if (right->shouldSpeculateStringOrStringObject()
+            && canOptimizeStringObjectAccess(node->codeOrigin)) {
+            convertStringAddUse<leftUseKind>(node, left);
+            convertStringAddUse<StringOrStringObjectUse>(node, right);
+            return true;
+        }
+        
+        // FIXME: We ought to be able to convert the right case to do
+        // ToPrimitiveToString.
+        return false; // Let the slow path worry about it.
+    }
+    
     bool isStringPrototypeMethodSane(Structure* stringPrototypeStructure, const Identifier& ident)
     {
         unsigned attributesUnused;
diff --git a/Source/JavaScriptCore/dfg/DFGOperations.cpp b/Source/JavaScriptCore/dfg/DFGOperations.cpp
index 10bcc9b..d48c6e2 100644
--- a/Source/JavaScriptCore/dfg/DFGOperations.cpp
+++ b/Source/JavaScriptCore/dfg/DFGOperations.cpp
@@ -1571,6 +1571,16 @@
     return JSValue::decode(value).toString(exec);
 }
 
+JSCell* DFG_OPERATION operationStringAdd(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);
+}
+
 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 9d57d55..ebcb497 100644
--- a/Source/JavaScriptCore/dfg/DFGOperations.h
+++ b/Source/JavaScriptCore/dfg/DFGOperations.h
@@ -87,6 +87,7 @@
 typedef JSCell* DFG_OPERATION (*C_DFGOperation_EIcf)(ExecState*, InlineCallFrame*);
 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_EOZ)(ExecState*, JSObject*, int32_t);
 typedef JSCell* DFG_OPERATION (*C_DFGOperation_ESt)(ExecState*, Structure*);
 typedef double DFG_OPERATION (*D_DFGOperation_DD)(double, double);
@@ -217,6 +218,7 @@
 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*);
 
 // 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/DFGSpeculativeJIT.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
index 4f83dad..b7dfb8e 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
@@ -3094,6 +3094,61 @@
         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);
diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h
index e352d2e..36e5af0 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h
@@ -1090,6 +1090,11 @@
         m_jit.setupArgumentsWithExecState(arg1, TrustedImmPtr(structure));
         return appendCallWithExceptionCheckSetResult(operation, result);
     }
+    JITCompiler::Call callOperation(C_DFGOperation_EJssJss operation, GPRReg result, GPRReg arg1, GPRReg arg2)
+    {
+        m_jit.setupArgumentsWithExecState(arg1, arg2);
+        return appendCallWithExceptionCheckSetResult(operation, result);
+    }
     JITCompiler::Call callOperation(C_DFGOperation_EJ operation, GPRReg result, GPRReg arg1)
     {
         m_jit.setupArgumentsWithExecState(arg1);
@@ -1488,6 +1493,11 @@
         m_jit.setupArgumentsWithExecState(arg1, TrustedImmPtr(structure));
         return appendCallWithExceptionCheckSetResult(operation, result);
     }
+    JITCompiler::Call callOperation(C_DFGOperation_EJssJss operation, GPRReg result, GPRReg arg1, GPRReg arg2)
+    {
+        m_jit.setupArgumentsWithExecState(arg1, arg2);
+        return appendCallWithExceptionCheckSetResult(operation, result);
+    }
     JITCompiler::Call callOperation(C_DFGOperation_EJ operation, GPRReg result, GPRReg arg1Tag, GPRReg arg1Payload)
     {
         m_jit.setupArgumentsWithExecState(arg1Payload, arg1Tag);
@@ -2127,11 +2137,11 @@
         
         return slowPath;
     }
-
-    // Allocator for an object of a specific size.
-    template <typename StructureType, typename StorageType> // StructureType and StorageType can be GPR or ImmPtr.
-    void emitAllocateJSObject(GPRReg resultGPR, GPRReg allocatorGPR, StructureType structure,
-        StorageType storage, GPRReg scratchGPR, MacroAssembler::JumpList& slowPath)
+    
+    // Allocator for a cell of a specific size.
+    template <typename StructureType> // StructureType can be GPR or ImmPtr.
+    void emitAllocateJSCell(GPRReg resultGPR, GPRReg allocatorGPR, StructureType structure,
+        GPRReg scratchGPR, MacroAssembler::JumpList& slowPath)
     {
         m_jit.loadPtr(MacroAssembler::Address(allocatorGPR, MarkedAllocator::offsetOfFreeListHead()), resultGPR);
         slowPath.append(m_jit.branchTestPtr(MacroAssembler::Zero, resultGPR));
@@ -2143,6 +2153,14 @@
 
         // Initialize the object's Structure.
         m_jit.storePtr(structure, MacroAssembler::Address(resultGPR, JSCell::structureOffset()));
+    }
+
+    // Allocator for an object of a specific size.
+    template <typename StructureType, typename StorageType> // StructureType and StorageType can be GPR or ImmPtr.
+    void emitAllocateJSObject(GPRReg resultGPR, GPRReg allocatorGPR, StructureType structure,
+        StorageType storage, GPRReg scratchGPR, MacroAssembler::JumpList& slowPath)
+    {
+        emitAllocateJSCell(resultGPR, allocatorGPR, structure, scratchGPR, slowPath);
         
         // Initialize the object's property storage pointer.
         m_jit.storePtr(storage, MacroAssembler::Address(resultGPR, JSObject::butterflyOffset()));