Bug 808245, Part 6/6 - Add MatchOnly mode and lazify RegExpStatics. r=dvander
authorSean Stangl <sstangl@mozilla.com>
Wed, 12 Dec 2012 18:11:28 -0800
changeset 125221 b7e2ba73b2ff7851a4a7f8502aa02c3fbb3405bc
parent 125220 7711a36c27717f84038edbfc8a2f3e2788dc46d3
child 125222 bed3d6fd43b0668d8208a18772031b907d8ee063
push id2151
push userlsblakk@mozilla.com
push dateTue, 19 Feb 2013 18:06:57 +0000
treeherdermozilla-beta@4952e88741ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdvander
bugs808245
milestone20.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 808245, Part 6/6 - Add MatchOnly mode and lazify RegExpStatics. r=dvander
js/src/builtin/RegExp.cpp
js/src/gc/Marking.cpp
js/src/gc/Marking.h
js/src/gc/RootMarking.cpp
js/src/vm/RegExpObject.cpp
js/src/vm/RegExpObject.h
js/src/vm/RegExpStatics-inl.h
js/src/vm/RegExpStatics.cpp
js/src/vm/RegExpStatics.h
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -114,24 +114,30 @@ js::CreateRegExpMatchResult(JSContext *c
 }
 
 RegExpRunStatus
 ExecuteRegExpImpl(JSContext *cx, RegExpStatics *res, RegExpShared &re, RegExpObject &regexp,
                   JSLinearString *input, StableCharPtr chars, size_t length,
                   size_t *lastIndex, MatchConduit &matches)
 {
     RegExpRunStatus status;
-    
-    /* Ahem, not handled in this patch. But it was a pain to rip out. */
-    JS_ASSERT(!matches.isPair);
 
-    /* Vector of MatchPairs provided: execute full regexp. */
-    status = re.execute(cx, chars, length, lastIndex, *matches.u.pairs);
-    if (status == RegExpRunStatus_Success && res)
-        res->updateFromMatchPairs(cx, input, *matches.u.pairs);
+    /* Switch between MatchOnly and IncludeSubpatterns modes. */
+    if (matches.isPair) {
+        size_t lastIndex_orig = *lastIndex;
+        /* Only one MatchPair slot provided: execute short-circuiting regexp. */
+        status = re.executeMatchOnly(cx, chars, length, lastIndex, *matches.u.pair);
+        if (status == RegExpRunStatus_Success && res)
+            res->updateLazily(cx, input, &regexp, lastIndex_orig);
+    } else {
+        /* Vector of MatchPairs provided: execute full regexp. */
+        status = re.execute(cx, chars, length, lastIndex, *matches.u.pairs);
+        if (status == RegExpRunStatus_Success && res)
+            res->updateFromMatchPairs(cx, input, *matches.u.pairs);
+    }
 
     return status;
 }
 
 /* Legacy ExecuteRegExp behavior is baked into the JSAPI. */
 bool
 js::ExecuteRegExpLegacy(JSContext *cx, RegExpStatics *res, RegExpObject &reobj,
                         Handle<JSStableString*> input, StableCharPtr chars, size_t length,
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -307,16 +307,17 @@ Is##base##AboutToBeFinalized(Encapsulate
 DeclMarkerImpl(BaseShape, BaseShape)
 DeclMarkerImpl(BaseShape, UnownedBaseShape)
 DeclMarkerImpl(IonCode, ion::IonCode)
 DeclMarkerImpl(Object, ArgumentsObject)
 DeclMarkerImpl(Object, DebugScopeObject)
 DeclMarkerImpl(Object, GlobalObject)
 DeclMarkerImpl(Object, JSObject)
 DeclMarkerImpl(Object, JSFunction)
+DeclMarkerImpl(Object, RegExpObject)
 DeclMarkerImpl(Object, ScopeObject)
 DeclMarkerImpl(Script, JSScript)
 DeclMarkerImpl(Shape, Shape)
 DeclMarkerImpl(String, JSAtom)
 DeclMarkerImpl(String, JSString)
 DeclMarkerImpl(String, JSFlatString)
 DeclMarkerImpl(String, JSLinearString)
 DeclMarkerImpl(String, PropertyName)
--- a/js/src/gc/Marking.h
+++ b/js/src/gc/Marking.h
@@ -92,16 +92,17 @@ bool Is##base##AboutToBeFinalized(Encaps
 DeclMarker(BaseShape, BaseShape)
 DeclMarker(BaseShape, UnownedBaseShape)
 DeclMarker(IonCode, ion::IonCode)
 DeclMarker(Object, ArgumentsObject)
 DeclMarker(Object, DebugScopeObject)
 DeclMarker(Object, GlobalObject)
 DeclMarker(Object, JSObject)
 DeclMarker(Object, JSFunction)
+DeclMarker(Object, RegExpObject)
 DeclMarker(Object, ScopeObject)
 DeclMarker(Script, JSScript)
 DeclMarker(Shape, Shape)
 DeclMarker(String, JSAtom)
 DeclMarker(String, JSString)
 DeclMarker(String, JSFlatString)
 DeclMarker(String, JSLinearString)
 DeclMarker(String, PropertyName)
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -669,16 +669,19 @@ Shape::Range::AutoRooter::trace(JSTracer
 {
     if (r->cursor)
         MarkShapeRoot(trc, const_cast<Shape**>(&r->cursor), "Shape::Range::AutoRooter");
 }
 
 void
 RegExpStatics::AutoRooter::trace(JSTracer *trc)
 {
+    if (statics->regexp)
+        MarkObjectRoot(trc, reinterpret_cast<JSObject**>(&statics->regexp),
+                       "RegExpStatics::AutoRooter regexp");
     if (statics->matchesInput)
         MarkStringRoot(trc, reinterpret_cast<JSString**>(&statics->matchesInput),
                        "RegExpStatics::AutoRooter matchesInput");
     if (statics->pendingInput)
         MarkStringRoot(trc, reinterpret_cast<JSString**>(&statics->pendingInput),
                        "RegExpStatics::AutoRooter pendingInput");
 }
 
--- a/js/src/vm/RegExpObject.cpp
+++ b/js/src/vm/RegExpObject.cpp
@@ -443,20 +443,20 @@ RegExpShared::checkSyntax(JSContext *cx,
     if (error == JSC::Yarr::NoError)
         return true;
 
     reportYarrError(cx, tokenStream, error);
     return false;
 }
 
 bool
-RegExpShared::compile(JSContext *cx)
+RegExpShared::compile(JSContext *cx, bool matchOnly)
 {
     if (!sticky())
-        return compile(cx, *source);
+        return compile(cx, *source, matchOnly);
 
     /*
      * The sticky case we implement hackily by prepending a caret onto the front
      * and relying on |::execute| to pseudo-slice the string when it sees a sticky regexp.
      */
     static const jschar prefix[] = {'^', '(', '?', ':'};
     static const jschar postfix[] = {')'};
 
@@ -467,21 +467,21 @@ RegExpShared::compile(JSContext *cx)
     sb.infallibleAppend(prefix, ArrayLength(prefix));
     sb.infallibleAppend(source->chars(), source->length());
     sb.infallibleAppend(postfix, ArrayLength(postfix));
 
     JSAtom *fakeySource = sb.finishAtom();
     if (!fakeySource)
         return false;
 
-    return compile(cx, *fakeySource);
+    return compile(cx, *fakeySource, matchOnly);
 }
 
 bool
-RegExpShared::compile(JSContext *cx, JSLinearString &pattern)
+RegExpShared::compile(JSContext *cx, JSLinearString &pattern, bool matchOnly)
 {
     /* Parse the pattern. */
     ErrorCode yarrError;
     YarrPattern yarrPattern(pattern, ignoreCase(), multiline(), &yarrError);
     if (yarrError) {
         reportYarrError(cx, NULL, yarrError);
         return false;
     }
@@ -489,17 +489,18 @@ RegExpShared::compile(JSContext *cx, JSL
 
 #if ENABLE_YARR_JIT
     if (isJITRuntimeEnabled(cx) && !yarrPattern.m_containsBackreferences) {
         JSC::ExecutableAllocator *execAlloc = cx->runtime->getExecAlloc(cx);
         if (!execAlloc)
             return false;
 
         JSGlobalData globalData(execAlloc);
-        YarrJITCompileMode compileMode = JSC::Yarr::IncludeSubpatterns;
+        YarrJITCompileMode compileMode = matchOnly ? JSC::Yarr::MatchOnly
+                                                   : JSC::Yarr::IncludeSubpatterns;
 
         jitCompile(yarrPattern, JSC::Yarr::Char16, &globalData, codeBlock, compileMode);
 
         /* Unset iff the Yarr JIT compilation was successful. */
         if (!codeBlock.isFallBack())
             return true;
     }
     codeBlock.setFallBack(true);
@@ -515,17 +516,25 @@ RegExpShared::compile(JSContext *cx, JSL
     return true;
 }
 
 bool
 RegExpShared::compileIfNecessary(JSContext *cx)
 {
     if (hasCode() || hasBytecode())
         return true;
-    return compile(cx);
+    return compile(cx, false);
+}
+
+bool
+RegExpShared::compileMatchOnlyIfNecessary(JSContext *cx)
+{
+    if (hasMatchOnlyCode() || hasBytecode())
+        return true;
+    return compile(cx, true);
 }
 
 RegExpRunStatus
 RegExpShared::execute(JSContext *cx, StableCharPtr chars, size_t length,
                       size_t *lastIndex, MatchPairs &matches)
 {
     /* Compile the code at point-of-use. */
     if (!compileIfNecessary(cx))
@@ -566,16 +575,72 @@ RegExpShared::execute(JSContext *cx, Sta
         return RegExpRunStatus_Success_NotFound;
 
     matches.displace(displacement);
     matches.checkAgainst(origLength);
     *lastIndex = matches[0].limit;
     return RegExpRunStatus_Success;
 }
 
+RegExpRunStatus
+RegExpShared::executeMatchOnly(JSContext *cx, StableCharPtr chars, size_t length,
+                               size_t *lastIndex, MatchPair &match)
+{
+    /* Compile the code at point-of-use. */
+    if (!compileMatchOnlyIfNecessary(cx))
+        return RegExpRunStatus_Error;
+
+    const size_t origLength = length;
+    size_t start = *lastIndex;
+    size_t displacement = 0;
+
+    if (sticky()) {
+        displacement = start;
+        chars += displacement;
+        length -= displacement;
+        start = 0;
+    }
+
+#if ENABLE_YARR_JIT
+    if (!codeBlock.isFallBack()) {
+        MatchResult result = codeBlock.execute(chars.get(), start, length);
+        if (!result)
+            return RegExpRunStatus_Success_NotFound;
+
+        match = MatchPair(result.start, result.end);
+        match.displace(displacement);
+        *lastIndex = match.limit;
+        return RegExpRunStatus_Success;
+    }
+#endif
+
+    /*
+     * The JIT could not be used, so fall back to the Yarr interpreter.
+     * Unfortunately, the interpreter does not have a MatchOnly mode, so a
+     * temporary output vector must be provided.
+     */
+    JS_ASSERT(hasBytecode());
+    ScopedMatchPairs matches(&cx->tempLifoAlloc());
+    if (!matches.initArray(pairCount()))
+        return RegExpRunStatus_Error;
+
+    unsigned result =
+        JSC::Yarr::interpret(bytecode, chars.get(), length, start, matches.rawBuf());
+
+    if (result == JSC::Yarr::offsetNoMatch)
+        return RegExpRunStatus_Success_NotFound;
+
+    matches.displace(displacement);
+    matches.checkAgainst(origLength);
+
+    *lastIndex = matches[0].limit;
+    match = MatchPair(result, matches[0].limit);
+    return RegExpRunStatus_Success;
+}
+
 /* RegExpCompartment */
 
 RegExpCompartment::RegExpCompartment(JSRuntime *rt)
   : map_(rt), inUse_(rt)
 {}
 
 RegExpCompartment::~RegExpCompartment()
 {
--- a/js/src/vm/RegExpObject.h
+++ b/js/src/vm/RegExpObject.h
@@ -134,20 +134,21 @@ class RegExpShared
 #endif
     BytecodePattern *bytecode;
 
     /* Lifetime-preserving variables: see class-level comment above. */
     size_t             activeUseCount;
     uint64_t           gcNumberWhenUsed;
 
     /* Internal functions. */
-    bool compile(JSContext *cx);
-    bool compile(JSContext *cx, JSLinearString &pattern);
+    bool compile(JSContext *cx, bool matchOnly);
+    bool compile(JSContext *cx, JSLinearString &pattern, bool matchOnly);
 
     bool compileIfNecessary(JSContext *cx);
+    bool compileMatchOnlyIfNecessary(JSContext *cx);
 
   public:
     RegExpShared(JSRuntime *rt, JSAtom *source, RegExpFlag flags);
     ~RegExpShared();
 
     /* Static functions to expose some Yarr logic. */
     static inline bool isJITRuntimeEnabled(JSContext *cx);
     static void reportYarrError(JSContext *cx, TokenStream *ts, ErrorCode error);
@@ -155,16 +156,20 @@ class RegExpShared
 
     /* Called when a RegExpShared is installed into a RegExpObject. */
     inline void prepareForUse(JSContext *cx);
 
     /* Primary interface: run this regular expression on the given string. */
     RegExpRunStatus execute(JSContext *cx, StableCharPtr chars, size_t length,
                             size_t *lastIndex, MatchPairs &matches);
 
+    /* Run the regular expression without collecting matches, for test(). */
+    RegExpRunStatus executeMatchOnly(JSContext *cx, StableCharPtr chars, size_t length,
+                                     size_t *lastIndex, MatchPair &match);
+
     /* Accessors */
 
     size_t getParenCount() const        { JS_ASSERT(isCompiled()); return parenCount; }
     void incRef()                       { activeUseCount++; }
     void decRef()                       { JS_ASSERT(activeUseCount > 0); activeUseCount--; }
 
     /* Accounts for the "0" (whole match) pair. */
     size_t pairCount() const            { return getParenCount() + 1; }
@@ -172,21 +177,23 @@ class RegExpShared
     RegExpFlag getFlags() const         { return flags; }
     bool ignoreCase() const             { return flags & IgnoreCaseFlag; }
     bool global() const                 { return flags & GlobalFlag; }
     bool multiline() const              { return flags & MultilineFlag; }
     bool sticky() const                 { return flags & StickyFlag; }
 
 #ifdef ENABLE_YARR_JIT
     bool hasCode() const                { return codeBlock.has16BitCode(); }
+    bool hasMatchOnlyCode() const       { return codeBlock.has16BitCodeMatchOnly(); }
 #else
     bool hasCode() const                { return false; }
+    bool hasMatchOnlyCode() const       { return false; }
 #endif
     bool hasBytecode() const            { return bytecode != NULL; }
-    bool isCompiled() const             { return hasBytecode() || hasCode(); }
+    bool isCompiled() const             { return hasBytecode() || hasCode() || hasMatchOnlyCode(); }
 };
 
 /*
  * Extend the lifetime of a given RegExpShared to at least the lifetime of
  * the guard object. See Regular Expression comment at the top.
  */
 class RegExpGuard
 {
@@ -247,17 +254,16 @@ class RegExpCompartment
 
   public:
     RegExpCompartment(JSRuntime *rt);
     ~RegExpCompartment();
 
     bool init(JSContext *cx);
     void sweep(JSRuntime *rt);
 
-    /* Return a regexp corresponding to the given (source, flags) pair. */
     bool get(JSContext *cx, JSAtom *source, RegExpFlag flags, RegExpGuard *g);
 
     /* Like 'get', but compile 'maybeOpt' (if non-null). */
     bool get(JSContext *cx, JSAtom *source, JSString *maybeOpt, RegExpGuard *g);
 
     size_t sizeOfExcludingThis(JSMallocSizeOfFun mallocSizeOf);
 };
 
--- a/js/src/vm/RegExpStatics-inl.h
+++ b/js/src/vm/RegExpStatics-inl.h
@@ -24,25 +24,28 @@ js::GlobalObject::getRegExpStatics() con
 inline size_t
 SizeOfRegExpStaticsData(const JSObject *obj, JSMallocSizeOfFun mallocSizeOf)
 {
     return mallocSizeOf(obj->getPrivate());
 }
 
 inline
 RegExpStatics::RegExpStatics()
-  : bufferLink(NULL),
+  : pendingLazyEvaluation(false),
+    bufferLink(NULL),
     copied(false)
 {
     clear();
 }
 
 inline bool
 RegExpStatics::createDependent(JSContext *cx, size_t start, size_t end, Value *out)
 {
+    /* Private function: caller must perform lazy evaluation. */
+    JS_ASSERT(!pendingLazyEvaluation);
 
     JS_ASSERT(start <= end);
     JS_ASSERT(end <= matchesInput->length());
     JSString *str = js_NewDependentString(cx, matchesInput, start, end - start);
     if (!str)
         return false;
     *out = StringValue(str);
     return true;
@@ -54,38 +57,46 @@ RegExpStatics::createPendingInput(JSCont
     /* Lazy evaluation need not be resolved to return the input. */
     out->setString(pendingInput ? pendingInput.get() : cx->runtime->emptyString);
     return true;
 }
 
 inline bool
 RegExpStatics::makeMatch(JSContext *cx, size_t checkValidIndex, size_t pairNum, Value *out)
 {
+    /* Private function: caller must perform lazy evaluation. */
+    JS_ASSERT(!pendingLazyEvaluation);
+
     bool checkWhich  = checkValidIndex % 2;
     size_t checkPair = checkValidIndex / 2;
 
     if (matches.empty() || checkPair >= matches.pairCount() ||
         (checkWhich ? matches[checkPair].limit : matches[checkPair].start) < 0)
     {
         out->setString(cx->runtime->emptyString);
         return true;
     }
     const MatchPair &pair = matches[pairNum];
     return createDependent(cx, pair.start, pair.limit, out);
 }
 
 inline bool
 RegExpStatics::createLastMatch(JSContext *cx, Value *out)
 {
+    if (!executeLazy(cx))
+        return false;
     return makeMatch(cx, 0, 0, out);
 }
 
 inline bool
 RegExpStatics::createLastParen(JSContext *cx, Value *out)
 {
+    if (!executeLazy(cx))
+        return false;
+
     if (matches.empty() || matches.pairCount() == 1) {
         out->setString(cx->runtime->emptyString);
         return true;
     }
     const MatchPair &pair = matches[matches.pairCount() - 1];
     if (pair.start == -1) {
         out->setString(cx->runtime->emptyString);
         return true;
@@ -94,118 +105,146 @@ RegExpStatics::createLastParen(JSContext
     JS_ASSERT(pair.limit >= pair.start);
     return createDependent(cx, pair.start, pair.limit, out);
 }
 
 inline bool
 RegExpStatics::createParen(JSContext *cx, size_t pairNum, Value *out)
 {
     JS_ASSERT(pairNum >= 1);
+    if (!executeLazy(cx))
+        return false;
+
     if (matches.empty() || pairNum >= matches.pairCount()) {
         out->setString(cx->runtime->emptyString);
         return true;
     }
     return makeMatch(cx, pairNum * 2, pairNum, out);
 }
 
 inline bool
 RegExpStatics::createLeftContext(JSContext *cx, Value *out)
 {
+    if (!executeLazy(cx))
+        return false;
+
     if (matches.empty()) {
         out->setString(cx->runtime->emptyString);
         return true;
     }
     if (matches[0].start < 0) {
         *out = UndefinedValue();
         return true;
     }
     return createDependent(cx, 0, matches[0].start, out);
 }
 
 inline bool
 RegExpStatics::createRightContext(JSContext *cx, Value *out)
 {
+    if (!executeLazy(cx))
+        return false;
+
     if (matches.empty()) {
         out->setString(cx->runtime->emptyString);
         return true;
     }
     if (matches[0].limit < 0) {
         *out = UndefinedValue();
         return true;
     }
     return createDependent(cx, matches[0].limit, matchesInput->length(), out);
 }
 
 inline void
 RegExpStatics::getParen(size_t pairNum, JSSubString *out) const
 {
+    JS_ASSERT(!pendingLazyEvaluation);
+
     JS_ASSERT(pairNum >= 1 && pairNum < matches.pairCount());
     const MatchPair &pair = matches[pairNum];
     if (pair.isUndefined()) {
         *out = js_EmptySubString;
         return;
     }
     out->chars  = matchesInput->chars() + pair.start;
     out->length = pair.length();
 }
 
 inline void
 RegExpStatics::getLastMatch(JSSubString *out) const
 {
+    JS_ASSERT(!pendingLazyEvaluation);
+
     if (matches.empty()) {
         *out = js_EmptySubString;
         return;
     }
     JS_ASSERT(matchesInput);
     out->chars = matchesInput->chars() + matches[0].start;
     JS_ASSERT(matches[0].limit >= matches[0].start);
     out->length = matches[0].length();
 }
 
 inline void
 RegExpStatics::getLastParen(JSSubString *out) const
 {
+    JS_ASSERT(!pendingLazyEvaluation);
+
     /* Note: the first pair is the whole match. */
     if (matches.empty() || matches.pairCount() == 1) {
         *out = js_EmptySubString;
         return;
     }
     getParen(matches.parenCount(), out);
 }
 
 inline void
 RegExpStatics::getLeftContext(JSSubString *out) const
 {
+    JS_ASSERT(!pendingLazyEvaluation);
+
     if (matches.empty()) {
         *out = js_EmptySubString;
         return;
     }
     out->chars = matchesInput->chars();
     out->length = matches[0].start;
 }
 
 inline void
 RegExpStatics::getRightContext(JSSubString *out) const
 {
+    JS_ASSERT(!pendingLazyEvaluation);
+
     if (matches.empty()) {
         *out = js_EmptySubString;
         return;
     }
     out->chars = matchesInput->chars() + matches[0].limit;
     JS_ASSERT(matches[0].limit <= int(matchesInput->length()));
     out->length = matchesInput->length() - matches[0].limit;
 }
 
 inline void
 RegExpStatics::copyTo(RegExpStatics &dst)
 {
-    dst.matches.initArrayFrom(matches);
+    /* Destination buffer has already been reserved by save(). */
+    if (!pendingLazyEvaluation)
+        dst.matches.initArrayFrom(matches);
+
     dst.matchesInput = matchesInput;
+    dst.regexp = regexp;
+    dst.lastIndex = lastIndex;
     dst.pendingInput = pendingInput;
     dst.flags = flags;
+    dst.pendingLazyEvaluation = pendingLazyEvaluation;
+
+    JS_ASSERT_IF(pendingLazyEvaluation, regexp);
+    JS_ASSERT_IF(pendingLazyEvaluation, matchesInput);
 }
 
 inline void
 RegExpStatics::aboutToWrite()
 {
     if (bufferLink && !bufferLink->copied) {
         copyTo(*bufferLink);
         bufferLink->copied = true;
@@ -215,22 +254,42 @@ RegExpStatics::aboutToWrite()
 inline void
 RegExpStatics::restore()
 {
     if (bufferLink->copied)
         bufferLink->copyTo(*this);
     bufferLink = bufferLink->bufferLink;
 }
 
+inline void
+RegExpStatics::updateLazily(JSContext *cx, JSLinearString *input,
+                            RegExpObject *regexp, size_t lastIndex)
+{
+    JS_ASSERT(input && regexp);
+    aboutToWrite();
+
+    BarrieredSetPair<JSString, JSLinearString>(cx->compartment,
+                                               pendingInput, input,
+                                               matchesInput, input);
+    pendingLazyEvaluation = true;
+    this->regexp = regexp;
+    this->lastIndex = lastIndex;
+}
+
 inline bool
 RegExpStatics::updateFromMatchPairs(JSContext *cx, JSLinearString *input, MatchPairs &newPairs)
 {
     JS_ASSERT(input);
     aboutToWrite();
 
+    /* Unset all lazy state. */
+    pendingLazyEvaluation = false;
+    this->regexp = NULL;
+    this->lastIndex = size_t(-1);
+
     BarrieredSetPair<JSString, JSLinearString>(cx->compartment,
                                                pendingInput, input,
                                                matchesInput, input);
 
     if (!matches.initArrayFrom(newPairs)) {
         js_ReportOutOfMemory(cx);
         return false;
     }
@@ -239,16 +298,17 @@ RegExpStatics::updateFromMatchPairs(JSCo
 }
 
 inline void
 RegExpStatics::clear()
 {
     aboutToWrite();
     flags = RegExpFlag(0);
     pendingInput = NULL;
+    pendingLazyEvaluation = false;
     matchesInput = NULL;
     matches.forgetArray();
 }
 
 inline void
 RegExpStatics::setPendingInput(JSString *newInput)
 {
     aboutToWrite();
@@ -297,16 +357,22 @@ RegExpStatics::reset(JSContext *cx, JSSt
     setMultiline(cx, newMultiline);
     checkInvariants();
 }
 
 inline void
 RegExpStatics::checkInvariants()
 {
 #ifdef DEBUG
+    if (pendingLazyEvaluation) {
+        JS_ASSERT(regexp);
+        JS_ASSERT(pendingInput);
+        return;
+    }
+
     if (matches.empty()) {
         JS_ASSERT(!matchesInput);
         return;
     }
 
     /* Pair count is non-zero, so there must be match pairs input. */
     JS_ASSERT(matchesInput);
     size_t mpiLen = matchesInput->length();
--- a/js/src/vm/RegExpStatics.cpp
+++ b/js/src/vm/RegExpStatics.cpp
@@ -62,8 +62,42 @@ RegExpStatics::create(JSContext *cx, Glo
     if (!obj)
         return NULL;
     RegExpStatics *res = cx->new_<RegExpStatics>();
     if (!res)
         return NULL;
     obj->setPrivate(static_cast<void *>(res));
     return obj;
 }
+
+bool
+RegExpStatics::executeLazy(JSContext *cx)
+{
+    if (!pendingLazyEvaluation)
+        return true;
+
+    JS_ASSERT(regexp);
+    JS_ASSERT(matchesInput);
+    JS_ASSERT(lastIndex != size_t(-1));
+
+    /*
+     * It is not necessary to call aboutToWrite(): evaluation of
+     * implicit copies is safe.
+     */
+
+    size_t length = matchesInput->length();
+    StableCharPtr chars(matchesInput->chars(), length);
+
+    /* Execute the full regular expression. */
+    RegExpGuard shared;
+    if (!regexp->getShared(cx, &shared))
+        return false;
+
+    RegExpRunStatus status = shared->execute(cx, chars, length, &this->lastIndex, this->matches);
+    if (status == RegExpRunStatus_Error)
+        return false;
+
+    /* Unset lazy state and remove rooted values that now have no use. */
+    pendingLazyEvaluation = false;
+    regexp = NULL;
+
+    return true;
+}
--- a/js/src/vm/RegExpStatics.h
+++ b/js/src/vm/RegExpStatics.h
@@ -19,25 +19,37 @@
 namespace js {
 
 class RegExpStatics
 {
     /* The latest RegExp output, set after execution. */
     VectorMatchPairs        matches;
     HeapPtr<JSLinearString> matchesInput;
 
+    /* The previous RegExp input, used to resolve lazy state. */
+    HeapPtr<RegExpObject>   regexp;
+    size_t                  lastIndex;
+
     /* The latest RegExp input, set before execution. */
     HeapPtr<JSString>       pendingInput;
     RegExpFlag              flags;
 
+    /*
+     * If true, |matchesInput|, |regexp|, and |lastIndex| may be used
+     * to replay the last executed RegExp, and |matches| is invalid.
+     */
+    bool                    pendingLazyEvaluation;
+
     /* Linkage for preserving RegExpStatics during nested RegExp execution. */
     RegExpStatics           *bufferLink;
     bool                    copied;
 
   private:
+    bool executeLazy(JSContext *cx);
+
     inline void aboutToWrite();
     inline void copyTo(RegExpStatics &dst);
 
     inline void restore();
     bool save(JSContext *cx, RegExpStatics *buffer) {
         JS_ASSERT(!buffer->copied && !buffer->bufferLink);
         buffer->bufferLink = bufferLink;
         bufferLink = buffer;
@@ -67,44 +79,52 @@ class RegExpStatics
 
   public:
     inline RegExpStatics();
 
     static JSObject *create(JSContext *cx, GlobalObject *parent);
 
     /* Mutators. */
 
+    inline void updateLazily(JSContext *cx, JSLinearString *input,
+                             RegExpObject *regexp, size_t lastIndex);
     inline bool updateFromMatchPairs(JSContext *cx, JSLinearString *input, MatchPairs &newPairs);
     inline void setMultiline(JSContext *cx, bool enabled);
 
     inline void clear();
 
     /* Corresponds to JSAPI functionality to set the pending RegExp input. */
     inline void reset(JSContext *cx, JSString *newInput, bool newMultiline);
 
     inline void setPendingInput(JSString *newInput);
 
   public:
     /* Default match accessor. */
     const MatchPairs &getMatches() const {
+        /* Safe: only used by String methods, which do not set lazy mode. */
+        JS_ASSERT(!pendingLazyEvaluation);
         return matches;
     }
 
     JSString *getPendingInput() const { return pendingInput; }
 
     RegExpFlag getFlags() const { return flags; }
     bool multiline() const { return flags & MultilineFlag; }
 
     /* Returns whether results for a non-empty match are present. */
     bool matched() const {
+        /* Safe: only used by String methods, which do not set lazy mode. */
+        JS_ASSERT(!pendingLazyEvaluation);
         JS_ASSERT(matches.pairCount() > 0);
         return matches[0].limit - matches[0].start > 0;
     }
 
     void mark(JSTracer *trc) {
+        if (regexp)
+            gc::MarkObject(trc, &regexp, "res->regexp");
         if (pendingInput)
             MarkString(trc, &pendingInput, "res->pendingInput");
         if (matchesInput)
             MarkString(trc, &matchesInput, "res->matchesInput");
     }
 
     /* Value creators. */