Start using ropes in String.prototype.replace.
authorggaren@apple.com <ggaren@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 11 May 2010 18:42:46 +0000 (18:42 +0000)
committerggaren@apple.com <ggaren@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 11 May 2010 18:42:46 +0000 (18:42 +0000)
Reviewed by Oliver Hunt and Darin Adler.

1%-1.5% speedup on SunSpider.

* runtime/JSString.cpp:
(JSC::JSString::resolveRope): Updated for RopeImpl refactoring.

(JSC::JSString::replaceCharacter): Added a replaceCharacter function, which creates
a rope for the resulting replacement.

* runtime/JSString.h: A few changes here:
(JSC::):
(JSC::RopeBuilder::RopeIterator::RopeIterator):
(JSC::RopeBuilder::RopeIterator::operator++):
(JSC::RopeBuilder::RopeIterator::operator*):
(JSC::RopeBuilder::RopeIterator::operator!=):
(JSC::RopeBuilder::RopeIterator::WorkItem::WorkItem):
(JSC::RopeBuilder::RopeIterator::WorkItem::operator!=):
(JSC::RopeBuilder::RopeIterator::skipRopes): Created a RopeIterator abstraction.
We use this to do a substring find without having to resolve the rope.
(We could use this iterator when resolving ropes, too, but resolving
ropes backwards is usually more efficient.)

(JSC::RopeBuilder::JSString): Added constructors for 2 & 3 UStrings.

(JSC::RopeBuilder::appendValueInConstructAndIncrementLength):
(JSC::RopeBuilder::size): Updated for RopeImpl refactoring.

* runtime/Operations.h: Updated for RopeImpl refactoring.
(JSC::jsString): Added jsString functions for 2 & 3 UStrings.

* runtime/RopeImpl.cpp:
(JSC::RopeImpl::derefFibersNonRecursive):
* runtime/RopeImpl.h:
(JSC::RopeImpl::initializeFiber):
(JSC::RopeImpl::size):
(JSC::RopeImpl::fibers):
(JSC::RopeImpl::deref):
(JSC::RopeImpl::RopeImpl): A little refactoring to make this patch easier:
Moved statics to the top of the class; put multi-statement functions on
multiple lines; renamed "fiberCount" to "size" to match other collections;
changed the "fibers" accessor to return the fibers buffer, instead of an
item in the buffer, to make iteration easier.

* runtime/StringPrototype.cpp:
(JSC::stringProtoFuncReplace): Don't resolve a rope unless we need to. Do
use our new replaceCharacter function if possible. Do use a rope to
represent splicing three strings together.

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

JavaScriptCore/ChangeLog
JavaScriptCore/runtime/JSString.cpp
JavaScriptCore/runtime/JSString.h
JavaScriptCore/runtime/Operations.h
JavaScriptCore/runtime/RopeImpl.cpp
JavaScriptCore/runtime/RopeImpl.h
JavaScriptCore/runtime/StringPrototype.cpp

