Ignore:
Timestamp:
May 6, 2009, 5:06:07 PM (16 years ago)
Author:
[email protected]
Message:

2009-05-06 Gavin Barraclough <[email protected]>

Reviewed by Maciej Stachowiak & Darin Adler.

Improve string concatenation (as coded in JS as a sequence of adds).

Detect patterns corresponding to string concatenation, and change the bytecode
generation to emit a new op_strcat instruction. By handling the full set of
additions within a single function we do not need allocate JSString wrappers
for intermediate results, and we can calculate the size of the output string
prior to allocating storage, in order to prevent reallocation of the buffer.

1.5%-2% progression on Sunspider, largely due to a 30% progression on date-format-xparb.

  • bytecode/CodeBlock.cpp: (JSC::CodeBlock::dump):

Add new opcodes.

  • bytecode/Opcode.h:

Add new opcodes.

  • bytecompiler/BytecodeGenerator.cpp: (JSC::BytecodeGenerator::emitStrcat): (JSC::BytecodeGenerator::emitToPrimitive):

Add generation of new opcodes.

  • bytecompiler/BytecodeGenerator.h:

Add generation of new opcodes.

  • interpreter/Interpreter.cpp: (JSC::Interpreter::privateExecute):

Add implmentation of new opcodes.

  • jit/JIT.cpp: (JSC::JIT::privateCompileMainPass): (JSC::JIT::privateCompileSlowCases):

Add implmentation of new opcodes.

  • jit/JITStubs.cpp: (JSC::JITStubs::cti_op_to_primitive): (JSC::JITStubs::cti_op_strcat):

Add implmentation of new opcodes.

  • jit/JITStubs.h:

Add implmentation of new opcodes.

  • parser/Nodes.cpp: (JSC::BinaryOpNode::emitStrcat): (JSC::BinaryOpNode::emitBytecode): (JSC::ReadModifyResolveNode::emitBytecode):

Add generation of new opcodes.

  • parser/Nodes.h: (JSC::ExpressionNode::): (JSC::AddNode::):

Add methods to allow identification of add nodes.

  • parser/ResultType.h: (JSC::ResultType::definitelyIsString): (JSC::ResultType::forAdd):

Fix error in detection of adds that will produce string results.

  • runtime/Operations.h: (JSC::concatenateStrings):

Add implmentation of new opcodes.

  • runtime/UString.cpp: (JSC::UString::appendNumeric):

Add methods to append numbers to an existing string.

  • runtime/UString.h: (JSC::UString::Rep::createEmptyBuffer): (JSC::UString::BaseString::BaseString):

