Title: [103626] trunk/Source/_javascript_Core
Revision
103626
Author
gga...@apple.com
Date
2011-12-23 07:38:43 -0800 (Fri, 23 Dec 2011)

Log Message

Refactored String.prototype.replace
https://bugs.webkit.org/show_bug.cgi?id=75114
        
Reviewed by Darin Adler.

No performance difference.
        
I think this is a step toward removing -fomit-frame-pointer.

* runtime/JSString.cpp:
* runtime/JSString.h: Removed the test and special case for a single-character
search string because the standard path does this test and special case
for us. (As an aside, if we do come up with a unique single-character
replace optimization in future, it probably belongs in the replace function,
and not in JSString.)

* runtime/StringPrototype.cpp:
(JSC::stringProtoFuncReplace): Split this mega-sized function into:
(JSC::replaceUsingStringSearch): - This reasonably sized function, and
(JSC::replaceUsingRegExpSearch): - This still mega-sized function.

Modified Paths

Diff

Modified: trunk/Source/_javascript_Core/ChangeLog (103625 => 103626)


--- trunk/Source/_javascript_Core/ChangeLog	2011-12-23 15:35:12 UTC (rev 103625)
+++ trunk/Source/_javascript_Core/ChangeLog	2011-12-23 15:38:43 UTC (rev 103626)
@@ -1,3 +1,26 @@
+2011-12-23  Geoffrey Garen  <gga...@apple.com>
+
+        Refactored String.prototype.replace
+        https://bugs.webkit.org/show_bug.cgi?id=75114
+        
+        Reviewed by Darin Adler.
+
+        No performance difference.
+        
+        I think this is a step toward removing -fomit-frame-pointer.
+
+        * runtime/JSString.cpp:
+        * runtime/JSString.h: Removed the test and special case for a single-character
+        search string because the standard path does this test and special case
+        for us. (As an aside, if we do come up with a unique single-character
+        replace optimization in future, it probably belongs in the replace function,
+        and not in JSString.)
+
+        * runtime/StringPrototype.cpp:
+        (JSC::stringProtoFuncReplace): Split this mega-sized function into:
+        (JSC::replaceUsingStringSearch): - This reasonably sized function, and
+        (JSC::replaceUsingRegExpSearch): - This still mega-sized function.
+
 2011-12-23  Pierre Rossi  <pierre.ro...@gmail.com>
 
         [Qt] REGRESSION(r103467): It broke fast/images/animated-gif-restored-from-bfcache.html

Modified: trunk/Source/_javascript_Core/runtime/JSString.cpp (103625 => 103626)


--- trunk/Source/_javascript_Core/runtime/JSString.cpp	2011-12-23 15:35:12 UTC (rev 103625)
+++ trunk/Source/_javascript_Core/runtime/JSString.cpp	2011-12-23 15:38:43 UTC (rev 103626)
@@ -195,14 +195,6 @@
         throwOutOfMemoryError(exec);
 }
 
