Make Array.join work directly on substrings without reifying them
authordarin@apple.com <darin@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 24 Jun 2015 02:33:18 +0000 (02:33 +0000)
committerdarin@apple.com <darin@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 24 Jun 2015 02:33:18 +0000 (02:33 +0000)
https://bugs.webkit.org/show_bug.cgi?id=146191

Reviewed by Andreas Kling.

Source/JavaScriptCore:

Besides the Array.join change, this has other optimizations based on
profiling the Peacekeeper array benchmark.

I measured a 14% speed improvement in the Peacekeeper array benchmark.

Still a lot of low hanging fruit in that test because so many of functions
on the array prototype are not optimizing for simple cases. For example,
the reverse function does individual get and put calls even when the array
is entirely made up of integers in contiguous storage.

* runtime/ArrayPrototype.cpp:
(JSC::getProperty): Use tryGetIndexQuickly first before getPropertySlot.
(JSC::argumentClampedIndexFromStartOrEnd): Marked inline.
(JSC::shift): Use the getProperty helper in this file instead of using
getPropertySlot. Use putByIndexInline instead of calling putByIndex directly.
In both cases this can yield a faster code path.
(JSC::unshift): Ditto.
(JSC::arrayProtoFuncToString): Updated to use the new JSStringJoiner
interface. Changed local variable name to thisArray since it's not a
JSObject*. Changed loop index to i instead of k.
(JSC::arrayProtoFuncToLocaleString): Updated to use the new JSStringJoiner
interface. Renamed thisObj to thisObject. Added a missing exception check
after the toLocaleString function is called, but before toString is called
the result of that function.
(JSC::arrayProtoFuncJoin): Updated to use the new JSStringJointer interface.
Added a missing exception check after calling toString on the separator
but before calling get to get the first element in the array-like object
being joined. Changed loop index to i instead of k. Added missing exception
check after calling toString on each string from the array before calling
get for the next element.
(JSC::arrayProtoFuncConcat): Use JSArray::length instead of using the
getLength function.
(JSC::arrayProtoFuncReverse): Ditto. Also use putByIndexInline.
(JSC::arrayProtoFuncShift): Ditto.
(JSC::arrayProtoFuncSplice): Use getIndex instead of get, which includes some
additional optimizations.
(JSC::getOrHole): Deleted. Unused function.
(JSC::arrayProtoFuncUnShift): Use putByIndexInline.

* runtime/ExceptionHelpers.cpp:
(JSC::errorDescriptionForValue): Removed the duplicate copy of the the logic
from JSValue::toString.

* runtime/JSCJSValue.cpp:
(JSC::JSValue::toStringSlowCase): Improved the performance when converting a
small integer to a single character string.
(JSC::JSValue::toWTFStringSlowCase): Moved the contents of the
inlineJSValueNotStringtoString function here.
* runtime/JSCJSValue.h: Removed no longer used toWTFStringInline and fixed
a comment with a typo.

* runtime/JSObject.h:
(JSC::JSObject::putByIndexInline): Marked ALWAYS_INLINE because this was not
getting inlined at some call sites.
(JSC::JSObject::indexingData): Deleted. Unused function.
(JSC::JSObject::currentIndexingData): Deleted. Unused function.
(JSC::JSObject::getHolyIndexQuickly): Deleted. Unused function.
(JSC::JSObject::relevantLength): Deleted. Unused function.
(JSC::JSObject::currentRelevantLength): Deleted. Unused function.

* runtime/JSString.h: Added the StringViewWithUnderlyingString struct and
the viewWithUnderlyingString function. Removed the inlineJSValueNotStringtoString
and toWTFStringInline functions.

* runtime/JSStringJoiner.cpp:
(JSC::appendStringToData): Changed this to be a template instead of writing
it out, since StringView::getCharactersWithUpconvert does almsot exactly what
this function was trying to do.
(JSC::joinStrings): Rewrote this to use StringView.
(JSC::JSStringJoiner::joinedLength): Added. Factored out from the join function.
(JSC::JSStringJoiner::join): Rewrote to make it a bit simpler. Added an assertion
that we entirely filled capacity, since we are now reserving capacity and using
uncheckedAppend. Use String instead of RefPtr<StringImpl> because there was no
particular value to using the impl directly.

* runtime/JSStringJoiner.h: Changed the interface to the class to use StringView.
Also changed this class so it now has the responsibility to convert each JSValue
into a string. This let us share more code between toString and join, and also
lets us use the new viewWithUnderlyingString function, which could be confusing at
all the call sites, but is easier to understand here.

Source/WTF:

* wtf/Vector.h: Added an overload of uncheckedAppend like the one we added
a while back, a non-template function that forwards through to the function
template. This lets us call uncheckedAppend on an argument list and have it
properly convert it to the Vector's element type.

* wtf/text/StringView.h:
(WTF::StringView::getCharactersWithUpconvert): Changed to not use memcpy;
saw some indication the hand-written loop was faster when profiling. Also
use m_length directly when we know we are dealing with an 8-bit string,
since the masking that the index function does is not needed in that case.
(WTF::StringView::UpconvertedCharacters::UpconvertedCharacters): Ditto.
(WTF::StringView::toString): Ditto.
(WTF::StringView::toFloat): Ditto.
(WTF::StringView::toInt): Ditto.
(WTF::StringView::toStringWithoutCopying): Ditto.
(WTF::StringView::find): Ditto.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@185899 268f45cc-cd09-0410-ab3c-d52691b4dbfc

12 files changed:
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/runtime/ArrayPrototype.cpp
Source/JavaScriptCore/runtime/ExceptionHelpers.cpp
Source/JavaScriptCore/runtime/JSCJSValue.cpp
Source/JavaScriptCore/runtime/JSCJSValue.h
Source/JavaScriptCore/runtime/JSObject.h
Source/JavaScriptCore/runtime/JSString.h
Source/JavaScriptCore/runtime/JSStringJoiner.cpp
Source/JavaScriptCore/runtime/JSStringJoiner.h
Source/WTF/ChangeLog
Source/WTF/wtf/Vector.h
Source/WTF/wtf/text/StringView.h