Add support for creating an empty string with a non-zero capacity available in the BaseString.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/JavaScriptCore/parser/Nodes.cpp

    r43313 r43331  
    11481148}
    11491149
     1150// BinaryOpNode::emitStrcat:
     1151//
     1152// This node generates an op_strcat operation.  This opcode can handle concatenation of three or
     1153// more values, where we can determine a set of separate op_add operations would be operating on
     1154// string values.
     1155//
     1156// This function expects to be operating on a graph of AST nodes looking something like this:
     1157//
     1158//     (a)...     (b)
     1159//          \   /
     1160//           (+)     (c)
     1161//              \   /
     1162//      [d]     ((+))
     1163//         \    /
     1164//          [+=]
     1165//
     1166// The assignment operation is optional, if it exists the register holding the value on the
     1167// lefthand side of the assignment should be passing as the optional 'lhs' argument.
     1168//
     1169// The method should be called on the node at the root of the tree of regular binary add
     1170// operations (marked in the diagram with a double set of parentheses).  This node must
     1171// be performing a string concatenation (determined by statically detecting that at least
     1172// one child must be a string). 
     1173//
     1174// Since the minimum number of values being concatenated together is expected to be 3, if
     1175// a lhs to a concatenating assignment is not provided then the  root add should have at
     1176// least one left child that is also an add that can be determined to be operating on strings.
     1177//
     1178// Fixme: This method should correctly support concatenation on read-modify nodes (+=)
     1179//        but disabling this due to performance degradation (op_strcat needs to be able
     1180//        to append to the existing string).
     1181//
     1182RegisterID* BinaryOpNode::emitStrcat(BytecodeGenerator& generator, RegisterID* dst, RegisterID* lhs)
     1183{
     1184    ASSERT(isAdd());
     1185    ASSERT(resultDescriptor().definitelyIsString());
     1186
     1187    // Create a list of expressions for all the adds in the tree of nodes we can convert into
     1188    // a string concatenation.  The rightmost node (c) is added first.  The rightmost node is
     1189    // added first, and the leftmost child is never added, so the vector produced for the
     1190    // example above will be [ c, b ].
     1191    Vector<ExpressionNode*, 16> reverseExpressionList;
     1192    reverseExpressionList.append(m_expr2.get());
     1193
     1194    // Examine the left child of the add.  So long as this is a string add, add its right-child
     1195    // to the list, and keep processing along the left fork.
     1196    ExpressionNode* leftMostAddChild = m_expr1.get();
     1197    while (leftMostAddChild->isAdd() && leftMostAddChild->resultDescriptor().definitelyIsString()) {
     1198        reverseExpressionList.append(static_cast<AddNode*>(leftMostAddChild)->m_expr2.get());
     1199        leftMostAddChild = static_cast<AddNode*>(leftMostAddChild)->m_expr1.get();
     1200    }
     1201
     1202    Vector<RefPtr<RegisterID>, 16> temporaryRegisters;
     1203
     1204    // If there is an assignment, allocate a temporary to hold the lhs after conversion.
     1205    // We could possibly avoid this (the lhs is converted last anyway, we could let the
     1206    // op_strcat node handle its conversion if required).
     1207    if (lhs)
     1208        temporaryRegisters.append(generator.newTemporary());
     1209
     1210    // Emit code for the leftmost node ((a) in the example).
     1211    temporaryRegisters.append(generator.newTemporary());
     1212    RegisterID* leftMostAddChildTempRegister = temporaryRegisters.last().get();
     1213    generator.emitNode(leftMostAddChildTempRegister, leftMostAddChild);
     1214
     1215    // Note on ordering of conversions:
     1216    //
     1217    // We maintain the same ordering of conversions as we would see if the concatenations
     1218    // was performed as a sequence of adds (otherwise this optimization could change
     1219    // behaviour should an object have been provided a valueOf or toString method).
     1220    //
     1221    // Considering the above example, the sequnce of execution is:
     1222    //     * evaluate operand (a)
     1223    //     * evaluate operand (b)
     1224    //     * convert (a) to primitive   <-  (this would be triggered by the first add)
     1225    //     * convert (b) to primitive   <-  (ditto)
     1226    //     * evaluate operand (c)
     1227    //     * convert (c) to primitive   <-  (this would be triggered by the second add)
     1228    // And optionally, if there is an assignment:
     1229    //     * convert (d) to primitive   <-  (this would be triggered by the assigning addition)
     1230    //
     1231    // As such we do not plant an op to convert the leftmost child now.  Instead, use
     1232    // 'leftMostAddChildTempRegister' as a flag to trigger generation of the conversion
     1233    // once the second node has been generated.  However, if the leftmost child is an
     1234    // immediate we can trivially determine that no conversion will be required.
     1235    // If this is the case
     1236    if (leftMostAddChild->isString())
     1237        leftMostAddChildTempRegister = 0;
     1238
     1239    while (reverseExpressionList.size()) {
     1240        ExpressionNode* node = reverseExpressionList.last();
     1241        reverseExpressionList.removeLast();
     1242
     1243        // Emit the code for the current node.
     1244        temporaryRegisters.append(generator.newTemporary());
     1245        generator.emitNode(temporaryRegisters.last().get(), node);
     1246
     1247        // On the first iteration of this loop, when we first reach this point we have just
     1248        // generated the second node, which means it is time to convert the leftmost operand.
     1249        if (leftMostAddChildTempRegister) {
     1250            generator.emitToPrimitive(leftMostAddChildTempRegister, leftMostAddChildTempRegister);
     1251            leftMostAddChildTempRegister = 0; // Only do this once.
     1252        }
     1253        // Plant a conversion for this node, if necessary.
     1254        if (!node->isString())
     1255            generator.emitToPrimitive(temporaryRegisters.last().get(), temporaryRegisters.last().get());
     1256    }
     1257    ASSERT(temporaryRegisters.size() >= 3);
     1258
     1259    // If there is an assignment convert the lhs now.  This will also copy lhs to
     1260    // the temporary register we allocated for it.
     1261    if (lhs)
     1262        generator.emitToPrimitive(temporaryRegisters[0].get(), lhs);
     1263
     1264    return generator.emitStrcat(generator.finalDestination(dst, temporaryRegisters[0].get()), temporaryRegisters[0].get(), temporaryRegisters.size());
     1265}
     1266
    11501267RegisterID* BinaryOpNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
    11511268{
    11521269    OpcodeID opcodeID = this->opcodeID();
     1270
     1271    if (opcodeID == op_add && m_expr1->isAdd() && m_expr1->resultDescriptor().definitelyIsString())
     1272        return emitStrcat(generator, dst);
     1273
    11531274    if (opcodeID == op_neq) {
    11541275        if (m_expr1->isNull() || m_expr2->isNull()) {
Note: See TracChangeset for help on using the changeset viewer.