index 359face..14d1045 100644 (file)
@@ -1,3 +1,56 @@
+2010-05-11  Geoffrey Garen  <ggaren@apple.com>
+
+        Reviewed by Oliver Hunt and Darin Adler.
+
+        Start using ropes in String.prototype.replace.
+        
+        1%-1.5% speedup on SunSpider.
+
+        * runtime/JSString.cpp:
+        (JSC::JSString::resolveRope): Updated for RopeImpl refactoring.
+
+        (JSC::JSString::replaceCharacter): Added a replaceCharacter function, which creates
+        a rope for the resulting replacement.
+
+        * runtime/JSString.h: A few changes here:
+        (JSC::):
+        (JSC::RopeBuilder::RopeIterator::RopeIterator):
+        (JSC::RopeBuilder::RopeIterator::operator++):
+        (JSC::RopeBuilder::RopeIterator::operator*):
+        (JSC::RopeBuilder::RopeIterator::operator!=):
+        (JSC::RopeBuilder::RopeIterator::WorkItem::WorkItem):
+        (JSC::RopeBuilder::RopeIterator::WorkItem::operator!=):
+        (JSC::RopeBuilder::RopeIterator::skipRopes): Created a RopeIterator abstraction.
+        We use this to do a substring find without having to resolve the rope.
+        (We could use this iterator when resolving ropes, too, but resolving
+        ropes backwards is usually more efficient.)
+
+        (JSC::RopeBuilder::JSString): Added constructors for 2 & 3 UStrings.
+
+        (JSC::RopeBuilder::appendValueInConstructAndIncrementLength):
+        (JSC::RopeBuilder::size): Updated for RopeImpl refactoring.
+
+        * runtime/Operations.h: Updated for RopeImpl refactoring.
+        (JSC::jsString): Added jsString functions for 2 & 3 UStrings.
+
+        * runtime/RopeImpl.cpp:
+        (JSC::RopeImpl::derefFibersNonRecursive):
+        * runtime/RopeImpl.h:
+        (JSC::RopeImpl::initializeFiber):
+        (JSC::RopeImpl::size):
+        (JSC::RopeImpl::fibers):
+        (JSC::RopeImpl::deref):
+        (JSC::RopeImpl::RopeImpl): A little refactoring to make this patch easier:
+        Moved statics to the top of the class; put multi-statement functions on
+        multiple lines; renamed "fiberCount" to "size" to match other collections;
+        changed the "fibers" accessor to return the fibers buffer, instead of an
+        item in the buffer, to make iteration easier.
+
+        * runtime/StringPrototype.cpp:
+        (JSC::stringProtoFuncReplace): Don't resolve a rope unless we need to. Do
+        use our new replaceCharacter function if possible. Do use a rope to
+        represent splicing three strings together.
+
 2010-05-10  Laszlo Gombos  <laszlo.1.gombos@nokia.com>
 
         Reviewed by Darin Adler.
index fbc7d72..d33c836 100644 (file)
@@ -75,8 +75,8 @@ void JSString::resolveRope(ExecState* exec) const
             // (we will be working backwards over the rope).
             unsigned fiberCountMinusOne = rope->fiberCount() - 1;
             for (unsigned i = 0; i < fiberCountMinusOne; ++i)
-                workQueue.append(rope->fibers(i));
-            currentFiber = rope->fibers(fiberCountMinusOne);
+                workQueue.append(rope->fibers()[i]);
+            currentFiber = rope->fibers()[fiberCountMinusOne];
         } else {
             UStringImpl* string = static_cast<UStringImpl*>(currentFiber);
             unsigned length = string->length();
@@ -104,6 +104,57 @@ void JSString::resolveRope(ExecState* exec) const
     }
 }
 