index 0d1aa1a70e52f415030ef709b9c637762541ad5d..a3453247310da027cbb1ff87ad29ed2c8a90ab40 100644 (file)
@@ -1,3 +1,91 @@
+2015-06-22  Darin Adler  <darin@apple.com>
+
+        Make Array.join work directly on substrings without reifying them
+        https://bugs.webkit.org/show_bug.cgi?id=146191
+
+        Reviewed by Andreas Kling.
+
+        Besides the Array.join change, this has other optimizations based on
+        profiling the Peacekeeper array benchmark.
+
+        I measured a 14% speed improvement in the Peacekeeper array benchmark.
+
+        Still a lot of low hanging fruit in that test because so many of functions
+        on the array prototype are not optimizing for simple cases. For example,
+        the reverse function does individual get and put calls even when the array
+        is entirely made up of integers in contiguous storage.
+
+        * runtime/ArrayPrototype.cpp:
+        (JSC::getProperty): Use tryGetIndexQuickly first before getPropertySlot.
+        (JSC::argumentClampedIndexFromStartOrEnd): Marked inline.
+        (JSC::shift): Use the getProperty helper in this file instead of using
+        getPropertySlot. Use putByIndexInline instead of calling putByIndex directly.
+        In both cases this can yield a faster code path.
+        (JSC::unshift): Ditto.
+        (JSC::arrayProtoFuncToString): Updated to use the new JSStringJoiner
+        interface. Changed local variable name to thisArray since it's not a
+        JSObject*. Changed loop index to i instead of k.
+        (JSC::arrayProtoFuncToLocaleString): Updated to use the new JSStringJoiner
+        interface. Renamed thisObj to thisObject. Added a missing exception check
+        after the toLocaleString function is called, but before toString is called
+        the result of that function.
+        (JSC::arrayProtoFuncJoin): Updated to use the new JSStringJointer interface.
+        Added a missing exception check after calling toString on the separator
+        but before calling get to get the first element in the array-like object
+        being joined. Changed loop index to i instead of k. Added missing exception
+        check after calling toString on each string from the array before calling
+        get for the next element.
+        (JSC::arrayProtoFuncConcat): Use JSArray::length instead of using the
+        getLength function.
+        (JSC::arrayProtoFuncReverse): Ditto. Also use putByIndexInline.
+        (JSC::arrayProtoFuncShift): Ditto.
+        (JSC::arrayProtoFuncSplice): Use getIndex instead of get, which includes some
+        additional optimizations.
+        (JSC::getOrHole): Deleted. Unused function.
+        (JSC::arrayProtoFuncUnShift): Use putByIndexInline.
+
+        * runtime/ExceptionHelpers.cpp:
+        (JSC::errorDescriptionForValue): Removed the duplicate copy of the the logic
+        from JSValue::toString.
+
+        * runtime/JSCJSValue.cpp:
+        (JSC::JSValue::toStringSlowCase): Improved the performance when converting a
+        small integer to a single character string.
+        (JSC::JSValue::toWTFStringSlowCase): Moved the contents of the
+        inlineJSValueNotStringtoString function here.
+        * runtime/JSCJSValue.h: Removed no longer used toWTFStringInline and fixed
+        a comment with a typo.
+
+        * runtime/JSObject.h:
+        (JSC::JSObject::putByIndexInline): Marked ALWAYS_INLINE because this was not
+        getting inlined at some call sites.
+        (JSC::JSObject::indexingData): Deleted. Unused function.
+        (JSC::JSObject::currentIndexingData): Deleted. Unused function.
+        (JSC::JSObject::getHolyIndexQuickly): Deleted. Unused function.
+        (JSC::JSObject::relevantLength): Deleted. Unused function.
+        (JSC::JSObject::currentRelevantLength): Deleted. Unused function.
+
+        * runtime/JSString.h: Added the StringViewWithUnderlyingString struct and
+        the viewWithUnderlyingString function. Removed the inlineJSValueNotStringtoString
+        and toWTFStringInline functions.
+
+        * runtime/JSStringJoiner.cpp:
+        (JSC::appendStringToData): Changed this to be a template instead of writing
+        it out, since StringView::getCharactersWithUpconvert does almsot exactly what
+        this function was trying to do.
+        (JSC::joinStrings): Rewrote this to use StringView.
+        (JSC::JSStringJoiner::joinedLength): Added. Factored out from the join function.
+        (JSC::JSStringJoiner::join): Rewrote to make it a bit simpler. Added an assertion
+        that we entirely filled capacity, since we are now reserving capacity and using
+        uncheckedAppend. Use String instead of RefPtr<StringImpl> because there was no
+        particular value to using the impl directly.
+
+        * runtime/JSStringJoiner.h: Changed the interface to the class to use StringView.
+        Also changed this class so it now has the responsibility to convert each JSValue
+        into a string. This let us share more code between toString and join, and also
+        lets us use the new viewWithUnderlyingString function, which could be confusing at
+        all the call sites, but is easier to understand here.
+
 2015-06-23  Matthew Mirman  <mmirman@apple.com>
 
         Completes native binding descriptors with native getters and potentially setters.
index 92804d6225b7bcd3fe204422f87d05f56f9f9784..29d2a4408c880fe6dab6a19086c8242d16f3d9d1 100644 (file)
@@ -138,11 +138,12 @@ void ArrayPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject)
 
 // ------------------------------ Array Functions ----------------------------
 
-// Helper function
-static ALWAYS_INLINE JSValue getProperty(ExecState* exec, JSObject* obj, unsigned index)
+static ALWAYS_INLINE JSValue getProperty(ExecState* exec, JSObject* object, unsigned index)
 {
-    PropertySlot slot(obj);
-    if (!obj->getPropertySlot(exec, index, slot))
+    if (JSValue result = object->tryGetIndexQuickly(index))
+        return result;
+    PropertySlot slot(object);
+    if (!object->getPropertySlot(exec, index, slot))
         return JSValue();
     return slot.getValue(exec, index);
 }
@@ -160,7 +161,7 @@ static void putLength(ExecState* exec, JSObject* obj, JSValue value)
     obj->methodTable()->put(obj, exec, exec->propertyNames().length, value, slot);
 }
 