-JSValue JSString::replaceCharacter(ExecState* exec, UChar character, const UString& replacement)
-{
-    size_t matchPosition = value(exec).find(character);
-    if (matchPosition == notFound)
-        return JSValue(this);
-    return jsString(exec, m_value.substringSharingImpl(0, matchPosition), replacement, value(exec).substringSharingImpl(matchPosition + 1));
-}
-
 JSString* JSString::getIndexSlowCase(ExecState* exec, unsigned i)
 {
     ASSERT(isRope());

Modified: trunk/Source/_javascript_Core/runtime/JSString.h (103625 => 103626)


--- trunk/Source/_javascript_Core/runtime/JSString.h	2011-12-23 15:35:12 UTC (rev 103625)
+++ trunk/Source/_javascript_Core/runtime/JSString.h	2011-12-23 15:38:43 UTC (rev 103626)
@@ -228,8 +228,6 @@
         JSString* getIndex(ExecState*, unsigned);
         JSString* getIndexSlowCase(ExecState*, unsigned);
 
-        JSValue replaceCharacter(ExecState*, UChar, const UString& replacement);
-
         static Structure* createStructure(JSGlobalData& globalData, JSGlobalObject* globalObject, JSValue proto)
         {
             return Structure::create(globalData, globalObject, proto, TypeInfo(StringType, OverridesGetOwnPropertySlot), &s_info);

Modified: trunk/Source/_javascript_Core/runtime/StringPrototype.cpp (103625 => 103626)


--- trunk/Source/_javascript_Core/runtime/StringPrototype.cpp	2011-12-23 15:35:12 UTC (rev 103625)
+++ trunk/Source/_javascript_Core/runtime/StringPrototype.cpp	2011-12-23 15:38:43 UTC (rev 103626)
@@ -398,217 +398,155 @@
     return jsString(exec, impl.release());
 }
 
-EncodedJSValue JSC_HOST_CALL stringProtoFuncReplace(ExecState* exec)
+static NEVER_INLINE EncodedJSValue replaceUsingRegExpSearch(ExecState* exec, JSString* string, JSValue searchValue, JSValue replaceValue)
 {
-    JSValue thisValue = exec->hostThisValue();
-    if (thisValue.isUndefinedOrNull()) // CheckObjectCoercible
-        return throwVMTypeError(exec);
-    JSString* sourceVal = thisValue.isString() ? asString(thisValue) : jsString(exec, thisValue.toString(exec));
-    JSValue pattern = exec->argument(0);
-    JSValue replacement = exec->argument(1);
-    JSGlobalData* globalData = &exec->globalData();
+    UString replacementString;
+    CallData callData;
+    CallType callType = getCallData(replaceValue, callData);
+    if (callType == CallTypeNone)
+        replacementString = replaceValue.toString(exec);
 
-    if (pattern.inherits(&RegExpObject::s_info)) {
-        UString replacementString;
-        CallData callData;
-        CallType callType = getCallData(replacement, callData);
-        if (callType == CallTypeNone)
-            replacementString = replacement.toString(exec);
+    const UString& source = string->value(exec);
+    unsigned sourceLen = source.length();
+    if (exec->hadException())
+        return JSValue::encode(JSValue());
+    RegExp* regExp = asRegExpObject(searchValue)->regExp();
+    bool global = regExp->global();
 
-        const UString& source = sourceVal->value(exec);
-        unsigned sourceLen = source.length();
-        if (exec->hadException())
-            return JSValue::encode(JSValue());
-        RegExp* reg = asRegExpObject(pattern)->regExp();
-        bool global = reg->global();
+    RegExpConstructor* regExpConstructor = exec->lexicalGlobalObject()->regExpConstructor();
 
-        RegExpConstructor* regExpConstructor = exec->lexicalGlobalObject()->regExpConstructor();
+    // Optimization for substring removal (replace with empty).
+    if (global && callType == CallTypeNone && !replacementString.length()) {
+        int lastIndex = 0;
+        unsigned startPosition = 0;
 
-        // Optimization for substring removal (replace with empty).
-        if (global && callType == CallTypeNone && !replacementString.length()) {
-            int lastIndex = 0;
-            unsigned startPosition = 0;
+        Vector<StringRange, 16> sourceRanges;
+        JSGlobalData* globalData = &exec->globalData();
+        while (true) {
+            int matchIndex;
+            int matchLen = 0;
+            int* ovector;
+            regExpConstructor->performMatch(*globalData, regExp, source, startPosition, matchIndex, matchLen, &ovector);
+            if (matchIndex < 0)
+                break;
 
-            Vector<StringRange, 16> sourceRanges;
+            if (lastIndex < matchIndex)
+                sourceRanges.append(StringRange(lastIndex, matchIndex - lastIndex));
 
-            while (true) {
-                int matchIndex;
-                int matchLen = 0;
-                int* ovector;
-                regExpConstructor->performMatch(*globalData, reg, source, startPosition, matchIndex, matchLen, &ovector);
-                if (matchIndex < 0)
+            lastIndex = matchIndex + matchLen;
+            startPosition = lastIndex;
+
+            // special case of empty match
+            if (!matchLen) {
+                startPosition++;
+                if (startPosition > sourceLen)
                     break;
-
-                if (lastIndex < matchIndex)
-                    sourceRanges.append(StringRange(lastIndex, matchIndex - lastIndex));
-
-                lastIndex = matchIndex + matchLen;
-                startPosition = lastIndex;
-
-                // special case of empty match
-                if (!matchLen) {
-                    startPosition++;
-                    if (startPosition > sourceLen)
-                        break;
-                }
             }
+        }
 
-            if (!lastIndex)
-                return JSValue::encode(sourceVal);
+        if (!lastIndex)
+            return JSValue::encode(string);
 
-            if (static_cast<unsigned>(lastIndex) < sourceLen)
-                sourceRanges.append(StringRange(lastIndex, sourceLen - lastIndex));
+        if (static_cast<unsigned>(lastIndex) < sourceLen)
+            sourceRanges.append(StringRange(lastIndex, sourceLen - lastIndex));
 
-            return JSValue::encode(jsSpliceSubstrings(exec, sourceVal, source, sourceRanges.data(), sourceRanges.size()));
-        }
+        return JSValue::encode(jsSpliceSubstrings(exec, string, source, sourceRanges.data(), sourceRanges.size()));
+    }
 
-        int lastIndex = 0;
-        unsigned startPosition = 0;
+    int lastIndex = 0;
+    unsigned startPosition = 0;
 
-        Vector<StringRange, 16> sourceRanges;
-        Vector<UString, 16> replacements;
+    Vector<StringRange, 16> sourceRanges;
+    Vector<UString, 16> replacements;
 
-        // This is either a loop (if global is set) or a one-way (if not).
-        if (global && callType == CallTypeJS) {
-            // reg->numSubpatterns() + 1 for pattern args, + 2 for match start and sourceValue
-            int argCount = reg->numSubpatterns() + 1 + 2;
-            JSFunction* func = asFunction(replacement);
-            CachedCall cachedCall(exec, func, argCount);
-            if (exec->hadException())
-                return JSValue::encode(jsNull());
-            if (source.is8Bit()) {
-                while (true) {
-                    int matchIndex;
-                    int matchLen = 0;
-                    int* ovector;
-                    regExpConstructor->performMatch(*globalData, reg, source, startPosition, matchIndex, matchLen, &ovector);
-                    if (matchIndex < 0)
-                        break;
+    // This is either a loop (if global is set) or a one-way (if not).
+    if (global && callType == CallTypeJS) {
+        // regExp->numSubpatterns() + 1 for pattern args, + 2 for match start and string
+        int argCount = regExp->numSubpatterns() + 1 + 2;
+        JSFunction* func = asFunction(replaceValue);
+        CachedCall cachedCall(exec, func, argCount);
+        if (exec->hadException())
+            return JSValue::encode(jsNull());
+        JSGlobalData* globalData = &exec->globalData();
+        if (source.is8Bit()) {
+            while (true) {
+                int matchIndex;
+                int matchLen = 0;
+                int* ovector;
+                regExpConstructor->performMatch(*globalData, regExp, source, startPosition, matchIndex, matchLen, &ovector);
+                if (matchIndex < 0)
+                    break;
 
-                    sourceRanges.append(StringRange(lastIndex, matchIndex - lastIndex));
+                sourceRanges.append(StringRange(lastIndex, matchIndex - lastIndex));
 
-                    int completeMatchStart = ovector[0];
-                    unsigned i = 0;
-                    for (; i < reg->numSubpatterns() + 1; ++i) {
-                        int matchStart = ovector[i * 2];
-                        int matchLen = ovector[i * 2 + 1] - matchStart;
+                int completeMatchStart = ovector[0];
+                unsigned i = 0;
+                for (; i < regExp->numSubpatterns() + 1; ++i) {
+                    int matchStart = ovector[i * 2];
+                    int matchLen = ovector[i * 2 + 1] - matchStart;
 
-                        if (matchStart < 0)
-                            cachedCall.setArgument(i, jsUndefined());
-                        else
-                            cachedCall.setArgument(i, jsSubstring8(globalData, source, matchStart, matchLen));
-                    }
-
-                    cachedCall.setArgument(i++, jsNumber(completeMatchStart));
-                    cachedCall.setArgument(i++, sourceVal);
-
-                    cachedCall.setThis(jsUndefined());
-                    JSValue result = cachedCall.call();
-                    if (LIKELY(result.isString()))
-                        replacements.append(asString(result)->value(exec));
+                    if (matchStart < 0)
+                        cachedCall.setArgument(i, jsUndefined());
                     else
-                        replacements.append(result.toString(cachedCall.newCallFrame(exec)));
-                    if (exec->hadException())
-                        break;
-
-                    lastIndex = matchIndex + matchLen;
-                    startPosition = lastIndex;
-
-                    // special case of empty match
-                    if (!matchLen) {
-                        startPosition++;
-                        if (startPosition > sourceLen)
-                            break;
-                    }
+                        cachedCall.setArgument(i, jsSubstring8(globalData, source, matchStart, matchLen));
                 }
-            } else {
-                while (true) {
-                    int matchIndex;
-                    int matchLen = 0;
-                    int* ovector;
-                    regExpConstructor->performMatch(*globalData, reg, source, startPosition, matchIndex, matchLen, &ovector);
-                    if (matchIndex < 0)
-                        break;
 
-                    sourceRanges.append(StringRange(lastIndex, matchIndex - lastIndex));
+                cachedCall.setArgument(i++, jsNumber(completeMatchStart));
+                cachedCall.setArgument(i++, string);
 
-                    int completeMatchStart = ovector[0];
-                    unsigned i = 0;
-                    for (; i < reg->numSubpatterns() + 1; ++i) {
-                        int matchStart = ovector[i * 2];
-                        int matchLen = ovector[i * 2 + 1] - matchStart;
+                cachedCall.setThis(jsUndefined());
+                JSValue result = cachedCall.call();
+                if (LIKELY(result.isString()))
+                    replacements.append(asString(result)->value(exec));
+                else
+                    replacements.append(result.toString(cachedCall.newCallFrame(exec)));
+                if (exec->hadException())
+                    break;
 
-                        if (matchStart < 0)
-                            cachedCall.setArgument(i, jsUndefined());
-                        else
-                            cachedCall.setArgument(i, jsSubstring(globalData, source, matchStart, matchLen));
-                    }
+                lastIndex = matchIndex + matchLen;
+                startPosition = lastIndex;
 
-                    cachedCall.setArgument(i++, jsNumber(completeMatchStart));
-                    cachedCall.setArgument(i++, sourceVal);
-
-                    cachedCall.setThis(jsUndefined());
-                    JSValue result = cachedCall.call();
-                    if (LIKELY(result.isString()))
-                        replacements.append(asString(result)->value(exec));
-                    else
-                        replacements.append(result.toString(cachedCall.newCallFrame(exec)));
-                    if (exec->hadException())
+                // special case of empty match
+                if (!matchLen) {
+                    startPosition++;
+                    if (startPosition > sourceLen)
                         break;
-
-                    lastIndex = matchIndex + matchLen;
-                    startPosition = lastIndex;
-
-                    // special case of empty match
-                    if (!matchLen) {
-                        startPosition++;
-                        if (startPosition > sourceLen)
-                            break;
-                    }
                 }
             }
         } else {
-            do {
+            while (true) {
                 int matchIndex;
                 int matchLen = 0;
                 int* ovector;
-                regExpConstructor->performMatch(*globalData, reg, source, startPosition, matchIndex, matchLen, &ovector);
+                regExpConstructor->performMatch(*globalData, regExp, source, startPosition, matchIndex, matchLen, &ovector);
                 if (matchIndex < 0)
                     break;
 
-                if (callType != CallTypeNone) {
-                    sourceRanges.append(StringRange(lastIndex, matchIndex - lastIndex));
+                sourceRanges.append(StringRange(lastIndex, matchIndex - lastIndex));
 
-                    int completeMatchStart = ovector[0];
-                    MarkedArgumentBuffer args;
+                int completeMatchStart = ovector[0];
+                unsigned i = 0;
+                for (; i < regExp->numSubpatterns() + 1; ++i) {
+                    int matchStart = ovector[i * 2];
+                    int matchLen = ovector[i * 2 + 1] - matchStart;
 
-                    for (unsigned i = 0; i < reg->numSubpatterns() + 1; ++i) {
-                        int matchStart = ovector[i * 2];
-                        int matchLen = ovector[i * 2 + 1] - matchStart;
- 
-                        if (matchStart < 0)
-                            args.append(jsUndefined());
-                        else
-                            args.append(jsSubstring(exec, source, matchStart, matchLen));
-                    }
+                    if (matchStart < 0)
+                        cachedCall.setArgument(i, jsUndefined());
+                    else
+                        cachedCall.setArgument(i, jsSubstring(globalData, source, matchStart, matchLen));
+                }
 
-                    args.append(jsNumber(completeMatchStart));
-                    args.append(sourceVal);
+                cachedCall.setArgument(i++, jsNumber(completeMatchStart));
+                cachedCall.setArgument(i++, string);
 
-                    replacements.append(call(exec, replacement, callType, callData, jsUndefined(), args).toString(exec));
-                    if (exec->hadException())
-                        break;
-                } else {
-                    int replLen = replacementString.length();
-                    if (lastIndex < matchIndex || replLen) {
-                        sourceRanges.append(StringRange(lastIndex, matchIndex - lastIndex));
- 
-                        if (replLen)
-                            replacements.append(substituteBackreferences(replacementString, source, ovector, reg));
-                        else
-                            replacements.append(UString());
-                    }
-                }
+                cachedCall.setThis(jsUndefined());
+                JSValue result = cachedCall.call();
+                if (LIKELY(result.isString()))
+                    replacements.append(asString(result)->value(exec));
+                else
+                    replacements.append(result.toString(cachedCall.newCallFrame(exec)));
+                if (exec->hadException())
+                    break;
 
                 lastIndex = matchIndex + matchLen;
                 startPosition = lastIndex;
@@ -619,56 +557,120 @@
                     if (startPosition > sourceLen)
                         break;
                 }
-            } while (global);
+            }
         }
+    } else {
+        JSGlobalData* globalData = &exec->globalData();
+        do {
+            int matchIndex;
+            int matchLen = 0;
+            int* ovector;
+            regExpConstructor->performMatch(*globalData, regExp, source, startPosition, matchIndex, matchLen, &ovector);
+            if (matchIndex < 0)
+                break;
 
-        if (!lastIndex && replacements.isEmpty())
-            return JSValue::encode(sourceVal);
+            if (callType != CallTypeNone) {
+                sourceRanges.append(StringRange(lastIndex, matchIndex - lastIndex));
 
-        if (static_cast<unsigned>(lastIndex) < sourceLen)
-            sourceRanges.append(StringRange(lastIndex, sourceLen - lastIndex));
+                int completeMatchStart = ovector[0];
+                MarkedArgumentBuffer args;
 
-        return JSValue::encode(jsSpliceSubstringsWithSeparators(exec, sourceVal, source, sourceRanges.data(), sourceRanges.size(), replacements.data(), replacements.size()));
+                for (unsigned i = 0; i < regExp->numSubpatterns() + 1; ++i) {
+                    int matchStart = ovector[i * 2];
+                    int matchLen = ovector[i * 2 + 1] - matchStart;
+
+                    if (matchStart < 0)
+                        args.append(jsUndefined());
+                    else
+                        args.append(jsSubstring(exec, source, matchStart, matchLen));
+                }
+
+                args.append(jsNumber(completeMatchStart));
+                args.append(string);
+
+                replacements.append(call(exec, replaceValue, callType, callData, jsUndefined(), args).toString(exec));
+                if (exec->hadException())
+                    break;
+            } else {
+                int replLen = replacementString.length();
+                if (lastIndex < matchIndex || replLen) {
+                    sourceRanges.append(StringRange(lastIndex, matchIndex - lastIndex));
+
+                    if (replLen)
+                        replacements.append(substituteBackreferences(replacementString, source, ovector, regExp));
+                    else
+                        replacements.append(UString());
+                }
+            }
+
+            lastIndex = matchIndex + matchLen;
+            startPosition = lastIndex;
+
+            // special case of empty match
+            if (!matchLen) {
+                startPosition++;
+                if (startPosition > sourceLen)
+                    break;
+            }
+        } while (global);
     }
 
-    // Not a regular _expression_, so treat the pattern as a string.
+    if (!lastIndex && replacements.isEmpty())
+        return JSValue::encode(string);
 
-    // 'patternString' (or 'searchValue', as it is referred to in the spec) is converted before the replacement.
-    UString patternString = pattern.toString(exec);
+    if (static_cast<unsigned>(lastIndex) < sourceLen)
+        sourceRanges.append(StringRange(lastIndex, sourceLen - lastIndex));
+
+    return JSValue::encode(jsSpliceSubstringsWithSeparators(exec, string, source, sourceRanges.data(), sourceRanges.size(), replacements.data(), replacements.size()));
+}
+
+static NEVER_INLINE EncodedJSValue replaceUsingStringSearch(ExecState* exec, JSString* jsString, JSValue searchValue, JSValue replaceValue)
+{
+    const UString& string = jsString->value(exec);
+    UString searchString = searchValue.toString(exec);
     if (exec->hadException())
         return JSValue::encode(jsUndefined());
 
-    UString replacementString;
+    size_t matchStart = string.find(searchString);
+    if (matchStart == notFound)
+        return JSValue::encode(jsString);
+
     CallData callData;
-    CallType callType = getCallData(replacement, callData);
-    if (callType == CallTypeNone)
-        replacementString = replacement.toString(exec);
+    CallType callType = getCallData(replaceValue, callData);
+    if (callType != CallTypeNone) {
+        MarkedArgumentBuffer args;
+        args.append(jsSubstring(exec, string, matchStart, searchString.length()));
+        args.append(jsNumber(matchStart));
+        args.append(jsString);
+        replaceValue = call(exec, replaceValue, callType, callData, jsUndefined(), args);
+        if (exec->hadException())
+            return JSValue::encode(jsUndefined());
+    }
+
+    UString replaceString = replaceValue.toString(exec);
     if (exec->hadException())
         return JSValue::encode(jsUndefined());
 
-    // Special case for single character patterns without back reference replacement
-    if (patternString.length() == 1 && callType == CallTypeNone && replacementString.find('$', 0) == notFound)
-        return JSValue::encode(sourceVal->replaceCharacter(exec, patternString[0], replacementString));
+    size_t matchEnd = matchStart + searchString.length();
+    int ovector[2] = { matchStart,  matchEnd};
+    return JSValue::encode(JSC::jsString(exec, string.substringSharingImpl(0, matchStart), substituteBackreferences(replaceString, string, ovector, 0), string.substringSharingImpl(matchEnd)));
+}
 
-    const UString& source = sourceVal->value(exec);
-    size_t matchPos = source.find(patternString);
+EncodedJSValue JSC_HOST_CALL stringProtoFuncReplace(ExecState* exec)
+{
+    JSValue thisValue = exec->hostThisValue();
+    if (!thisValue.isString()) {
+        if (thisValue.isUndefinedOrNull()) // CheckObjectCoercible
+            return throwVMTypeError(exec);
+        thisValue = jsString(exec, thisValue.toString(exec));
+    }
+    JSString* string = asString(thisValue);
+    JSValue searchValue = exec->argument(0);
+    JSValue replaceValue = exec->argument(1);
 
-    if (matchPos == notFound)
-        return JSValue::encode(sourceVal);
-
-    int matchLen = patternString.length();
-    if (callType != CallTypeNone) {
-        MarkedArgumentBuffer args;
-        args.append(jsSubstring(exec, source, matchPos, matchLen));
-        args.append(jsNumber(matchPos));
-        args.append(sourceVal);
-
-        replacementString = call(exec, replacement, callType, callData, jsUndefined(), args).toString(exec);
-    }
-    
-    size_t matchEnd = matchPos + matchLen;
-    int ovector[2] = { matchPos, matchEnd };
-    return JSValue::encode(jsString(exec, source.substringSharingImpl(0, matchPos), substituteBackreferences(replacementString, source, ovector, 0), source.substringSharingImpl(matchEnd)));
+    if (searchValue.inherits(&RegExpObject::s_info))
+        return replaceUsingRegExpSearch(exec, string, searchValue, replaceValue);
+    return replaceUsingStringSearch(exec, string, searchValue, replaceValue);
 }
 
 EncodedJSValue JSC_HOST_CALL stringProtoFuncToString(ExecState* exec)
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
http://lists.webkit.org/mailman/listinfo.cgi/webkit-changes

Reply via email to