+JSValue JSString::replaceCharacter(ExecState* exec, UChar character, const UString& replacement)
+{
+    if (!isRope()) {
+        size_t matchPosition = m_value.find(character);
+        if (matchPosition == notFound)
+            return JSValue(this);
+        return jsString(exec, m_value.substr(0, matchPosition), replacement, m_value.substr(matchPosition + 1));
+    }
+
+    RopeIterator end;
+    
+    // Count total fibers and find matching string.
+    size_t fiberCount = 0;
+    UStringImpl* matchString = 0;
+    size_t matchPosition = notFound;
+    for (RopeIterator it(m_other.m_fibers, m_fiberCount); it != end; ++it) {
+        ++fiberCount;
+        if (matchString)
+            continue;
+
+        UStringImpl* string = *it;
+        matchPosition = string->find(character);
+        if (matchPosition == notFound)
+            continue;
+        matchString = string;
+    }
+
+    if (!matchString)
+        return this;
+
+    RopeBuilder builder(replacement.size() ? fiberCount + 2 : fiberCount + 1);
+    if (UNLIKELY(builder.isOutOfMemory()))
+        return throwOutOfMemoryError(exec);
+
+    for (RopeIterator it(m_other.m_fibers, m_fiberCount); it != end; ++it) {
+        UStringImpl* string = *it;
+        if (string != matchString) {
+            builder.append(UString(string));
+            continue;
+        }
+
+        builder.append(UString(string).substr(0, matchPosition));
+        if (replacement.size())
+            builder.append(replacement);
+        builder.append(UString(string).substr(matchPosition + 1));
+    }
+
+    JSGlobalData* globalData = &exec->globalData();
+    return JSValue(new (globalData) JSString(globalData, builder.release()));
+}
+
 JSString* JSString::getIndexSlowCase(ExecState* exec, unsigned i)
 {
     ASSERT(isRope());
index 85d3c8e..5001b01 100644 (file)
@@ -111,6 +111,79 @@ namespace JSC {
             RefPtr<RopeImpl> m_rope;
         };
 
+        class RopeIterator {
+            public:
+                RopeIterator() { }
+
+                RopeIterator(RopeImpl::Fiber* fibers, size_t fiberCount)
+                {
+                    ASSERT(fiberCount);
+                    m_workQueue.append(WorkItem(fibers, fiberCount));
+                    skipRopes();
+                }
+
+                RopeIterator& operator++()
+                {
+                    WorkItem& item = m_workQueue.last();
+                    ASSERT(!RopeImpl::isRope(item.fibers[item.i]));
+                    if (++item.i == item.fiberCount)
+                        m_workQueue.removeLast();
+                    skipRopes();
+                    return *this;
+                }
+
+                UStringImpl* operator*()
+                {
+                    WorkItem& item = m_workQueue.last();
+                    RopeImpl::Fiber fiber = item.fibers[item.i];
+                    ASSERT(!RopeImpl::isRope(fiber));
+                    return static_cast<UStringImpl*>(fiber);
+                }
+
+                bool operator!=(const RopeIterator& other) const
+                {
+                    return m_workQueue != other.m_workQueue;
+                }
+
+            private:
+                struct WorkItem {
+                    WorkItem(RopeImpl::Fiber* fibers, size_t fiberCount)
+                        : fibers(fibers)
+                        , fiberCount(fiberCount)
+                        , i(0)
+                    {
+                    }
+
+                    bool operator!=(const WorkItem& other) const
+                    {
+                        return fibers != other.fibers || fiberCount != other.fiberCount || i != other.i;
+                    }
+
+                    RopeImpl::Fiber* fibers;
+                    size_t fiberCount;
+                    size_t i;
+                };
+
+                void skipRopes()
+                {
+                    if (m_workQueue.isEmpty())
+                        return;
+
+                    while (1) {
+                        WorkItem& item = m_workQueue.last();
+                        RopeImpl::Fiber fiber = item.fibers[item.i];
+                        if (!RopeImpl::isRope(fiber))
+                            break;
+                        RopeImpl* rope = static_cast<RopeImpl*>(fiber);
+                        if (++item.i == item.fiberCount)
+                            m_workQueue.removeLast();
+                        m_workQueue.append(WorkItem(rope->fibers(), rope->fiberCount()));
+                    }
+                }
+
+                Vector<WorkItem, 16> m_workQueue;
+        };
+
         ALWAYS_INLINE JSString(JSGlobalData* globalData, const UString& value)
             : JSCell(globalData->stringStructure.get())
             , m_length(value.size())
@@ -130,7 +203,7 @@ namespace JSC {
         {
             ASSERT(!m_value.isNull());
         }
-        JSString(JSGlobalData* globalData, PassRefPtr<UString::Rep> value, HasOtherOwnerType)
+        JSString(JSGlobalData* globalData, PassRefPtr<UStringImpl> value, HasOtherOwnerType)
             : JSCell(globalData->stringStructure.get())
             , m_length(value->length())
             , m_value(value)
@@ -200,6 +273,31 @@ namespace JSC {
             ASSERT(index == s_maxInternalRopeLength);
         }
 
+        // This constructor constructs a new string by concatenating u1 & u2.
+        JSString(JSGlobalData* globalData, const UString& u1, const UString& u2)
+            : JSCell(globalData->stringStructure.get())
+            , m_length(u1.size() + u2.size())
+            , m_fiberCount(2)
+        {
+            unsigned index = 0;
+            appendStringInConstruct(index, u1);
+            appendStringInConstruct(index, u2);
+            ASSERT(index <= s_maxInternalRopeLength);
+        }
+
+        // This constructor constructs a new string by concatenating u1, u2 & u3.
+        JSString(JSGlobalData* globalData, const UString& u1, const UString& u2, const UString& u3)
+            : JSCell(globalData->stringStructure.get())
+            , m_length(u1.size() + u2.size() + u3.size())
+            , m_fiberCount(s_maxInternalRopeLength)
+        {
+            unsigned index = 0;
+            appendStringInConstruct(index, u1);
+            appendStringInConstruct(index, u2);
+            appendStringInConstruct(index, u3);
+            ASSERT(index <= s_maxInternalRopeLength);
+        }
+
         JSString(JSGlobalData* globalData, const UString& value, JSStringFinalizerCallback finalizer, void* context)
             : JSCell(globalData->stringStructure.get())
             , m_length(value.size())
@@ -246,6 +344,8 @@ namespace JSC {
         JSString* getIndex(ExecState*, unsigned);
         JSString* getIndexSlowCase(ExecState*, unsigned);
 
+        JSValue replaceCharacter(ExecState*, UChar, const UString& replacement);
+
         static PassRefPtr<Structure> createStructure(JSValue proto) { return Structure::create(proto, TypeInfo(StringType, OverridesGetOwnPropertySlot | NeedsThisConversion), AnonymousSlotCount); }
 
     private:
@@ -282,7 +382,7 @@ namespace JSC {
             if (v.isString()) {
                 ASSERT(asCell(v)->isString());
                 JSString* s = static_cast<JSString*>(asCell(v));
-                ASSERT(s->fiberCount() == 1);
+                ASSERT(s->size() == 1);
                 appendStringInConstruct(index, s);
                 m_length += s->length();
             } else {
@@ -328,7 +428,7 @@ namespace JSC {
 
         bool isRope() const { return m_fiberCount; }
         UString& string() { ASSERT(!isRope()); return m_value; }
-        unsigned fiberCount() { return m_fiberCount ? m_fiberCount : 1; }
+        unsigned size() { return m_fiberCount ? m_fiberCount : 1; }
 
         friend JSValue jsString(ExecState* exec, JSString* s1, JSString* s2);
         friend JSValue jsString(ExecState* exec, const UString& u1, JSString* s2);
@@ -375,7 +475,7 @@ namespace JSC {
         UChar c = s.data()[offset];
         if (c <= 0xFF)
             return globalData->smallStrings.singleCharacterString(globalData, c);
-        return fixupVPtr(globalData, new (globalData) JSString(globalData, UString(UString::Rep::create(s.rep(), offset, 1))));
+        return fixupVPtr(globalData, new (globalData) JSString(globalData, UString(UStringImpl::create(s.rep(), offset, 1))));
     }
 
     inline JSString* jsNontrivialString(JSGlobalData* globalData, const char* s)
@@ -433,7 +533,7 @@ namespace JSC {
             if (c <= 0xFF)
                 return globalData->smallStrings.singleCharacterString(globalData, c);
         }
-        return fixupVPtr(globalData, new (globalData) JSString(globalData, UString(UString::Rep::create(s.rep(), offset, length)), JSString::HasOtherOwner));
+        return fixupVPtr(globalData, new (globalData) JSString(globalData, UString(UStringImpl::create(s.rep(), offset, length)), JSString::HasOtherOwner));
     }
 
     inline JSString* jsOwnedString(JSGlobalData* globalData, const UString& s)
index cc0d603..1228902 100644 (file)
@@ -46,7 +46,7 @@ namespace JSC {
         if ((length1 + length2) < length1)
             return throwOutOfMemoryError(exec);
 
-        unsigned fiberCount = s1->fiberCount() + s2->fiberCount();
+        unsigned fiberCount = s1->size() + s2->size();
         JSGlobalData* globalData = &exec->globalData();
 
         if (fiberCount <= JSString::s_maxInternalRopeLength)
@@ -71,7 +71,7 @@ namespace JSC {
         if ((length1 + length2) < length1)
             return throwOutOfMemoryError(exec);
 
-        unsigned fiberCount = 1 + s2->fiberCount();
+        unsigned fiberCount = 1 + s2->size();
         JSGlobalData* globalData = &exec->globalData();
 
         if (fiberCount <= JSString::s_maxInternalRopeLength)
@@ -96,7 +96,7 @@ namespace JSC {
         if ((length1 + length2) < length1)
             return throwOutOfMemoryError(exec);
 
-        unsigned fiberCount = s1->fiberCount() + 1;
+        unsigned fiberCount = s1->size() + 1;
         JSGlobalData* globalData = &exec->globalData();
 
         if (fiberCount <= JSString::s_maxInternalRopeLength)
@@ -110,6 +110,42 @@ namespace JSC {
         return new (globalData) JSString(globalData, ropeBuilder.release());
     }
 
+    ALWAYS_INLINE JSValue jsString(ExecState* exec, const UString& u1, const UString& u2)
+    {
+        unsigned length1 = u1.size();
+        if (!length1)
+            return jsString(exec, u2);
+        unsigned length2 = u2.size();
+        if (!length2)
+            return jsString(exec, u1);
+        if ((length1 + length2) < length1)
+            return throwOutOfMemoryError(exec);
+
+        JSGlobalData* globalData = &exec->globalData();
+        return new (globalData) JSString(globalData, u1, u2);
+    }
+
+    ALWAYS_INLINE JSValue jsString(ExecState* exec, const UString& u1, const UString& u2, const UString& u3)
+    {
+        unsigned length1 = u1.size();
+        unsigned length2 = u2.size();
+        unsigned length3 = u3.size();
+        if (!length1)
+            return jsString(exec, u2, u3);
+        if (!length2)
+            return jsString(exec, u1, u3);
+        if (!length3)
+            return jsString(exec, u1, u2);
+
+        if ((length1 + length2) < length1)
+            return throwOutOfMemoryError(exec);
+        if ((length1 + length2 + length3) < length3)
+            return throwOutOfMemoryError(exec);
+
+        JSGlobalData* globalData = &exec->globalData();
+        return new (globalData) JSString(globalData, u1, u2, u3);
+    }
+
     ALWAYS_INLINE JSValue jsString(ExecState* exec, Register* strings, unsigned count)
     {
         ASSERT(count >= 3);
@@ -118,7 +154,7 @@ namespace JSC {
         for (unsigned i = 0; i < count; ++i) {
             JSValue v = strings[i].jsValue();
             if (LIKELY(v.isString()))
-                fiberCount += asString(v)->fiberCount();
+                fiberCount += asString(v)->size();
             else
                 ++fiberCount;
         }
@@ -157,13 +193,13 @@ namespace JSC {
     {
         unsigned fiberCount = 0;
         if (LIKELY(thisValue.isString()))
-            fiberCount += asString(thisValue)->fiberCount();
+            fiberCount += asString(thisValue)->size();
         else
             ++fiberCount;
         for (unsigned i = 0; i < args.size(); ++i) {
             JSValue v = args.at(i);
             if (LIKELY(v.isString()))
-                fiberCount += asString(v)->fiberCount();
+                fiberCount += asString(v)->size();
             else
                 ++fiberCount;
         }
index a3760e6..25b9848 100644 (file)
@@ -30,9 +30,9 @@ namespace JSC {
 
 void RopeImpl::derefFibersNonRecursive(Vector<RopeImpl*, 32>& workQueue)
 {
-    unsigned length = fiberCount();
-    for (unsigned i = 0; i < length; ++i) {
-        Fiber& fiber = fibers(i);
+    unsigned fiberCount = this->fiberCount();
+    for (unsigned i = 0; i < fiberCount; ++i) {
+        Fiber& fiber = m_fibers[i];
         if (isRope(fiber)) {
             RopeImpl* nextRope = static_cast<RopeImpl*>(fiber);
             if (nextRope->hasOneRef())
index 6fbc595..ac2b502 100644 (file)
@@ -46,18 +46,6 @@ public:
         return 0;
     }
 
-    void initializeFiber(unsigned &index, Fiber fiber)
-    {
-        m_fibers[index++] = fiber;
-        fiber->ref();
-        m_length += fiber->length();
-    }
-
-    unsigned fiberCount() { return m_fiberCount; }
-    Fiber& fibers(unsigned index) { return m_fibers[index]; }
-
-    ALWAYS_INLINE void deref() { m_refCountAndFlags -= s_refCountIncrement; if (!(m_refCountAndFlags & s_refCountMask)) destructNonRecursive(); }
-
     static bool isRope(Fiber fiber)
     {
         return !fiber->isStringImpl();
@@ -71,15 +59,36 @@ public:
             static_cast<UStringImpl*>(fiber)->deref();
     }
 
+    void initializeFiber(unsigned &index, Fiber fiber)
+    {
+        m_fibers[index++] = fiber;
+        fiber->ref();
+        m_length += fiber->length();
+    }
+
+    unsigned fiberCount() { return m_size; }
+    Fiber* fibers() { return m_fibers; }
+
+    ALWAYS_INLINE void deref()
+    {
+        m_refCountAndFlags -= s_refCountIncrement;
+        if (!(m_refCountAndFlags & s_refCountMask))
+            destructNonRecursive();
+    }
+
 private:
-    RopeImpl(unsigned fiberCount) : StringImplBase(ConstructNonStringImpl), m_fiberCount(fiberCount) {}
+    RopeImpl(unsigned fiberCount)
+        : StringImplBase(ConstructNonStringImpl)
+        , m_size(fiberCount)
+    {
+    }
 
     void destructNonRecursive();
     void derefFibersNonRecursive(Vector<RopeImpl*, 32>& workQueue);
 
     bool hasOneRef() { return (m_refCountAndFlags & s_refCountMask) == s_refCountIncrement; }
 
-    unsigned m_fiberCount;
+    unsigned m_size;
     Fiber m_fibers[1];
 };
 
index 345378e..db4600b 100644 (file)
@@ -288,35 +288,12 @@ JSValue jsSpliceSubstringsWithSeparators(ExecState* exec, JSString* sourceVal, c
     return jsString(exec, impl);
 }
 
-JSValue jsReplaceRange(ExecState* exec, const UString& source, int rangeStart, int rangeLength, const UString& replacement);
-JSValue jsReplaceRange(ExecState* exec, const UString& source, int rangeStart, int rangeLength, const UString& replacement)
-{
-    int replacementLength = replacement.size();
-    int totalLength = source.size() - rangeLength + replacementLength;
-    if (totalLength == 0)
-        return jsString(exec, "");
-
-    UChar* buffer;
-    PassRefPtr<UStringImpl> impl = UStringImpl::tryCreateUninitialized(totalLength, buffer);
-    if (!impl)
-        return throwOutOfMemoryError(exec);
-
-    UStringImpl::copyChars(buffer, source.data(), rangeStart);
-    UStringImpl::copyChars(buffer + rangeStart, replacement.data(), replacementLength);
-    int rangeEnd = rangeStart + rangeLength;
-    UStringImpl::copyChars(buffer + rangeStart + replacementLength, source.data() + rangeEnd, source.size() - rangeEnd);
-
-    return jsString(exec, impl);
-}
-
 JSValue JSC_HOST_CALL stringProtoFuncReplace(ExecState* exec, JSObject*, JSValue thisValue, const ArgList& args)
 {
     JSString* sourceVal = thisValue.toThisJSString(exec);
-    const UString& source = sourceVal->value(exec);
-
     JSValue pattern = args.at(0);
-
     JSValue replacement = args.at(1);
+
     UString replacementString;
     CallData callData;
     CallType callType = replacement.getCallData(callData);
@@ -324,6 +301,7 @@ JSValue JSC_HOST_CALL stringProtoFuncReplace(ExecState* exec, JSObject*, JSValue
         replacementString = replacement.toString(exec);
 
     if (pattern.inherits(&RegExpObject::info)) {
+        const UString& source = sourceVal->value(exec);
         RegExp* reg = asRegExpObject(pattern)->regExp();
         bool global = reg->global();
 
@@ -442,6 +420,10 @@ JSValue JSC_HOST_CALL stringProtoFuncReplace(ExecState* exec, JSObject*, JSValue
     // Not a regular expression, so treat the pattern as a string.
 
     UString patternString = pattern.toString(exec);
+    if (patternString.size() == 1 && callType == CallTypeNone)
+        return sourceVal->replaceCharacter(exec, patternString[0], replacementString);
+    
+    const UString& source = sourceVal->value(exec);
     unsigned matchPos = source.find(patternString);
 
     if (matchPos == UString::NotFound)
@@ -456,9 +438,10 @@ JSValue JSC_HOST_CALL stringProtoFuncReplace(ExecState* exec, JSObject*, JSValue
 
         replacementString = call(exec, replacement, callType, callData, exec->globalThisValue(), args).toString(exec);
     }
-
-    int ovector[2] = { matchPos, matchPos + matchLen };
-    return jsReplaceRange(exec, source, matchPos, matchLen, substituteBackreferences(replacementString, source, ovector, 0));
+    
+    size_t matchEnd = matchPos + matchLen;
+    int ovector[2] = { matchPos, matchEnd };
+    return jsString(exec, source.substr(0, matchPos), substituteBackreferences(replacementString, source, ovector, 0), source.substr(matchEnd));
 }
 
 JSValue JSC_HOST_CALL stringProtoFuncToString(ExecState* exec, JSObject*, JSValue thisValue, const ArgList&)