-static unsigned argumentClampedIndexFromStartOrEnd(ExecState* exec, int argument, unsigned length, unsigned undefinedValue = 0)
+static inline unsigned argumentClampedIndexFromStartOrEnd(ExecState* exec, int argument, unsigned length, unsigned undefinedValue = 0)
 {
     JSValue value = exec->argument(argument);
     if (value.isUndefined())
@@ -174,7 +175,6 @@ static unsigned argumentClampedIndexFromStartOrEnd(ExecState* exec, int argument
     return indexDouble > length ? length : static_cast<unsigned>(indexDouble);
 }
 
-
 // The shift/unshift function implement the shift/unshift behaviour required
 // by the corresponding array prototype methods, and by splice. In both cases,
 // the methods are operating an an array or array like object.
@@ -189,6 +189,7 @@ static unsigned argumentClampedIndexFromStartOrEnd(ExecState* exec, int argument
 // currentCount) will be shifted to the left or right as appropriate; in the
 // case of shift this must be removing values, in the case of unshift this
 // must be introducing new values.
+
 template<JSArray::ShiftCountMode shiftCountMode>
 void shift(ExecState* exec, JSObject* thisObj, unsigned header, unsigned currentCount, unsigned resultCount, unsigned length)
 {
@@ -207,12 +208,10 @@ void shift(ExecState* exec, JSObject* thisObj, unsigned header, unsigned current
     for (unsigned k = header; k < length - currentCount; ++k) {
         unsigned from = k + currentCount;
         unsigned to = k + resultCount;
-        PropertySlot slot(thisObj);
-        if (thisObj->getPropertySlot(exec, from, slot)) {
-            JSValue value = slot.getValue(exec, from);
+        if (JSValue value = getProperty(exec, thisObj, from)) {
             if (exec->hadException())
                 return;
-            thisObj->methodTable(exec->vm())->putByIndex(thisObj, exec, to, value, true);
+            thisObj->putByIndexInline(exec, to, value, true);
             if (exec->hadException())
                 return;
         } else if (!thisObj->methodTable(exec->vm())->deletePropertyByIndex(thisObj, exec, to)) {
@@ -227,6 +226,7 @@ void shift(ExecState* exec, JSObject* thisObj, unsigned header, unsigned current
         }
     }
 }
+
 template<JSArray::ShiftCountMode shiftCountMode>
 void unshift(ExecState* exec, JSObject* thisObj, unsigned header, unsigned currentCount, unsigned resultCount, unsigned length)
 {
@@ -247,16 +247,14 @@ void unshift(ExecState* exec, JSObject* thisObj, unsigned header, unsigned curre
         if (array->length() == length && array->unshiftCount<shiftCountMode>(exec, header, count))
             return;
     }
-    
+
     for (unsigned k = length - currentCount; k > header; --k) {
         unsigned from = k + currentCount - 1;
         unsigned to = k + resultCount - 1;
-        PropertySlot slot(thisObj);
-        if (thisObj->getPropertySlot(exec, from, slot)) {
-            JSValue value = slot.getValue(exec, from);
+        if (JSValue value = getProperty(exec, thisObj, from)) {
             if (exec->hadException())
                 return;
-            thisObj->methodTable(exec->vm())->putByIndex(thisObj, exec, to, value, true);
+            thisObj->putByIndexInline(exec, to, value, true);
         } else if (!thisObj->methodTable(exec->vm())->deletePropertyByIndex(thisObj, exec, to)) {
             throwTypeError(exec, ASCIILiteral("Unable to delete property."));
             return;
@@ -291,80 +289,75 @@ EncodedJSValue JSC_HOST_CALL arrayProtoFuncToString(ExecState* exec)
         return JSValue::encode(call(exec, function, callType, callData, thisObject, exec->emptyList()));
 
     ASSERT(isJSArray(thisValue));
-    JSArray* thisObj = asArray(thisValue);
+    JSArray* thisArray = asArray(thisValue);
     
-    unsigned length = getLength(exec, thisObj);
-    if (exec->hadException())
-        return JSValue::encode(jsUndefined());
+    unsigned length = thisArray->length();
 
-    StringRecursionChecker checker(exec, thisObj);
+    StringRecursionChecker checker(exec, thisArray);
     if (JSValue earlyReturnValue = checker.earlyReturnValue())
         return JSValue::encode(earlyReturnValue);
 
-    String separator(",", String::ConstructFromLiteral);
-    JSStringJoiner stringJoiner(separator, length);
-    for (unsigned k = 0; k < length; k++) {
-        JSValue element;
-        if (thisObj->canGetIndexQuickly(k))
-            element = thisObj->getIndexQuickly(k);
-        else {
-            element = thisObj->get(exec, k);
+    JSStringJoiner joiner(*exec, ',', length);
+    if (exec->hadException())
+        return JSValue::encode(jsUndefined());
+
+    for (unsigned i = 0; i < length; ++i) {
+        JSValue element = thisArray->tryGetIndexQuickly(i);
+        if (!element) {
+            element = thisArray->get(exec, i);
             if (exec->hadException())
                 return JSValue::encode(jsUndefined());
         }
-
-        if (element.isUndefinedOrNull())
-            stringJoiner.append(String());
-        else
-            stringJoiner.append(element.toWTFString(exec));
-
+        joiner.append(*exec, element);
         if (exec->hadException())
             return JSValue::encode(jsUndefined());
     }
-    return JSValue::encode(stringJoiner.join(exec));
+
+    return JSValue::encode(joiner.join(*exec));
 }
 
 EncodedJSValue JSC_HOST_CALL arrayProtoFuncToLocaleString(ExecState* exec)
 {
     JSValue thisValue = exec->thisValue().toThis(exec, StrictMode);
 
-    JSObject* thisObj = thisValue.toObject(exec);
+    JSObject* thisObject = thisValue.toObject(exec);
     if (exec->hadException())
         return JSValue::encode(jsUndefined());
 
-    unsigned length = getLength(exec, thisObj);
+    unsigned length = getLength(exec, thisObject);
     if (exec->hadException())
         return JSValue::encode(jsUndefined());
 
-    StringRecursionChecker checker(exec, thisObj);
+    StringRecursionChecker checker(exec, thisObject);
     if (JSValue earlyReturnValue = checker.earlyReturnValue())
         return JSValue::encode(earlyReturnValue);
 
-    String separator(",", String::ConstructFromLiteral);
-    JSStringJoiner stringJoiner(separator, length);
-    for (unsigned k = 0; k < length; k++) {
-        JSValue element = thisObj->get(exec, k);
+    JSStringJoiner stringJoiner(*exec, ',', length);
+    if (exec->hadException())
+        return JSValue::encode(jsUndefined());
+
+    for (unsigned i = 0; i < length; ++i) {
+        JSValue element = thisObject->getIndex(exec, i);
         if (exec->hadException())
             return JSValue::encode(jsUndefined());
-        if (!element.isUndefinedOrNull()) {
-            JSObject* o = element.toObject(exec);
-            JSValue conversionFunction = o->get(exec, exec->propertyNames().toLocaleString);
-            if (exec->hadException())
-                return JSValue::encode(jsUndefined());
-            String str;
-            CallData callData;
-            CallType callType = getCallData(conversionFunction, callData);
-            if (callType != CallTypeNone)
-                str = call(exec, conversionFunction, callType, callData, element, exec->emptyList()).toWTFString(exec);
-            else
-                str = element.toWTFString(exec);
+        if (element.isUndefinedOrNull())
+            continue;
+        JSValue conversionFunction = element.get(exec, exec->propertyNames().toLocaleString);
+        if (exec->hadException())
+            return JSValue::encode(jsUndefined());
+        CallData callData;
+        CallType callType = getCallData(conversionFunction, callData);
+        if (callType != CallTypeNone) {
+            element = call(exec, conversionFunction, callType, callData, element, exec->emptyList());
             if (exec->hadException())
                 return JSValue::encode(jsUndefined());
-            stringJoiner.append(str);
         }
+        stringJoiner.append(*exec, element);
+        if (exec->hadException())
+            return JSValue::encode(jsUndefined());
     }
 
-    return JSValue::encode(stringJoiner.join(exec));
+    return JSValue::encode(stringJoiner.join(*exec));
 }
 
 EncodedJSValue JSC_HOST_CALL arrayProtoFuncJoin(ExecState* exec)
@@ -378,61 +371,56 @@ EncodedJSValue JSC_HOST_CALL arrayProtoFuncJoin(ExecState* exec)
     if (JSValue earlyReturnValue = checker.earlyReturnValue())
         return JSValue::encode(earlyReturnValue);
 
-    String separator;
-    if (!exec->argument(0).isUndefined())
-        separator = exec->argument(0).toWTFString(exec);
-    if (separator.isNull())
-        separator = String(",", String::ConstructFromLiteral);
+    JSString* separator = nullptr;
+    if (!exec->argument(0).isUndefined()) {
+        separator = exec->argument(0).toString(exec);
+        if (exec->hadException())
+            return JSValue::encode(jsUndefined());
+    }
+
+    const LChar comma = ',';
+    JSStringJoiner stringJoiner(*exec, separator ? separator->view(exec) : StringView{ &comma, 1 }, length);
+    if (exec->hadException())
+        return JSValue::encode(jsUndefined());
 
-    JSStringJoiner stringJoiner(separator, length);
+    unsigned i = 0;
 
-    unsigned k = 0;
     if (isJSArray(thisObj)) {
         JSArray* array = asArray(thisObj);
-
-        for (; k < length; k++) {
-            if (!array->canGetIndexQuickly(k))
-                break;
-
-            JSValue element = array->getIndexQuickly(k);
-            if (!element.isUndefinedOrNull())
-                stringJoiner.append(element.toWTFStringInline(exec));
-            else
-                stringJoiner.append(String());
+        for (; i < length && array->canGetIndexQuickly(i); ++i) {
+            stringJoiner.append(*exec, array->getIndexQuickly(i));
+            if (exec->hadException())
+                return JSValue::encode(jsUndefined());
         }
     }
 
-    for (; k < length; k++) {
-        JSValue element = thisObj->get(exec, k);
+    for (; i < length; ++i) {
+        JSValue element = thisObj->get(exec, i);
+        if (exec->hadException())
+            return JSValue::encode(jsUndefined());
+        stringJoiner.append(*exec, element);
         if (exec->hadException())
             return JSValue::encode(jsUndefined());
-        if (!element.isUndefinedOrNull())
-            stringJoiner.append(element.toWTFStringInline(exec));
-        else
-            stringJoiner.append(String());
     }
 
-    return JSValue::encode(stringJoiner.join(exec));
+    return JSValue::encode(stringJoiner.join(*exec));
 }
 
 EncodedJSValue JSC_HOST_CALL arrayProtoFuncConcat(ExecState* exec)
 {
     JSValue thisValue = exec->thisValue().toThis(exec, StrictMode);
-    size_t argCount = exec->argumentCount();
+    unsigned argCount = exec->argumentCount();
     JSValue curArg = thisValue.toObject(exec);
     Checked<unsigned, RecordOverflow> finalArraySize = 0;
 
-    for (size_t i = 0;;) {
-        if (JSArray* currentArray = jsDynamicCast<JSArray*>(curArg)) {
-            finalArraySize += getLength(exec, currentArray);
-            if (exec->hadException())
-                return JSValue::encode(jsUndefined());
-        } else
-            finalArraySize++;
+    for (unsigned i = 0; ; ++i) {
+        if (JSArray* currentArray = jsDynamicCast<JSArray*>(curArg))
+            finalArraySize += currentArray->length();
+        else
+            ++finalArraySize;
         if (i == argCount)
             break;
         curArg = exec->uncheckedArgument(i);
-        ++i;
     }
 
     if (finalArraySize.hasOverflowed())
@@ -444,9 +432,9 @@ EncodedJSValue JSC_HOST_CALL arrayProtoFuncConcat(ExecState* exec)
 
     curArg = thisValue.toObject(exec);
     unsigned n = 0;
-    for (size_t i = 0;;) {
+    for (unsigned i = 0; ; ++i) {
         if (JSArray* currentArray = jsDynamicCast<JSArray*>(curArg)) {
-            unsigned length = getLength(exec, currentArray);
+            unsigned length = currentArray->length();
             if (exec->hadException())
                 return JSValue::encode(jsUndefined());
             for (unsigned k = 0; k < length; ++k) {
@@ -464,7 +452,6 @@ EncodedJSValue JSC_HOST_CALL arrayProtoFuncConcat(ExecState* exec)
         if (i == argCount)
             break;
         curArg = exec->uncheckedArgument(i);
-        ++i;
     }
     arr->setLength(exec, n);
     return JSValue::encode(arr);
@@ -550,7 +537,7 @@ EncodedJSValue JSC_HOST_CALL arrayProtoFuncReverse(ExecState* exec)
             return JSValue::encode(jsUndefined());
 
         if (obj2) {
-            thisObj->methodTable(exec->vm())->putByIndex(thisObj, exec, k, obj2, true);
+            thisObj->putByIndexInline(exec, k, obj2, true);
             if (exec->hadException())
                 return JSValue::encode(jsUndefined());
         } else if (!thisObj->methodTable(exec->vm())->deletePropertyByIndex(thisObj, exec, k)) {
@@ -559,7 +546,7 @@ EncodedJSValue JSC_HOST_CALL arrayProtoFuncReverse(ExecState* exec)
         }
 
         if (obj) {
-            thisObj->methodTable(exec->vm())->putByIndex(thisObj, exec, lk1, obj, true);
+            thisObj->putByIndexInline(exec, lk1, obj, true);
             if (exec->hadException())
                 return JSValue::encode(jsUndefined());
         } else if (!thisObj->methodTable(exec->vm())->deletePropertyByIndex(thisObj, exec, lk1)) {
@@ -582,7 +569,7 @@ EncodedJSValue JSC_HOST_CALL arrayProtoFuncShift(ExecState* exec)
         putLength(exec, thisObj, jsNumber(length));
         result = jsUndefined();
     } else {
-        result = thisObj->get(exec, 0);
+        result = thisObj->getIndex(exec, 0);
         shift<JSArray::ShiftCountForShift>(exec, thisObj, 0, 1, 0, length);
         if (exec->hadException())
             return JSValue::encode(jsUndefined());
@@ -621,15 +608,6 @@ EncodedJSValue JSC_HOST_CALL arrayProtoFuncSlice(ExecState* exec)
     return JSValue::encode(result);
 }
 
-inline JSValue getOrHole(JSObject* obj, ExecState* exec, unsigned propertyName)
-{
-    PropertySlot slot(obj);
-    if (obj->getPropertySlot(exec, propertyName, slot))
-        return slot.getValue(exec, propertyName);
-
-    return JSValue();
-}
-
 EncodedJSValue JSC_HOST_CALL arrayProtoFuncSplice(ExecState* exec)
 {
     // 15.4.4.12
@@ -686,7 +664,7 @@ EncodedJSValue JSC_HOST_CALL arrayProtoFuncSplice(ExecState* exec)
             return JSValue::encode(jsUndefined());
     }
     for (unsigned k = 0; k < additionalArgs; ++k) {
-        thisObj->methodTable(exec->vm())->putByIndex(thisObj, exec, k + begin, exec->uncheckedArgument(k + 2), true);
+        thisObj->putByIndexInline(exec, k + begin, exec->uncheckedArgument(k + 2), true);
         if (exec->hadException())
             return JSValue::encode(jsUndefined());
     }
@@ -711,7 +689,7 @@ EncodedJSValue JSC_HOST_CALL arrayProtoFuncUnShift(ExecState* exec)
             return JSValue::encode(jsUndefined());
     }
     for (unsigned k = 0; k < nrArgs; ++k) {
-        thisObj->methodTable(exec->vm())->putByIndex(thisObj, exec, k, exec->uncheckedArgument(k), true);
+        thisObj->putByIndexInline(exec, k, exec->uncheckedArgument(k), true);
         if (exec->hadException())
             return JSValue::encode(jsUndefined());
     }
index 7cdf36a12926028f0cd2d667429484f90b27b629..4970212ec9cf379c265a6fc13bd3a4c1d6b176df 100644 (file)
@@ -82,32 +82,16 @@ JSObject* createUndefinedVariableError(ExecState* exec, const Identifier& ident)
     
 JSString* errorDescriptionForValue(ExecState* exec, JSValue v)
 {
-    VM& vm = exec->vm();
-    if (v.isNull())
-        return vm.smallStrings.nullString();
-    if (v.isUndefined())
-        return vm.smallStrings.undefinedString();
-    if (v.isInt32())
-        return jsString(&vm, vm.numericStrings.add(v.asInt32()));
-    if (v.isDouble())
-        return jsString(&vm, vm.numericStrings.add(v.asDouble()));
-    if (v.isTrue())
-        return vm.smallStrings.trueString();
-    if (v.isFalse())
-        return vm.smallStrings.falseString();
     if (v.isString())
-        return jsNontrivialString(&vm, makeString('"',  asString(v)->value(exec), '"'));
+        return jsNontrivialString(exec, makeString('"',  asString(v)->value(exec), '"'));
     if (v.isObject()) {
         CallData callData;
         JSObject* object = asObject(v);
         if (object->methodTable()->getCallData(object, callData) != CallTypeNone)
-            return vm.smallStrings.functionString();
+            return exec->vm().smallStrings.functionString();
         return jsString(exec, JSObject::calculatedClassName(object));
     }
-    
-    // The JSValue should never be empty, so this point in the code should never be reached.
-    ASSERT_NOT_REACHED();
-    return vm.smallStrings.emptyString();
+    return v.toString(exec);
 }
     
 static String defaultApproximateSourceError(const String& originalMessage, const String& sourceText)
index a7083aedf249fc05c81e0fa440fd5547122e5e5c..9e0aab1774de17a03ca6de12c9e98f9d7236360b 100644 (file)
@@ -353,8 +353,12 @@ JSString* JSValue::toStringSlowCase(ExecState* exec) const
 {
     VM& vm = exec->vm();
     ASSERT(!isString());
-    if (isInt32())
-        return jsString(&vm, vm.numericStrings.add(asInt32()));
+    if (isInt32()) {
+        auto integer = asInt32();
+        if (static_cast<unsigned>(integer) <= 9)
+            return vm.smallStrings.singleCharacterString(integer + '0');
+        return jsNontrivialString(&vm, vm.numericStrings.add(integer));
+    }
     if (isDouble())
         return jsString(&vm, vm.numericStrings.add(asDouble()));
     if (isTrue())
@@ -380,7 +384,20 @@ JSString* JSValue::toStringSlowCase(ExecState* exec) const
 
 String JSValue::toWTFStringSlowCase(ExecState* exec) const
 {
-    return inlineJSValueNotStringtoString(*this, exec);
+    VM& vm = exec->vm();
+    if (isInt32())
+        return vm.numericStrings.add(asInt32());
+    if (isDouble())
+        return vm.numericStrings.add(asDouble());
+    if (isTrue())
+        return vm.propertyNames->trueKeyword.string();
+    if (isFalse())
+        return vm.propertyNames->falseKeyword.string();
+    if (isNull())
+        return vm.propertyNames->nullKeyword.string();
+    if (isUndefined())
+        return vm.propertyNames->undefinedKeyword.string();
+    return toString(exec)->value(exec);
 }
 
 } // namespace JSC
index 79b09b418350e8e2e153e4c2587e5fc38ecca5d2..039c14eb7a12aae6565dba7fa4fbdcbfaff31993 100644 (file)
@@ -252,7 +252,6 @@ public:
     JSString* toString(ExecState*) const;
     Identifier toPropertyKey(ExecState*) const;
     WTF::String toWTFString(ExecState*) const;
-    WTF::String toWTFStringInline(ExecState*) const;
     JSObject* toObject(ExecState*) const;
     JSObject* toObject(ExecState*, JSGlobalObject*) const;
 
@@ -262,8 +261,8 @@ public:
     int32_t toInt32(ExecState*) const;
     uint32_t toUInt32(ExecState*) const;
 
-    // Floating point conversions (this is a convenience method for webcore;
-    // signle precision float is not a representation used in JS or JSC).
+    // Floating point conversions (this is a convenience function for WebCore;
+    // single precision float is not a representation used in JS or JSC).
     float toFloat(ExecState* exec) const { return static_cast<float>(toNumber(exec)); }
 
     // Object operations, with the toObject operation included.
index bcc26cd7135f8592d278d4f6bac2ebcbfc366cc1..5c9622cc4de4adabe51136c239c5ea1732382f2f 100644 (file)
@@ -145,7 +145,7 @@ public:
     JS_EXPORT_PRIVATE static void put(JSCell*, ExecState*, PropertyName, JSValue, PutPropertySlot&);
     JS_EXPORT_PRIVATE static void putByIndex(JSCell*, ExecState*, unsigned propertyName, JSValue, bool shouldThrow);
         
-    void putByIndexInline(ExecState* exec, unsigned propertyName, JSValue value, bool shouldThrow)
+    ALWAYS_INLINE void putByIndexInline(ExecState* exec, unsigned propertyName, JSValue value, bool shouldThrow)
     {
         if (canSetIndexQuickly(propertyName)) {
             setIndexQuickly(exec->vm(), propertyName, value);
@@ -837,103 +837,6 @@ protected:
     // This is relevant to undecided, int32, double, and contiguous.
     unsigned countElements();
         
-    // This strange method returns a pointer to the start of the indexed data
-    // as if it contained JSValues. But it won't always contain JSValues.
-    // Make sure you cast this to the appropriate type before using.
-    template<IndexingType indexingType>
-    ContiguousJSValues indexingData()
-    {
-        switch (indexingType) {
-        case ALL_INT32_INDEXING_TYPES:
-        case ALL_DOUBLE_INDEXING_TYPES:
-        case ALL_CONTIGUOUS_INDEXING_TYPES:
-            return m_butterfly->contiguous();
-                
-        case ALL_ARRAY_STORAGE_INDEXING_TYPES:
-            return m_butterfly->arrayStorage()->vector();
-
-        default:
-            CRASH();
-            return ContiguousJSValues();
-        }
-    }
-
-    ContiguousJSValues currentIndexingData()
-    {
-        switch (indexingType()) {
-        case ALL_INT32_INDEXING_TYPES:
-        case ALL_CONTIGUOUS_INDEXING_TYPES:
-            return m_butterfly->contiguous();
-
-        case ALL_ARRAY_STORAGE_INDEXING_TYPES:
-            return m_butterfly->arrayStorage()->vector();
-
-        default:
-            CRASH();
-            return ContiguousJSValues();
-        }
-    }
-        
-    JSValue getHolyIndexQuickly(unsigned i)
-    {
-        ASSERT(i < m_butterfly->vectorLength());
-        switch (indexingType()) {
-        case ALL_INT32_INDEXING_TYPES:
-        case ALL_CONTIGUOUS_INDEXING_TYPES:
-            return m_butterfly->contiguous()[i].get();
-        case ALL_DOUBLE_INDEXING_TYPES: {
-            double value = m_butterfly->contiguousDouble()[i];
-            if (value == value)
-                return JSValue(JSValue::EncodeAsDouble, value);
-            return JSValue();
-        }
-        case ALL_ARRAY_STORAGE_INDEXING_TYPES:
-            return m_butterfly->arrayStorage()->m_vector[i].get();
-        default:
-            CRASH();
-            return JSValue();
-        }
-    }
-        
-    template<IndexingType indexingType>
-    unsigned relevantLength()
-    {
-        switch (indexingType) {
-        case ALL_INT32_INDEXING_TYPES:
-        case ALL_DOUBLE_INDEXING_TYPES:
-        case ALL_CONTIGUOUS_INDEXING_TYPES:
-            return m_butterfly->publicLength();
-                
-        case ALL_ARRAY_STORAGE_INDEXING_TYPES:
-            return std::min(
-                m_butterfly->arrayStorage()->length(),
-                m_butterfly->arrayStorage()->vectorLength());
-                
-        default:
-            CRASH();
-            return 0;
-        }
-    }
-
-    unsigned currentRelevantLength()
-    {
-        switch (indexingType()) {
-        case ALL_INT32_INDEXING_TYPES:
-        case ALL_DOUBLE_INDEXING_TYPES:
-        case ALL_CONTIGUOUS_INDEXING_TYPES:
-            return m_butterfly->publicLength();
-
-        case ALL_ARRAY_STORAGE_INDEXING_TYPES:
-            return std::min(
-                m_butterfly->arrayStorage()->length(),
-                m_butterfly->arrayStorage()->vectorLength());
-
-        default:
-            CRASH();
-            return 0;
-        }
-    }
-
 private:
     friend class LLIntOffsetsExtractor;
         
index 66a38b2ac8b568877ed2a324e139bd3aeefd2a44..034e7cb50cdd1d73735f6a7c8e399ef8c9ceb9ca 100644 (file)
@@ -62,6 +62,11 @@ JSString* jsOwnedString(ExecState*, const String&);
 
 JSRopeString* jsStringBuilder(VM*);
 
+struct StringViewWithUnderlyingString {
+    StringView view;
+    String underlyingString;
+};
+
 class JSString : public JSCell {
 public:
     friend class JIT;
@@ -144,6 +149,7 @@ public:
     AtomicString toAtomicString(ExecState*) const;
     RefPtr<AtomicStringImpl> toExistingAtomicString(ExecState*) const;
     StringView view(ExecState*) const;
+    StringViewWithUnderlyingString viewWithUnderlyingString(ExecState&) const;
     const String& value(ExecState*) const;
     const String& tryGetValue() const;
     const StringImpl* tryGetValueImpl() const;
@@ -384,6 +390,7 @@ private:
     void resolveRopeInternal16NoSubstring(UChar*) const;
     void clearFibers() const;
     StringView view(ExecState*) const;
+    StringViewWithUnderlyingString viewWithUnderlyingString(ExecState&) const;
 
     WriteBarrierBase<JSString>& fiber(unsigned i) const
     {
@@ -673,32 +680,6 @@ inline String JSValue::toWTFString(ExecState* exec) const
     return toWTFStringSlowCase(exec);
 }
 
-ALWAYS_INLINE String inlineJSValueNotStringtoString(const JSValue& value, ExecState* exec)
-{
-    VM& vm = exec->vm();
-    if (value.isInt32())
-        return vm.numericStrings.add(value.asInt32());
-    if (value.isDouble())
-        return vm.numericStrings.add(value.asDouble());
-    if (value.isTrue())
-        return vm.propertyNames->trueKeyword.string();
-    if (value.isFalse())
-        return vm.propertyNames->falseKeyword.string();
-    if (value.isNull())
-        return vm.propertyNames->nullKeyword.string();
-    if (value.isUndefined())
-        return vm.propertyNames->undefinedKeyword.string();
-    return value.toString(exec)->value(exec);
-}
-
-ALWAYS_INLINE String JSValue::toWTFStringInline(ExecState* exec) const
-{
-    if (isString())
-        return static_cast<JSString*>(asCell())->value(exec);
-
-    return inlineJSValueNotStringtoString(*this, exec);
-}
-
 ALWAYS_INLINE StringView JSRopeString::view(ExecState* exec) const
 {
     if (isSubstring()) {
@@ -710,6 +691,18 @@ ALWAYS_INLINE StringView JSRopeString::view(ExecState* exec) const
     return StringView(m_value);
 }
 
+ALWAYS_INLINE StringViewWithUnderlyingString JSRopeString::viewWithUnderlyingString(ExecState& state) const
+{
+    if (isSubstring()) {
+        auto& base = substringBase()->m_value;
+        if (is8Bit())
+            return { { base.characters8() + substringOffset(), m_length }, base };
+        return { { base.characters16() + substringOffset(), m_length }, base };
+    }
+    resolveRope(&state);
+    return { m_value, m_value };
+}
+
 ALWAYS_INLINE StringView JSString::view(ExecState* exec) const
 {
     if (isRope())
@@ -717,6 +710,13 @@ ALWAYS_INLINE StringView JSString::view(ExecState* exec) const
     return StringView(m_value);
 }
 
+ALWAYS_INLINE StringViewWithUnderlyingString JSString::viewWithUnderlyingString(ExecState& state) const
+{
+    if (isRope())
+        return static_cast<const JSRopeString&>(*this).viewWithUnderlyingString(state);
+    return { m_value, m_value };
+}
+
 inline bool JSString::isSubstring() const
 {
     return isRope() && static_cast<const JSRopeString*>(this)->isSubstring();
index f9870c3423b4ad1119825bf51df137a821a03cc2..53db7f3abd14c3f9494956f373fa16b4e3a321b2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012, 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2012-2015 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
 #include "config.h"
 #include "JSStringJoiner.h"
 
-#include "ExceptionHelpers.h"
-#include "JSScope.h"
-#include "JSString.h"
 #include "JSCInlines.h"
-#include <wtf/text/StringImpl.h>
 
 namespace JSC {
 
-// The destination is 16bits, at least one string is 16 bits.
-static inline void appendStringToData(UChar*& data, const String& string)
+template<typename CharacterType>
+static inline void appendStringToData(CharacterType*& data, StringView string)
 {
-    if (string.isNull())
-        return;
+    string.getCharactersWithUpconvert(data);
+    data += string.length();
+}
 
-    unsigned length = string.length();
-    const StringImpl* stringImpl = string.impl();
+template<typename CharacterType>
+static inline String joinStrings(const Vector<StringViewWithUnderlyingString>& strings, StringView separator, unsigned joinedLength)
+{
+    ASSERT(joinedLength);
 
-    if (stringImpl->is8Bit()) {
-        for (unsigned i = 0; i < length; ++i) {
-            *data = stringImpl->characters8()[i];
-            ++data;
+    CharacterType* data;
+    String result = StringImpl::tryCreateUninitialized(joinedLength, data);
+    if (result.isNull())
+        return result;
+
+    appendStringToData(data, strings[0].view);
+
+    unsigned size = strings.size();
+
+    switch (separator.length()) {
+    case 0:
+        for (unsigned i = 1; i < size; ++i)
+            appendStringToData(data, strings[i].view);
+        break;
+    case 1: {
+        CharacterType separatorCharacter = separator[0];
+        for (unsigned i = 1; i < size; ++i) {
+            *data++ = separatorCharacter;
+            appendStringToData(data, strings[i].view);
         }
-    } else {
-        for (unsigned i = 0; i < length; ++i) {
-            *data = stringImpl->characters16()[i];
-            ++data;
+        break;
+    }
+    default:
+        for (unsigned i = 1; i < size; ++i) {
+            appendStringToData(data, separator);
+            appendStringToData(data, strings[i].view);
         }
     }
-}
-
-// If the destination is 8bits, we know every string has to be 8bit.
-static inline void appendStringToData(LChar*& data, const String& string)
-{
-    if (string.isNull())
-        return;
-    ASSERT(string.is8Bit());
+    ASSERT(data == result.characters<CharacterType>() + joinedLength);
 
-    unsigned length = string.length();
-    const StringImpl* stringImpl = string.impl();
-
-    for (unsigned i = 0; i < length; ++i) {
-        *data = stringImpl->characters8()[i];
-        ++data;
-    }
+    return result;
 }
 
-template<typename CharacterType>
-static inline PassRefPtr<StringImpl> joinStrings(const Vector<String>& strings, const String& separator, unsigned outputLength)
+inline unsigned JSStringJoiner::joinedLength(ExecState& state) const
 {
-    ASSERT(outputLength);
-
-    CharacterType* data;
-    RefPtr<StringImpl> outputStringImpl = StringImpl::tryCreateUninitialized(outputLength, data);
-    if (!outputStringImpl)
-        return nullptr;
+    unsigned numberOfStrings = m_strings.size();
+    if (!numberOfStrings)
+        return 0;
 
-    const String firstString = strings.first();
-    appendStringToData(data, firstString);
+    Checked<unsigned, RecordOverflow> separatorLength = m_separator.length();
+    Checked<unsigned, RecordOverflow> totalSeparatorsLength = separatorLength * (numberOfStrings - 1);
+    Checked<unsigned, RecordOverflow> totalLength = totalSeparatorsLength + m_accumulatedStringsLength;
 
-    for (size_t i = 1; i < strings.size(); ++i) {
-        appendStringToData(data, separator);
-        appendStringToData(data, strings[i]);
+    unsigned result;
+    if (totalLength.safeGet(result) == CheckedState::DidOverflow) {
+        throwOutOfMemoryError(&state);
+        return 0;
     }
-
-    ASSERT(data == (outputStringImpl->characters<CharacterType>() + outputStringImpl->length()));
-    return outputStringImpl.release();
+    return result;
 }
 
-JSValue JSStringJoiner::join(ExecState* exec)
+JSValue JSStringJoiner::join(ExecState& state)
 {
-    if (!m_isValid)
-        return throwOutOfMemoryError(exec);
+    ASSERT(m_strings.size() == m_strings.capacity());
 
-    if (!m_strings.size())
-        return jsEmptyString(exec);
+    unsigned length = joinedLength(state);
+    if (state.hadException())
+        return jsUndefined();
 
-    Checked<unsigned, RecordOverflow> separatorLength = m_separator.length();
-    // FIXME: add special cases of joinStrings() for (separatorLength == 0) and (separatorLength == 1).
-    ASSERT(m_strings.size() > 0);
-    Checked<unsigned, RecordOverflow> totalSeparactorsLength = separatorLength * (m_strings.size() - 1);
-    Checked<unsigned, RecordOverflow> outputStringSize = totalSeparactorsLength + m_accumulatedStringsLength;
-
-    unsigned finalSize;
-    if (outputStringSize.safeGet(finalSize) == CheckedState::DidOverflow)
-        return throwOutOfMemoryError(exec);
-        
-    if (!outputStringSize)
-        return jsEmptyString(exec);
-
-    RefPtr<StringImpl> outputStringImpl;
-    if (m_is8Bits)
-        outputStringImpl = joinStrings<LChar>(m_strings, m_separator, finalSize);
+    if (!length)
+        return jsEmptyString(&state);
+
+    String result;
+    if (m_isAll8Bit)
+        result = joinStrings<LChar>(m_strings, m_separator, length);
     else
-        outputStringImpl = joinStrings<UChar>(m_strings, m_separator, finalSize);
+        result = joinStrings<UChar>(m_strings, m_separator, length);
 
-    if (!outputStringImpl)
-        return throwOutOfMemoryError(exec);
+    if (result.isNull())
+        return throwOutOfMemoryError(&state);
 
-    return JSString::create(exec->vm(), outputStringImpl.release());
+    return jsString(&state, WTF::move(result));
 }
 
 }
index 73950c6d745a0ac8caf3eca3225277f7673ef254..b914cdb667f4d4de203c9e4a42bc5c465bae6dce 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012, 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2012-2015 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
 #ifndef JSStringJoiner_h
 #define JSStringJoiner_h
 
+#include "ExceptionHelpers.h"
 #include "JSCJSValue.h"
-#include <wtf/Vector.h>
-#include <wtf/text/WTFString.h>
 
 namespace JSC {
 
-class ExecState;
-
-
 class JSStringJoiner {
 public:
-    JSStringJoiner(const String& separator, size_t stringCount);
+    JSStringJoiner(ExecState&, LChar separator, unsigned stringCount);
+    JSStringJoiner(ExecState&, StringView separator, unsigned stringCount);
 
-    void append(const String&);
-    JSValue join(ExecState*);
+    void append(ExecState&, JSValue);
+
+    JSValue join(ExecState&);
 
 private:
-    String m_separator;
-    Vector<String> m_strings;
+    void append(StringViewWithUnderlyingString&&);
+    void append8Bit(const String&);
+    void appendLiteral(const Identifier&);
+    void appendEmptyString();
+    unsigned joinedLength(ExecState&) const;
 
+    LChar m_singleCharacterSeparator;
+    StringView m_separator;
+    Vector<StringViewWithUnderlyingString> m_strings;
     Checked<unsigned, RecordOverflow> m_accumulatedStringsLength;
-    bool m_isValid;
-    bool m_is8Bits;
+    bool m_isAll8Bit { true };
 };
 
-inline JSStringJoiner::JSStringJoiner(const String& separator, size_t stringCount)
+inline JSStringJoiner::JSStringJoiner(ExecState& state, StringView separator, unsigned stringCount)
     : m_separator(separator)
-    , m_isValid(true)
-    , m_is8Bits(m_separator.is8Bit())
+    , m_isAll8Bit(m_separator.is8Bit())
+{
+    if (!m_strings.tryReserveCapacity(stringCount))
+        throwOutOfMemoryError(&state);
+}
+
+inline JSStringJoiner::JSStringJoiner(ExecState& state, LChar separator, unsigned stringCount)
+    : m_singleCharacterSeparator(separator)
+    , m_separator { &m_singleCharacterSeparator, 1 }
+{
+    if (!m_strings.tryReserveCapacity(stringCount))
+        throwOutOfMemoryError(&state);
+}
+
+ALWAYS_INLINE void JSStringJoiner::append(StringViewWithUnderlyingString&& string)
 {
-    ASSERT(!m_separator.isNull());
-    m_isValid = m_strings.tryReserveCapacity(stringCount);
+    m_accumulatedStringsLength += string.view.length();
+    m_isAll8Bit = m_isAll8Bit && string.view.is8Bit();
+    m_strings.uncheckedAppend(WTF::move(string));
 }
 
-inline void JSStringJoiner::append(const String& str)
+ALWAYS_INLINE void JSStringJoiner::append8Bit(const String& string)
 {
-    if (!m_isValid)
+    ASSERT(string.is8Bit());
+    m_accumulatedStringsLength += string.length();
+    m_strings.uncheckedAppend({ string, string });
+}
+
+ALWAYS_INLINE void JSStringJoiner::appendLiteral(const Identifier& literal)
+{
+    m_accumulatedStringsLength += literal.length();
+    ASSERT(literal.string().is8Bit());
+    m_strings.uncheckedAppend({ literal.string(), { } });
+}
+
+ALWAYS_INLINE void JSStringJoiner::appendEmptyString()
+{
+    m_strings.uncheckedAppend({ { }, { } });
+}
+
+ALWAYS_INLINE void JSStringJoiner::append(ExecState& state, JSValue value)
+{
+    // The following code differs from using the result of JSValue::toString in the following ways:
+    // 1) It's inlined more than JSValue::toString is.
+    // 2) It includes conversion to WTF::String in a way that avoids allocating copies of substrings.
+    // 3) It doesn't create a JSString for numbers, true, or false.
+    // 4) It turns undefined and null into the empty string instead of "undefined" and "null".
+    // 5) It uses optimized code paths for all the cases known to be 8-bit and for the empty string.
+
+    if (value.isCell()) {
+        if (value.asCell()->isString()) {
+            append(asString(value)->viewWithUnderlyingString(state));
+            return;
+        }
+        append(value.toString(&state)->viewWithUnderlyingString(state));
         return;
+    }
 
-    m_strings.append(str);
-    if (!str.isNull()) {
-        m_accumulatedStringsLength += str.length();
-        m_is8Bits = m_is8Bits && str.is8Bit();
+    if (value.isInt32()) {
+        append8Bit(state.vm().numericStrings.add(value.asInt32()));
+        return;
+    }
+    if (value.isDouble()) {
+        append8Bit(state.vm().numericStrings.add(value.asDouble()));
+        return;
+    }
+    if (value.isTrue()) {
+        append8Bit(state.vm().propertyNames->trueKeyword.string());
+        return;
+    }
+    if (value.isFalse()) {
+        append8Bit(state.vm().propertyNames->falseKeyword.string());
+        return;
     }
+    ASSERT(value.isUndefinedOrNull());
+    appendEmptyString();
 }
 
 }
index ee7c535f967377727f9cc27e8954faf4cf34e457..7cd9f7c351b2d98730c718742419d4681d157cf9 100644 (file)
@@ -1,3 +1,27 @@
+2015-06-22  Darin Adler  <darin@apple.com>
+
+        Make Array.join work directly on substrings without reifying them
+        https://bugs.webkit.org/show_bug.cgi?id=146191
+
+        Reviewed by Andreas Kling.
+
+        * wtf/Vector.h: Added an overload of uncheckedAppend like the one we added
+        a while back, a non-template function that forwards through to the function
+        template. This lets us call uncheckedAppend on an argument list and have it
+        properly convert it to the Vector's element type.
+
+        * wtf/text/StringView.h:
+        (WTF::StringView::getCharactersWithUpconvert): Changed to not use memcpy;
+        saw some indication the hand-written loop was faster when profiling. Also
+        use m_length directly when we know we are dealing with an 8-bit string,
+        since the masking that the index function does is not needed in that case.
+        (WTF::StringView::UpconvertedCharacters::UpconvertedCharacters): Ditto.
+        (WTF::StringView::toString): Ditto.
+        (WTF::StringView::toFloat): Ditto.
+        (WTF::StringView::toInt): Ditto.
+        (WTF::StringView::toStringWithoutCopying): Ditto.
+        (WTF::StringView::find): Ditto.
+
 2015-06-22  YunQiang Su  <wzssyqa@gmail.com>
 
         [WTF] Platform.h: use _ABI64 instead of _MIPS_SIM_ABI64 to determine MIPS N64
index d61c5ffc36a0cb8ea1e9e7c4ff6559e99ea66d07..dacdb0a9490f9479f7aa87cbf640b33289fb5b42 100644 (file)
@@ -724,9 +724,12 @@ public:
     void clear() { shrinkCapacity(0); }
 
     void append(ValueType&& value) { append<ValueType>(std::forward<ValueType>(value)); }
-    template<typename U> void append(const U*, size_t);
     template<typename U> void append(U&&);
-    template<typename U> void uncheckedAppend(U&& val);
+
+    void uncheckedAppend(ValueType&& value) { uncheckedAppend<ValueType>(std::forward<ValueType>(value)); }
+    template<typename U> void uncheckedAppend(U&&);
+
+    template<typename U> void append(const U*, size_t);
     template<typename U, size_t otherCapacity> void appendVector(const Vector<U, otherCapacity>&);
     template<typename U> bool tryAppend(const U*, size_t);
 
index 3560df017b7df8c45781fae55dc74207101e312b..662da879e21d95b27ec0d6d45fd284d2bf533835 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 Apple Inc. All rights reserved.
+ * Copyright (C) 2014-2015 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -409,19 +409,23 @@ inline bool StringView::contains(UChar character) const
 inline void StringView::getCharactersWithUpconvert(LChar* destination) const
 {
     ASSERT(is8Bit());
-    memcpy(destination, characters8(), length());
+    auto characters8 = this->characters8();
+    for (unsigned i = 0; i < m_length; ++i)
+        destination[i] = characters8[i];
 }
 
 inline void StringView::getCharactersWithUpconvert(UChar* destination) const
 {
     if (is8Bit()) {
-        const LChar* characters8 = this->characters8();
-        unsigned length = this->length();
-        for (unsigned i = 0; i < length; ++i)
+        auto characters8 = this->characters8();
+        for (unsigned i = 0; i < m_length; ++i)
             destination[i] = characters8[i];
         return;
     }
-    memcpy(destination, characters16(), length() * sizeof(UChar));
+    auto characters16 = this->characters16();
+    unsigned length = this->length();
+    for (unsigned i = 0; i < length; ++i)
+        destination[i] = characters16[i];
 }
 
 inline StringView::UpconvertedCharacters::UpconvertedCharacters(const StringView& string)
@@ -431,7 +435,7 @@ inline StringView::UpconvertedCharacters::UpconvertedCharacters(const StringView
         return;
     }
     const LChar* characters8 = string.characters8();
-    unsigned length = string.length();
+    unsigned length = string.m_length;
     m_upconvertedCharacters.reserveInitialCapacity(length);
     for (unsigned i = 0; i < length; ++i)
         m_upconvertedCharacters.uncheckedAppend(characters8[i]);
@@ -441,35 +445,35 @@ inline StringView::UpconvertedCharacters::UpconvertedCharacters(const StringView
 inline String StringView::toString() const
 {
     if (is8Bit())
-        return String(characters8(), length());
+        return String(characters8(), m_length);
     return String(characters16(), length());
 }
 
 inline float StringView::toFloat(bool& isValid) const
 {
     if (is8Bit())
-        return charactersToFloat(characters8(), length(), &isValid);
+        return charactersToFloat(characters8(), m_length, &isValid);
     return charactersToFloat(characters16(), length(), &isValid);
 }
 
 inline int StringView::toInt(bool& isValid) const
 {
     if (is8Bit())
-        return charactersToInt(characters8(), length(), &isValid);
+        return charactersToInt(characters8(), m_length, &isValid);
     return charactersToInt(characters16(), length(), &isValid);
 }
 
 inline String StringView::toStringWithoutCopying() const
 {
     if (is8Bit())
-        return StringImpl::createWithoutCopying(characters8(), length());
+        return StringImpl::createWithoutCopying(characters8(), m_length);
     return StringImpl::createWithoutCopying(characters16(), length());
 }
 
 inline size_t StringView::find(UChar character, unsigned start) const
 {
     if (is8Bit())
-        return WTF::find(characters8(), length(), character, start);
+        return WTF::find(characters8(), m_length, character, start);
     return WTF::find(characters16(), length(), character, start);
 }