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()));