Bug 808245, Part 5/6 - Use MatchPairs for RegExp output. r=dvander
authorSean Stangl <sstangl@mozilla.com>
Wed, 12 Dec 2012 17:42:02 -0800
changeset 116118 7711a36c2771
parent 116117 d229008d60ac
child 116119 b7e2ba73b2ff
push id24043
push userryanvm@gmail.com
push date2012-12-15 21:18 +0000
treeherdermozilla-central@c8a1314aa449 [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 5/6 - Use MatchPairs for RegExp output. r=dvander
js/src/builtin/RegExp.cpp
js/src/builtin/RegExp.h
js/src/gc/RootMarking.cpp
js/src/ion/CodeGenerator.cpp
js/src/jsapi.cpp
js/src/jsprvtd.h
js/src/jsstr.cpp
js/src/vm/MatchPairs.h
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
@@ -45,19 +45,19 @@ class RegExpMatchBuilder
 
     bool setInput(JSString *str) {
         JS_ASSERT(str);
         RootedValue value(cx, StringValue(str));
         return setProperty(cx->names().input, value);
     }
 };
 
-static bool
-CreateRegExpMatchResult(JSContext *cx, JSString *input_, StableCharPtr chars, size_t length,
-                        MatchPairs *matchPairs, Value *rval)
+bool
+js::CreateRegExpMatchResult(JSContext *cx, JSString *input_, StableCharPtr chars, size_t length,
+                            MatchPairs &matches, Value *rval)
 {
     RootedString input(cx, input_);
 
     /*
      * Create the (slow) result array for a match.
      *
      * Array contents:
      *  0:              matched string
@@ -73,87 +73,101 @@ CreateRegExpMatchResult(JSContext *cx, J
         input = js_NewStringCopyN(cx, chars.get(), length);
         if (!input)
             return false;
     }
 
     RegExpMatchBuilder builder(cx, array);
     RootedValue undefinedValue(cx, UndefinedValue());
 
-    for (size_t i = 0; i < matchPairs->pairCount(); ++i) {
-        MatchPair pair = matchPairs->pair(i);
+    size_t numPairs = matches.length();
+    JS_ASSERT(numPairs > 0);
+
+    for (size_t i = 0; i < numPairs; ++i) {
+        const MatchPair &pair = matches[i];
 
         JSString *captured;
         if (pair.isUndefined()) {
             JS_ASSERT(i != 0); /* Since we had a match, first pair must be present. */
             if (!builder.append(i, undefinedValue))
                 return false;
         } else {
             captured = js_NewDependentString(cx, input, pair.start, pair.length());
             RootedValue value(cx, StringValue(captured));
             if (!captured || !builder.append(i, value))
                 return false;
         }
     }
 
-    if (!builder.setIndex(matchPairs->pair(0).start) || !builder.setInput(input))
+    if (!builder.setIndex(matches[0].start) || !builder.setInput(input))
         return false;
 
     *rval = ObjectValue(*array);
     return true;
 }
 
-template <class T>
 bool
-ExecuteRegExpImpl(JSContext *cx, RegExpStatics *res, T &re, JSLinearString *input,
-                  StableCharPtr chars, size_t length,
-                  size_t *lastIndex, RegExpExecType type, Value *rval)
+js::CreateRegExpMatchResult(JSContext *cx, HandleString string, MatchPairs &matches, Value *rval)
 {
-    LifoAllocScope allocScope(&cx->tempLifoAlloc());
-    MatchPairs *matchPairs = NULL;
-    RegExpRunStatus status = re.execute(cx, chars, length, lastIndex, &matchPairs);
-
-    switch (status) {
-      case RegExpRunStatus_Error:
+    Rooted<JSStableString*> input(cx, string->ensureStable(cx));
+    if (!input)
         return false;
-      case RegExpRunStatus_Success_NotFound:
-        *rval = NullValue();
-        return true;
-      default:
-        JS_ASSERT(status == RegExpRunStatus_Success);
-        JS_ASSERT(matchPairs);
-    }
+    return CreateRegExpMatchResult(cx, input, input->chars(), input->length(), matches, rval);
+}
+
+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);
+
+    return status;
+}
 
-    if (res)
-        res->updateFromMatchPairs(cx, input, matchPairs);
+/* 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,
+                        size_t *lastIndex, JSBool test, jsval *rval)
+{
+    RegExpGuard shared;
+    if (!reobj.getShared(cx, &shared))
+        return false;
 
-    *lastIndex = matchPairs->pair(0).limit;
+    ScopedMatchPairs matches(&cx->tempLifoAlloc());
+    MatchConduit conduit(&matches);
+
+    RegExpRunStatus status =
+        ExecuteRegExpImpl(cx, res, *shared, reobj, input, chars, length, lastIndex, conduit);
 
-    if (type == RegExpTest) {
-        *rval = BooleanValue(true);
+    if (status == RegExpRunStatus_Error)
+        return false;
+
+    if (status == RegExpRunStatus_Success_NotFound) {
+        /* ExecuteRegExp() previously returned an array or null. */
+        rval->setNull();
         return true;
     }
 
-    return CreateRegExpMatchResult(cx, input, chars, length, matchPairs, rval);
-}
+    if (test) {
+        /* Forbid an array, as an optimization. */
+        rval->setBoolean(true);
+        return true;
+    }
 
-bool
-js::ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpShared &shared,
-                  Handle<JSStableString*> input, StableCharPtr chars, size_t length,
-                  size_t *lastIndex, RegExpExecType type, Value *rval)
-{
-    return ExecuteRegExpImpl(cx, res, shared, input, chars, length, lastIndex, type, rval);
-}
-
-bool
-js::ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpObject &reobj,
-                  Handle<JSStableString*> input, StableCharPtr chars, size_t length,
-                  size_t *lastIndex, RegExpExecType type, Value *rval)
-{
-    return ExecuteRegExpImpl(cx, res, reobj, input, chars, length, lastIndex, type, rval);
+    return CreateRegExpMatchResult(cx, input, chars, length, matches, rval);
 }
 
 /* Note: returns the original if no escaping need be performed. */
 static JSAtom *
 EscapeNakedForwardSlashes(JSContext *cx, JSAtom *unescaped)
 {
     size_t oldLen = unescaped->length();
     const jschar *oldChars = unescaped->chars();
@@ -525,117 +539,145 @@ js_InitRegExpClass(JSContext *cx, Handle
     AddTypeProperty(cx, type, "lastIndex", Type::Int32Type());
 
     if (!DefineConstructorAndPrototype(cx, global, JSProto_RegExp, ctor, proto))
         return NULL;
 
     return proto;
 }
 
-bool
-js::ExecuteRegExp(JSContext *cx, RegExpExecType execType, HandleObject regexp,
-                  HandleString string, MutableHandleValue rval)
+RegExpRunStatus
+js::ExecuteRegExp(JSContext *cx, HandleObject regexp, HandleString string, MatchConduit &matches)
 {
     /* Step 1 (b) was performed by CallNonGenericMethod. */
     Rooted<RegExpObject*> reobj(cx, &regexp->asRegExp());
 
     RegExpGuard re;
     if (!reobj->getShared(cx, &re))
-        return false;
+        return RegExpRunStatus_Error;
 
     RegExpStatics *res = cx->regExpStatics();
 
     /* Step 3. */
     Rooted<JSStableString*> stableInput(cx, string->ensureStable(cx));
     if (!stableInput)
-        return false;
+        return RegExpRunStatus_Error;
 
     /* Step 4. */
     Value lastIndex = reobj->getLastIndex();
 
     /* Step 5. */
     double i;
     if (!ToInteger(cx, lastIndex, &i))
-        return false;
+        return RegExpRunStatus_Error;
 
     /* Steps 6-7 (with sticky extension). */
     if (!re->global() && !re->sticky())
         i = 0;
 
     StableCharPtr chars = stableInput->chars();
     size_t length = stableInput->length();
 
     /* Step 9a. */
     if (i < 0 || i > length) {
         reobj->zeroLastIndex();
-        rval.setNull();
-        return true;
+        return RegExpRunStatus_Success_NotFound;
     }
 
     /* Steps 8-21. */
     size_t lastIndexInt(i);
-    if (!ExecuteRegExp(cx, res, *re, stableInput, chars, length, &lastIndexInt, execType,
-                       rval.address())) {
-        return false;
-    }
+    RegExpRunStatus status =
+        ExecuteRegExpImpl(cx, res, *re, *reobj, stableInput, chars, length, &lastIndexInt, matches);
+
+    if (status == RegExpRunStatus_Error)
+        return RegExpRunStatus_Error;
 
     /* Step 11 (with sticky extension). */
-    if (re->global() || (!rval.isNull() && re->sticky())) {
-        if (rval.isNull())
+    if (re->global() || (status == RegExpRunStatus_Success && re->sticky())) {
+        if (status == RegExpRunStatus_Success_NotFound)
             reobj->zeroLastIndex();
         else
             reobj->setLastIndex(lastIndexInt);
     }
 
-    return true;
+    return status;
 }
 
-/*
- * ES5 15.10.6.2 (and 15.10.6.3, which calls 15.10.6.2).
- *
- * RegExp.prototype.test doesn't need to create a results array, and we use
- * |execType| to perform this optimization.
- */
-static bool
-ExecuteRegExp(JSContext *cx, RegExpExecType execType, CallArgs args)
+/* ES5 15.10.6.2 (and 15.10.6.3, which calls 15.10.6.2). */
+static RegExpRunStatus
+ExecuteRegExp(JSContext *cx, CallArgs args, MatchConduit &matches)
 {
     /* Step 1 (a) was performed by CallNonGenericMethod. */
     RootedObject regexp(cx, &args.thisv().toObject());
 
     /* Step 2. */
     RootedString string(cx, ToString(cx, (args.length() > 0) ? args[0] : UndefinedValue()));
     if (!string)
-        return false;
+        return RegExpRunStatus_Error;
 
-    return ExecuteRegExp(cx, execType, regexp, string, args.rval());
+    return ExecuteRegExp(cx, regexp, string, matches);
 }
 
 /* ES5 15.10.6.2. */
 static bool
 regexp_exec_impl(JSContext *cx, CallArgs args)
 {
-    return ExecuteRegExp(cx, RegExpExec, args);
+    /* Execute regular expression and gather matches. */
+    ScopedMatchPairs matches(&cx->tempLifoAlloc());
+    MatchConduit conduit(&matches);
+
+    /*
+     * Extract arguments to share between ExecuteRegExp()
+     * and CreateRegExpMatchResult().
+     */
+    RootedObject regexp(cx, &args.thisv().toObject());
+    RootedString string(cx, ToString(cx, (args.length() > 0) ? args[0] : UndefinedValue()));
+    if (!string)
+        return false;
+
+    RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, conduit);
+
+    if (status == RegExpRunStatus_Error)
+        return false;
+
+    if (status == RegExpRunStatus_Success_NotFound) {
+        args.rval().setNull();
+        return true;
+    }
+
+    return CreateRegExpMatchResult(cx, string, matches, args.rval().address());
 }
 
 JSBool
 js::regexp_exec(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod(cx, IsRegExp, regexp_exec_impl, args);
 }
 
 /* ES5 15.10.6.3. */
 static bool
 regexp_test_impl(JSContext *cx, CallArgs args)
 {
-    if (!ExecuteRegExp(cx, RegExpTest, args))
-        return false;
-    if (!args.rval().isTrue())
-        args.rval().setBoolean(false);
-    return true;
+    MatchPair match;
+    MatchConduit conduit(&match);
+    RegExpRunStatus status = ExecuteRegExp(cx, args, conduit);
+    args.rval().setBoolean(status == RegExpRunStatus_Success);
+    return (status != RegExpRunStatus_Error);
+}
+
+/* Separate interface for use by IonMonkey. */
+bool
+js::regexp_test_raw(JSContext *cx, HandleObject regexp, HandleString input, JSBool *result)
+{
+    MatchPair match;
+    MatchConduit conduit(&match);
+    RegExpRunStatus status = ExecuteRegExp(cx, regexp, input, conduit);
+    *result = (status == RegExpRunStatus_Success);
+    return (status != RegExpRunStatus_Error);
 }
 
 JSBool
 js::regexp_test(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod(cx, IsRegExp, regexp_test_impl, args);
 }
--- a/js/src/builtin/RegExp.h
+++ b/js/src/builtin/RegExp.h
@@ -5,46 +5,57 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef RegExp_h___
 #define RegExp_h___
 
 #include "jsprvtd.h"
 
+#include "vm/MatchPairs.h"
+#include "vm/RegExpObject.h"
+
 JSObject *
 js_InitRegExpClass(JSContext *cx, js::HandleObject obj);
 
 /*
  * The following builtin natives are extern'd for pointer comparison in
  * other parts of the engine.
  */
 
 namespace js {
 
+RegExpRunStatus
+ExecuteRegExp(JSContext *cx, HandleObject regexp, HandleString string,
+              MatchConduit &matches);
+
 /*
- * |res| may be null if the |RegExpStatics| are not to be updated.
- * |input| may be null if there is no |JSString| corresponding to
+ * Legacy behavior of ExecuteRegExp(), which is baked into the JSAPI.
+ *
+ * |res| may be NULL if the RegExpStatics are not to be updated.
+ * |input| may be NULL if there is no JSString corresponding to
  * |chars| and |length|.
  */
 bool
-ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpObject &reobj,
-              Handle<JSStableString*> input, StableCharPtr chars, size_t length,
-              size_t *lastIndex, RegExpExecType type, Value *rval);
+ExecuteRegExpLegacy(JSContext *cx, RegExpStatics *res, RegExpObject &reobj,
+                    Handle<JSStableString*> input, StableCharPtr chars, size_t length,
+                    size_t *lastIndex, JSBool test, jsval *rval);
+
+/* Translation from MatchPairs to a JS array in regexp_exec()'s output format. */
+bool
+CreateRegExpMatchResult(JSContext *cx, HandleString string, MatchPairs &matches, Value *rval);
 
 bool
-ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpShared &shared,
-              Handle<JSStableString*> input, StableCharPtr chars, size_t length,
-              size_t *lastIndex, RegExpExecType type, Value *rval);
-
-bool
-ExecuteRegExp(JSContext *cx, RegExpExecType execType, HandleObject regexp,
-              HandleString string, MutableHandleValue rval);
+CreateRegExpMatchResult(JSContext *cx, JSString *input_, StableCharPtr chars, size_t length,
+                        MatchPairs &matches, Value *rval);
 
 extern JSBool
 regexp_exec(JSContext *cx, unsigned argc, Value *vp);
 
+bool
+regexp_test_raw(JSContext *cx, HandleObject regexp, HandleString input, JSBool *result);
+
 extern JSBool
 regexp_test(JSContext *cx, unsigned argc, Value *vp);
 
 } /* namespace js */
 
-#endif
+#endif /* RegExp_h__ */
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -669,19 +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->matchPairsInput)
-        MarkStringRoot(trc, reinterpret_cast<JSString**>(&statics->matchPairsInput),
-                       "RegExpStatics::AutoRooter matchPairsInput");
+    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");
 }
 
 void
 HashableValue::AutoRooter::trace(JSTracer *trc)
 {
--- a/js/src/ion/CodeGenerator.cpp
+++ b/js/src/ion/CodeGenerator.cpp
@@ -240,38 +240,26 @@ CodeGenerator::visitRegExp(LRegExp *lir)
 {
     JSObject *proto = lir->mir()->getRegExpPrototype();
 
     pushArg(ImmGCPtr(proto));
     pushArg(ImmGCPtr(lir->mir()->source()));
     return callVM(CloneRegExpObjectInfo, lir);
 }
 
-typedef bool (*ExecuteRegExpFn)(JSContext *cx, RegExpExecType type, HandleObject regexp,
-                                HandleString string, MutableHandleValue rval);
-static const VMFunction ExecuteRegExpInfo = FunctionInfo<ExecuteRegExpFn>(ExecuteRegExp);
+typedef bool (*RegExpTestRawFn)(JSContext *cx, HandleObject regexp,
+                                HandleString input, JSBool *result);
+static const VMFunction RegExpTestRawInfo = FunctionInfo<RegExpTestRawFn>(regexp_test_raw);
 
 bool
 CodeGenerator::visitRegExpTest(LRegExpTest *lir)
 {
     pushArg(ToRegister(lir->string()));
     pushArg(ToRegister(lir->regexp()));
-    pushArg(Imm32(RegExpTest));
-    if (!callVM(ExecuteRegExpInfo, lir))
-        return false;
-
-    Register output = ToRegister(lir->output());
-    Label notBool, end;
-    masm.branchTestBoolean(Assembler::NotEqual, JSReturnOperand, &notBool);
-    masm.unboxBoolean(JSReturnOperand, output);
-    masm.jump(&end);
-    masm.bind(&notBool);
-    masm.mov(Imm32(0), output);
-    masm.bind(&end);
-    return true;
+    return callVM(RegExpTestRawInfo, lir);
 }
 
 typedef JSObject *(*LambdaFn)(JSContext *, HandleFunction, HandleObject);
 static const VMFunction LambdaInfo =
     FunctionInfo<LambdaFn>(js::Lambda);
 
 bool
 CodeGenerator::visitLambdaForSingleton(LLambdaForSingleton *lir)
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -6734,18 +6734,20 @@ JS_ExecuteRegExp(JSContext *cx, JSObject
                  size_t *indexp, JSBool test, jsval *rval)
 {
     RootedObject obj(cx, objArg);
     RootedObject reobj(cx, reobjArg);
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
 
     RegExpStatics *res = obj->asGlobal().getRegExpStatics();
-    return ExecuteRegExp(cx, res, reobj->asRegExp(), NullPtr(), StableCharPtr(chars, length),
-                         length, indexp, test ? RegExpTest : RegExpExec, rval);
+    StableCharPtr charPtr(chars, length);
+
+    return ExecuteRegExpLegacy(cx, res, reobj->asRegExp(), NullPtr(),
+                               charPtr, length, indexp, test, rval);
 }
 
 JS_PUBLIC_API(JSObject *)
 JS_NewRegExpObjectNoStatics(JSContext *cx, char *bytes, size_t length, unsigned flags)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     jschar *chars = InflateString(cx, bytes, &length);
@@ -6769,18 +6771,19 @@ JS_NewUCRegExpObjectNoStatics(JSContext 
 JS_PUBLIC_API(JSBool)
 JS_ExecuteRegExpNoStatics(JSContext *cx, JSObject *objArg, jschar *chars, size_t length,
                           size_t *indexp, JSBool test, jsval *rval)
 {
     RootedObject obj(cx, objArg);
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
 
-    return ExecuteRegExp(cx, NULL, obj->asRegExp(), NullPtr(), StableCharPtr(chars, length),
-                         length, indexp, test ? RegExpTest : RegExpExec, rval);
+    StableCharPtr charPtr(chars, length);
+    return ExecuteRegExpLegacy(cx, NULL, obj->asRegExp(), NullPtr(),
+                               charPtr, length, indexp, test, rval);
 }
 
 JS_PUBLIC_API(JSBool)
 JS_ObjectIsRegExp(JSContext *cx, JSObject *objArg)
 {
     RootedObject obj(cx, objArg);
     AssertHeapIsIdle(cx);
     JS_ASSERT(obj);
--- a/js/src/jsprvtd.h
+++ b/js/src/jsprvtd.h
@@ -100,22 +100,16 @@ enum RegExpFlag
     GlobalFlag      = 0x02,
     MultilineFlag   = 0x04,
     StickyFlag      = 0x08,
 
     NoFlags         = 0x00,
     AllFlags        = 0x0f
 };
 
-enum RegExpExecType
-{
-    RegExpExec,
-    RegExpTest
-};
-
 class ExecuteArgsGuard;
 class InvokeFrameGuard;
 class InvokeArgsGuard;
 class StringBuffer;
 
 class FrameRegs;
 class StackFrame;
 class StackSegment;
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -1643,23 +1643,16 @@ class StringRegExpGuard
         JS_ASSERT(patstr);
 
         return cx->compartment->regExps.get(cx, patstr, opt, &re_);
     }
 
     RegExpShared &regExp() { return *re_; }
 };
 
-/* ExecuteRegExp indicates success in two ways, based on the 'test' flag. */
-static JS_ALWAYS_INLINE bool
-Matched(RegExpExecType type, const Value &v)
-{
-    return (type == RegExpTest) ? v.isTrue() : !v.isNull();
-}
-
 typedef bool (*DoMatchCallback)(JSContext *cx, RegExpStatics *res, size_t count, void *data);
 
 /*
  * BitOR-ing these flags allows the DoMatch caller to control when how the
  * RegExp engine is called and when callbacks are fired.
  */
 enum MatchControlFlags {
    TEST_GLOBAL_BIT         = 0x1, /* use RegExp.test for global regexps */
@@ -1675,44 +1668,70 @@ enum MatchControlFlags {
 static bool
 DoMatch(JSContext *cx, RegExpStatics *res, JSString *str, RegExpShared &re,
         DoMatchCallback callback, void *data, MatchControlFlags flags, Value *rval)
 {
     Rooted<JSStableString*> stableStr(cx, str->ensureStable(cx));
     if (!stableStr)
         return false;
 
+    StableCharPtr chars = stableStr->chars();
+    size_t charsLen = stableStr->length();
+
+    ScopedMatchPairs matches(&cx->tempLifoAlloc());
+
     if (re.global()) {
-        RegExpExecType type = (flags & TEST_GLOBAL_BIT) ? RegExpTest : RegExpExec;
+        bool isTest = bool(flags & TEST_GLOBAL_BIT);
         for (size_t count = 0, i = 0, length = str->length(); i <= length; ++count) {
             if (!JS_CHECK_OPERATION_LIMIT(cx))
                 return false;
 
-            StableCharPtr chars = stableStr->chars();
-            size_t charsLen = stableStr->length();
-
-            if (!ExecuteRegExp(cx, res, re, stableStr, chars, charsLen, &i, type, rval))
+            RegExpRunStatus status = re.execute(cx, chars, charsLen, &i, matches);
+            if (status == RegExpRunStatus_Error)
                 return false;
-            if (!Matched(type, *rval))
+
+            if (status == RegExpRunStatus_Success_NotFound) {
+                rval->setNull();
                 break;
+            }
+
+            res->updateFromMatchPairs(cx, stableStr, matches);
+            if (!isTest && !CreateRegExpMatchResult(cx, stableStr, matches, rval))
+                return false;
+
             if (!callback(cx, res, count, data))
                 return false;
             if (!res->matched())
                 ++i;
         }
     } else {
-        StableCharPtr chars = stableStr->chars();
-        size_t charsLen = stableStr->length();
-
-        RegExpExecType type = (flags & TEST_SINGLE_BIT) ? RegExpTest : RegExpExec;
+        bool isTest = bool(flags & TEST_SINGLE_BIT);
         bool callbackOnSingle = !!(flags & CALLBACK_ON_SINGLE_BIT);
         size_t i = 0;
-        if (!ExecuteRegExp(cx, res, re, stableStr, chars, charsLen, &i, type, rval))
+
+        RegExpRunStatus status = re.execute(cx, chars, charsLen, &i, matches);
+        if (status == RegExpRunStatus_Error)
             return false;
-        if (callbackOnSingle && Matched(type, *rval) && !callback(cx, res, 0, data))
+
+        /* Emulate ExecuteRegExpLegacy() behavior. */
+        if (status == RegExpRunStatus_Success_NotFound) {
+            rval->setNull();
+            return true;
+        }
+
+        res->updateFromMatchPairs(cx, stableStr, matches);
+
+        if (isTest) {
+            rval->setBoolean(true);
+        } else {
+            if (!CreateRegExpMatchResult(cx, stableStr, matches, rval))
+                return false;
+        }
+
+        if (callbackOnSingle && !callback(cx, res, 0, data))
             return false;
     }
     return true;
 }
 
 static bool
 BuildFlatMatchArray(JSContext *cx, HandleString textstr, const FlatMatch &fm, CallArgs *args)
 {
@@ -1827,24 +1846,29 @@ js::str_search(JSContext *cx, unsigned a
         return false;
 
     StableCharPtr chars = stableStr->chars();
     size_t length = stableStr->length();
     RegExpStatics *res = cx->regExpStatics();
 
     /* Per ECMAv5 15.5.4.12 (5) The last index property is ignored and left unchanged. */
     size_t i = 0;
-    Value result;
-    if (!ExecuteRegExp(cx, res, g.regExp(), stableStr, chars, length, &i, RegExpTest, &result))
+    ScopedMatchPairs matches(&cx->tempLifoAlloc());
+
+    RegExpRunStatus status = g.regExp().execute(cx, chars, length, &i, matches);
+    if (status == RegExpRunStatus_Error)
         return false;
 
-    if (result.isTrue())
-        args.rval().setInt32(res->matchStart());
-    else
+    if (status == RegExpRunStatus_Success) {
+        res->updateFromMatchPairs(cx, stableStr, matches);
+        args.rval().setInt32(matches[0].start);
+    } else {
         args.rval().setInt32(-1);
+    }
+
     return true;
 }
 
 struct ReplaceData
 {
     ReplaceData(JSContext *cx)
       : str(cx), g(cx), lambda(cx), elembase(cx), repstr(cx),
         dollarRoot(cx, &dollar), dollarEndRoot(cx, &dollarEnd),
@@ -1877,33 +1901,33 @@ InterpretDollar(RegExpStatics *res, cons
     if (dp + 1 >= ep)
         return false;
 
     /* Interpret all Perl match-induced dollar variables. */
     jschar dc = dp[1];
     if (JS7_ISDEC(dc)) {
         /* ECMA-262 Edition 3: 1-9 or 01-99 */
         unsigned num = JS7_UNDEC(dc);
-        if (num > res->parenCount())
+        if (num > res->getMatches().parenCount())
             return false;
 
         const jschar *cp = dp + 2;
         if (cp < ep && (dc = *cp, JS7_ISDEC(dc))) {
             unsigned tmp = 10 * num + JS7_UNDEC(dc);
-            if (tmp <= res->parenCount()) {
+            if (tmp <= res->getMatches().parenCount()) {
                 cp++;
                 num = tmp;
             }
         }
         if (num == 0)
             return false;
 
         *skip = cp - dp;
 
-        JS_ASSERT(num <= res->parenCount());
+        JS_ASSERT(num <= res->getMatches().parenCount());
 
         /*
          * Note: we index to get the paren with the (1-indexed) pair
          * number, as opposed to a (0-indexed) paren number.
          */
         res->getParen(num, out);
         return true;
     }
@@ -1984,38 +2008,38 @@ FindReplaceLength(JSContext *cx, RegExpS
         /*
          * In the lambda case, not only do we find the replacement string's
          * length, we compute repstr and return it via rdata for use within
          * DoReplace.  The lambda is called with arguments ($&, $1, $2, ...,
          * index, input), i.e., all the properties of a regexp match array.
          * For $&, etc., we must create string jsvals from cx->regExpStatics.
          * We grab up stack space to keep the newborn strings GC-rooted.
          */
-        unsigned p = res->parenCount();
+        unsigned p = res->getMatches().parenCount();
         unsigned argc = 1 + p + 2;
 
         InvokeArgsGuard &args = rdata.fig.args();
         if (!args.pushed() && !cx->stack.pushInvokeArgs(cx, argc, &args))
             return false;
 
         args.setCallee(ObjectValue(*lambda));
         args.setThis(UndefinedValue());
 
         /* Push $&, $1, $2, ... */
         unsigned argi = 0;
         if (!res->createLastMatch(cx, &args[argi++]))
             return false;
 
-        for (size_t i = 0; i < res->parenCount(); ++i) {
+        for (size_t i = 0; i < res->getMatches().parenCount(); ++i) {
             if (!res->createParen(cx, i + 1, &args[argi++]))
                 return false;
         }
 
         /* Push match index and input string. */
-        args[argi++].setInt32(res->matchStart());
+        args[argi++].setInt32(res->getMatches()[0].start);
         args[argi].setString(rdata.str);
 
         if (!rdata.fig.invoke(cx))
             return false;
 
         /* root repstr: rdata is on the stack, so scanned by conservative gc. */
         JSString *repstr = ToString(cx, args.rval());
         if (!repstr)
@@ -2086,20 +2110,24 @@ DoReplace(RegExpStatics *res, ReplaceDat
     rdata.sb.infallibleAppend(cp, repstr->length() - (cp - bp));
 }
 
 static bool
 ReplaceRegExpCallback(JSContext *cx, RegExpStatics *res, size_t count, void *p)
 {
     ReplaceData &rdata = *static_cast<ReplaceData *>(p);
 
+    const MatchPair &match = res->getMatches()[0];
+    JS_ASSERT(!match.isUndefined());
+    JS_ASSERT(match.limit >= match.start && match.limit >= 0);
+
     rdata.calledBack = true;
     size_t leftoff = rdata.leftIndex;
-    size_t leftlen = res->matchStart() - leftoff;
-    rdata.leftIndex = res->matchLimit();
+    size_t leftlen = match.start - leftoff;
+    rdata.leftIndex = match.limit;
 
     size_t replen = 0;  /* silence 'unused' warning */
     if (!FindReplaceLength(cx, res, rdata, &replen))
         return false;
 
     CheckedInt<uint32_t> newlen(rdata.sb.length());
     newlen += leftlen;
     newlen += replen;
@@ -2618,19 +2646,20 @@ SplitHelper(JSContext *cx, Handle<JSStab
             return NewDenseCopiedArray(cx, splits.length(), splits.begin());
 
         /* Step 13(c)(iii)(5). */
         lastEndIndex = endIndex;
 
         /* Step 13(c)(iii)(6-7). */
         if (Matcher::returnsCaptures) {
             RegExpStatics *res = cx->regExpStatics();
-            for (size_t i = 0; i < res->parenCount(); i++) {
+            const MatchPairs &matches = res->getMatches();
+            for (size_t i = 0; i < matches.parenCount(); i++) {
                 /* Steps 13(c)(iii)(7)(a-c). */
-                if (res->pairIsPresent(i + 1)) {
+                if (!matches[i + 1].isUndefined()) {
                     JSSubString parsub;
                     res->getParen(i + 1, &parsub);
                     sub = js_NewStringCopyN(cx, parsub.chars, parsub.length);
                     if (!sub || !splits.append(StringValue(sub)))
                         return NULL;
                 } else {
                     /* Only string entries have been accounted for so far. */
                     AddTypeProperty(cx, type, NULL, UndefinedValue());
@@ -2673,25 +2702,31 @@ class SplitRegExpMatcher
     SplitRegExpMatcher(RegExpShared &re, RegExpStatics *res) : re(re), res(res) {}
 
     static const bool returnsCaptures = true;
 
     bool operator()(JSContext *cx, Handle<JSStableString*> str, size_t index,
                     SplitMatchResult *result) const
     {
         AssertCanGC();
-        Value rval = UndefinedValue();
         StableCharPtr chars = str->chars();
         size_t length = str->length();
-        if (!ExecuteRegExp(cx, res, re, str, chars, length, &index, RegExpTest, &rval))
+
+        ScopedMatchPairs matches(&cx->tempLifoAlloc());
+        RegExpRunStatus status = re.execute(cx, chars, length, &index, matches);
+        if (status == RegExpRunStatus_Error)
             return false;
-        if (!rval.isTrue()) {
+
+        if (status == RegExpRunStatus_Success_NotFound) {
             result->setFailure();
             return true;
         }
+
+        res->updateFromMatchPairs(cx, str, matches);
+
         JSSubString sep;
         res->getLastMatch(&sep);
 
         result->setResult(sep.length, index);
         return true;
     }
 };
 
--- a/js/src/vm/MatchPairs.h
+++ b/js/src/vm/MatchPairs.h
@@ -18,75 +18,149 @@
 
 namespace js {
 
 struct MatchPair
 {
     int start;
     int limit;
 
-    MatchPair(int start, int limit) : start(start), limit(limit) {}
+    MatchPair()
+      : start(-1), limit(-1)
+    { }
+
+    MatchPair(int start, int limit)
+      : start(start), limit(limit)
+    { }
 
-    size_t length() const {
-        JS_ASSERT(!isUndefined());
-        return limit - start;
+    size_t length()      const { JS_ASSERT(!isUndefined()); return limit - start; }
+    bool isEmpty()       const { return length() == 0; }
+    bool isUndefined()   const { return start < 0; }
+
+    void displace(size_t amount) {
+        start += (start < 0) ? 0 : amount;
+        limit += (limit < 0) ? 0 : amount;
     }
 
-    bool isUndefined() const {
-        return start == -1;
-    }
-
-    void check() const {
+    inline bool check() const {
         JS_ASSERT(limit >= start);
-        JS_ASSERT_IF(!isUndefined(), start >= 0);
+        JS_ASSERT_IF(start < 0, start == -1);
+        JS_ASSERT_IF(limit < 0, limit == -1);
+        return true;
     }
 };
 
+/* Base class for RegExp execution output. */
 class MatchPairs
 {
-    size_t  pairCount_;
-    int     buffer_[1];
+  protected:
+    size_t     pairCount_;   /* Length of pairs_. */
+    MatchPair *pairs_;       /* Raw pointer into an allocated MatchPair buffer. */
+
+  protected:
+    /* Not used directly: use ScopedMatchPairs or VectorMatchPairs. */
+    MatchPairs()
+      : pairCount_(0), pairs_(NULL)
+    { }
+
+  protected:
+    /* Functions used by friend classes. */
+    friend class RegExpShared;
+    friend class RegExpStatics;
+
+    /* MatchPair buffer allocator: set pairs_ and pairCount_. */
+    virtual bool allocOrExpandArray(size_t pairCount) = 0;
 
-    explicit MatchPairs(size_t pairCount) : pairCount_(pairCount) {
-        initPairValues();
+    bool initArray(size_t pairCount);
+    bool initArrayFrom(MatchPairs &copyFrom);
+    void forgetArray() { pairs_ = NULL; }
+
+    void displace(size_t disp);
+    inline void checkAgainst(size_t length);
+
+  public:
+    /* Querying functions in the style of RegExpStatics. */
+    bool   empty() const           { return pairCount_ == 0; }
+    size_t pairCount() const       { JS_ASSERT(pairCount_ > 0); return pairCount_; }
+    size_t parenCount() const      { return pairCount_ - 1; }
+
+  public:
+    unsigned *rawBuf() const { return reinterpret_cast<unsigned *>(pairs_); }
+    size_t length() const { return pairCount_; }
+
+    /* Pair accessors. */
+    const MatchPair &pair(size_t i) const {
+        JS_ASSERT(pairCount_ && i < pairCount_);
+        JS_ASSERT(pairs_);
+        return pairs_[i];
     }
 
-    void initPairValues() {
-        for (int *it = buffer_; it < buffer_ + 2 * pairCount_; ++it)
-            *it = -1;
-    }
+    const MatchPair &operator[](size_t i) const { return pair(i); }
+};
 
-    static size_t calculateSize(size_t backingPairCount) {
-        return sizeof(MatchPairs) - sizeof(int) + sizeof(int) * backingPairCount;
-    }
-
-    int *buffer() { return buffer_; }
-
-    friend class RegExpShared;
+/* MatchPairs allocated into temporary storage, removed when out of scope. */
+class ScopedMatchPairs : public MatchPairs
+{
+    LifoAlloc *lifoAlloc_;
+    void      *mark_;        /* Saved original position in bump allocator. */
 
   public:
-    /*
-     * |backingPairCount| is necessary because PCRE uses extra space
-     * after the actual results in the buffer.
-     */
-    static MatchPairs *create(LifoAlloc &alloc, size_t pairCount, size_t backingPairCount);
+    /* Constructs an implicit LifoAllocScope. */
+    ScopedMatchPairs(LifoAlloc *lifoAlloc)
+      : lifoAlloc_(lifoAlloc),
+        mark_(lifoAlloc->mark())
+    { }
 
-    size_t pairCount() const { return pairCount_; }
-
-    MatchPair pair(size_t i) {
-        JS_ASSERT(i < pairCount());
-        return MatchPair(buffer_[2 * i], buffer_[2 * i + 1]);
+    ~ScopedMatchPairs() {
+        lifoAlloc_->release(mark_);
     }
 
-    void displace(size_t amount) {
-        if (!amount)
-            return;
+    const MatchPair &operator[](size_t i) const { return pair(i); }
+
+  protected:
+    bool allocOrExpandArray(size_t pairCount);
+};
 
-        for (int *it = buffer_; it < buffer_ + 2 * pairCount_; ++it)
-            *it = (*it < 0) ? -1 : *it + amount;
+/*
+ * MatchPairs allocated into permanent storage, for RegExpStatics.
+ * The Vector of MatchPairs is reusable by Vector expansion.
+ */
+class VectorMatchPairs : public MatchPairs
+{
+    Vector<MatchPair, 10, SystemAllocPolicy> vec_;
+
+  public:
+    VectorMatchPairs() {
+        vec_.clear();
     }
 
-    inline void checkAgainst(size_t length);
+    const MatchPair &operator[](size_t i) const { return pair(i); }
+
+  protected:
+    friend class RegExpStatics;
+    bool allocOrExpandArray(size_t pairCount);
+};
+
+/*
+ * Passes either MatchPair or MatchPairs through ExecuteRegExp()
+ * to avoid duplication of generic code.
+ */
+struct MatchConduit
+{
+    union {
+        MatchPair  *pair;
+        MatchPairs *pairs;
+    } u;
+    bool isPair;
+
+    explicit MatchConduit(MatchPair *pair) {
+        isPair = true;
+        u.pair = pair;
+    }
+    explicit MatchConduit(MatchPairs *pairs) {
+        isPair = false;
+        u.pairs = pairs;
+    }
 };
 
 } /* namespace js */
 
-#endif
+#endif /* MatchPairs_h__ */
--- a/js/src/vm/RegExpObject.cpp
+++ b/js/src/vm/RegExpObject.cpp
@@ -106,40 +106,108 @@ RegExpObjectBuilder::clone(Handle<RegExp
         return NULL;
 
     Rooted<JSAtom *> source(cx, other->getSource());
     return build(source, *g);
 }
 
 /* MatchPairs */
 
-MatchPairs *
-MatchPairs::create(LifoAlloc &alloc, size_t pairCount, size_t backingPairCount)
+bool
+MatchPairs::initArray(size_t pairCount)
+{
+    JS_ASSERT(pairCount > 0);
+
+    /* Guarantee adequate space in buffer. */
+    if (!allocOrExpandArray(pairCount))
+        return false;
+
+    /* Initialize all MatchPair objects to invalid locations. */
+    for (size_t i = 0; i < pairCount; i++) {
+        pairs_[i].start = size_t(-1);
+        pairs_[i].limit = size_t(-1);
+    }
+
+    return true;
+}
+
+bool
+MatchPairs::initArrayFrom(MatchPairs &copyFrom)
 {
-    void *mem = alloc.alloc(calculateSize(backingPairCount));
-    if (!mem)
-        return NULL;
+    JS_ASSERT(copyFrom.pairCount() > 0);
+
+    if (!allocOrExpandArray(copyFrom.pairCount()))
+        return false;
+
+    for (size_t i = 0; i < pairCount_; i++) {
+        JS_ASSERT(copyFrom[i].check());
+        pairs_[i].start = copyFrom[i].start;
+        pairs_[i].limit = copyFrom[i].limit;
+    }
 
-    return new (mem) MatchPairs(pairCount);
+    return true;
+}
+
+void
+MatchPairs::displace(size_t disp)
+{
+    if (disp == 0)
+        return;
+
+    for (size_t i = 0; i < pairCount_; i++) {
+        JS_ASSERT(pairs_[i].check());
+        pairs_[i].start += (pairs_[i].start < 0) ? 0 : disp;
+        pairs_[i].limit += (pairs_[i].limit < 0) ? 0 : disp;
+    }
 }
 
 inline void
 MatchPairs::checkAgainst(size_t inputLength)
 {
 #if DEBUG
-    for (size_t i = 0; i < pairCount(); ++i) {
-        MatchPair p = pair(i);
-        p.check();
+    for (size_t i = 0; i < pairCount_; i++) {
+        const MatchPair &p = pair(i);
+        JS_ASSERT(p.check());
         if (p.isUndefined())
             continue;
         JS_ASSERT(size_t(p.limit) <= inputLength);
     }
 #endif
 }
 
+bool
+ScopedMatchPairs::allocOrExpandArray(size_t pairCount)
+{
+    /* Array expansion is forbidden, but array reuse is acceptable. */
+    if (pairCount_) {
+        JS_ASSERT(pairs_);
+        JS_ASSERT(pairCount_ == pairCount);
+        return true;
+    }
+
+    JS_ASSERT(!pairs_);
+    pairs_ = (MatchPair *)lifoAlloc_->alloc(sizeof(MatchPair) * pairCount);
+    if (!pairs_)
+        return false;
+
+    pairCount_ = pairCount;
+    return true;
+}
+
+bool
+VectorMatchPairs::allocOrExpandArray(size_t pairCount)
+{
+    if (!vec_.resizeUninitialized(sizeof(MatchPair) * pairCount))
+        return false;
+
+    pairs_ = &vec_[0];
+    pairCount_ = pairCount;
+    return true;
+}
+
 /* RegExpObject */
 
 static void
 regexp_trace(JSTracer *trc, RawObject obj)
 {
      /*
       * We have to check both conditions, since:
       *   1. During TraceRuntime, isHeapBusy() is true
@@ -286,26 +354,16 @@ RegExpObject::init(JSContext *cx, Handle
     self->setSource(source);
     self->setGlobal(flags & GlobalFlag);
     self->setIgnoreCase(flags & IgnoreCaseFlag);
     self->setMultiline(flags & MultilineFlag);
     self->setSticky(flags & StickyFlag);
     return true;
 }
 
-RegExpRunStatus
-RegExpObject::execute(JSContext *cx, StableCharPtr chars, size_t length, size_t *lastIndex,
-                      MatchPairs **output)
-{
-    RegExpGuard g;
-    if (!getShared(cx, &g))
-        return RegExpRunStatus_Error;
-    return g->execute(cx, chars, length, lastIndex, output);
-}
-
 JSFlatString *
 RegExpObject::toString(JSContext *cx) const
 {
     JSAtom *src = getSource();
     StringBuffer sb(cx);
     if (size_t len = src->length()) {
         if (!sb.reserve(len + 2))
             return NULL;
@@ -461,65 +519,60 @@ bool
 RegExpShared::compileIfNecessary(JSContext *cx)
 {
     if (hasCode() || hasBytecode())
         return true;
     return compile(cx);
 }
 
 RegExpRunStatus
-RegExpShared::execute(JSContext *cx, StableCharPtr chars, size_t length, size_t *lastIndex,
-                      MatchPairs **output)
+RegExpShared::execute(JSContext *cx, StableCharPtr chars, size_t length,
+                      size_t *lastIndex, MatchPairs &matches)
 {
     /* Compile the code at point-of-use. */
     if (!compileIfNecessary(cx))
         return RegExpRunStatus_Error;
 
-    const size_t origLength = length;
-    size_t backingPairCount = pairCount() * 2;
-
-    LifoAlloc &alloc = cx->tempLifoAlloc();
-    MatchPairs *matchPairs = MatchPairs::create(alloc, pairCount(), backingPairCount);
-    if (!matchPairs)
+    /* Ensure sufficient memory for output vector. */
+    if (!matches.initArray(pairCount()))
         return RegExpRunStatus_Error;
 
     /*
      * |displacement| emulates sticky mode by matching from this offset
      * into the char buffer and subtracting the delta off at the end.
      */
+    size_t origLength = length;
     size_t start = *lastIndex;
     size_t displacement = 0;
 
     if (sticky()) {
-        displacement = *lastIndex;
+        displacement = start;
         chars += displacement;
         length -= displacement;
         start = 0;
     }
 
-    unsigned *outputBuf = (unsigned *)matchPairs->buffer();
+    unsigned *outputBuf = matches.rawBuf();
     unsigned result;
 
 #if ENABLE_YARR_JIT
     if (codeBlock.isFallBack())
         result = JSC::Yarr::interpret(bytecode, chars.get(), length, start, outputBuf);
     else
         result = codeBlock.execute(chars.get(), start, length, (int *)outputBuf).start;
 #else
     result = JSC::Yarr::interpret(bytecode, chars.get(), length, start, outputBuf);
 #endif
 
-    *output = matchPairs;
-
     if (result == JSC::Yarr::offsetNoMatch)
         return RegExpRunStatus_Success_NotFound;
 
-    matchPairs->displace(displacement);
-    matchPairs->checkAgainst(origLength);
-    *lastIndex = matchPairs->pair(0).limit;
+    matches.displace(displacement);
+    matches.checkAgainst(origLength);
+    *lastIndex = matches[0].limit;
     return RegExpRunStatus_Success;
 }
 
 /* RegExpCompartment */
 
 RegExpCompartment::RegExpCompartment(JSRuntime *rt)
   : map_(rt), inUse_(rt)
 {}
--- a/js/src/vm/RegExpObject.h
+++ b/js/src/vm/RegExpObject.h
@@ -10,17 +10,19 @@
 
 #include "mozilla/Attributes.h"
 
 #include <stddef.h>
 #include "jscntxt.h"
 #include "jsobj.h"
 
 #include "js/TemplateLib.h"
+#include "vm/MatchPairs.h"
 
+#include "yarr/MatchResult.h"
 #include "yarr/Yarr.h"
 #if ENABLE_YARR_JIT
 #include "yarr/YarrJIT.h"
 #endif
 #include "yarr/YarrSyntaxChecker.h"
 
 /*
  * JavaScript Regular Expressions
@@ -151,17 +153,17 @@ class RegExpShared
     static void reportYarrError(JSContext *cx, TokenStream *ts, ErrorCode error);
     static bool checkSyntax(JSContext *cx, TokenStream *tokenStream, JSLinearString *source);
 
     /* 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);
+                            size_t *lastIndex, MatchPairs &matches);
 
     /* 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. */
@@ -282,31 +284,16 @@ class RegExpObject : public JSObject
 
     static RegExpObject *
     createNoStatics(JSContext *cx, StableCharPtr chars, size_t length, RegExpFlag flags,
                     frontend::TokenStream *ts);
 
     static RegExpObject *
     createNoStatics(JSContext *cx, HandleAtom atom, RegExpFlag flags, frontend::TokenStream *ts);
 
-    /*
-     * Run the regular expression over the input text.
-     *
-     * Results are placed in |output| as integer pairs. For eaxmple,
-     * |output[0]| and |output[1]| represent the text indices that make
-     * up the "0" (whole match) pair. Capturing parens will result in
-     * more output.
-     *
-     * N.B. it's the responsibility of the caller to hook the |output|
-     * into the |RegExpStatics| appropriately, if necessary.
-     */
-    RegExpRunStatus
-    execute(JSContext *cx, StableCharPtr chars, size_t length, size_t *lastIndex,
-            MatchPairs **output);
-
     /* Accessors. */
 
     const Value &getLastIndex() const { return getSlot(LAST_INDEX_SLOT); }
     inline void setLastIndex(double d);
     inline void zeroLastIndex();
 
     JSFlatString *toString(JSContext *cx) const;
 
--- a/js/src/vm/RegExpStatics-inl.h
+++ b/js/src/vm/RegExpStatics-inl.h
@@ -31,158 +31,179 @@ inline
 RegExpStatics::RegExpStatics()
   : bufferLink(NULL),
     copied(false)
 {
     clear();
 }
 
 inline bool
-RegExpStatics::createDependent(JSContext *cx, size_t start, size_t end, Value *out) const
+RegExpStatics::createDependent(JSContext *cx, size_t start, size_t end, Value *out)
 {
+
     JS_ASSERT(start <= end);
-    JS_ASSERT(end <= matchPairsInput->length());
-    JSString *str = js_NewDependentString(cx, matchPairsInput, start, end - start);
+    JS_ASSERT(end <= matchesInput->length());
+    JSString *str = js_NewDependentString(cx, matchesInput, start, end - start);
     if (!str)
         return false;
     *out = StringValue(str);
     return true;
 }
 
 inline bool
-RegExpStatics::createPendingInput(JSContext *cx, Value *out) const
+RegExpStatics::createPendingInput(JSContext *cx, Value *out)
 {
+    /* 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) const
+RegExpStatics::makeMatch(JSContext *cx, size_t checkValidIndex, size_t pairNum, Value *out)
 {
-    if (checkValidIndex / 2 >= pairCount() || matchPairs[checkValidIndex] < 0) {
+    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;
     }
-    return createDependent(cx, get(pairNum, 0), get(pairNum, 1), out);
+    const MatchPair &pair = matches[pairNum];
+    return createDependent(cx, pair.start, pair.limit, out);
 }
 
 inline bool
-RegExpStatics::createLastParen(JSContext *cx, Value *out) const
+RegExpStatics::createLastMatch(JSContext *cx, Value *out)
 {
-    if (pairCount() <= 1) {
+    return makeMatch(cx, 0, 0, out);
+}
+
+inline bool
+RegExpStatics::createLastParen(JSContext *cx, Value *out)
+{
+    if (matches.empty() || matches.pairCount() == 1) {
         out->setString(cx->runtime->emptyString);
         return true;
     }
-    size_t num = pairCount() - 1;
-    int start = get(num, 0);
-    int end = get(num, 1);
-    if (start == -1) {
+    const MatchPair &pair = matches[matches.pairCount() - 1];
+    if (pair.start == -1) {
         out->setString(cx->runtime->emptyString);
         return true;
     }
-    JS_ASSERT(start >= 0 && end >= 0);
-    JS_ASSERT(end >= start);
-    return createDependent(cx, start, end, out);
+    JS_ASSERT(pair.start >= 0 && pair.limit >= 0);
+    JS_ASSERT(pair.limit >= pair.start);
+    return createDependent(cx, pair.start, pair.limit, out);
 }
 
 inline bool
-RegExpStatics::createLeftContext(JSContext *cx, Value *out) const
+RegExpStatics::createParen(JSContext *cx, size_t pairNum, Value *out)
 {
-    if (!pairCount()) {
+    JS_ASSERT(pairNum >= 1);
+    if (matches.empty() || pairNum >= matches.pairCount()) {
         out->setString(cx->runtime->emptyString);
         return true;
     }
-    if (matchPairs[0] < 0) {
+    return makeMatch(cx, pairNum * 2, pairNum, out);
+}
+
+inline bool
+RegExpStatics::createLeftContext(JSContext *cx, Value *out)
+{
+    if (matches.empty()) {
+        out->setString(cx->runtime->emptyString);
+        return true;
+    }
+    if (matches[0].start < 0) {
         *out = UndefinedValue();
         return true;
     }
-    return createDependent(cx, 0, matchPairs[0], out);
+    return createDependent(cx, 0, matches[0].start, out);
 }
 
 inline bool
-RegExpStatics::createRightContext(JSContext *cx, Value *out) const
+RegExpStatics::createRightContext(JSContext *cx, Value *out)
 {
-    if (!pairCount()) {
+    if (matches.empty()) {
         out->setString(cx->runtime->emptyString);
         return true;
     }
-    if (matchPairs[1] < 0) {
+    if (matches[0].limit < 0) {
         *out = UndefinedValue();
         return true;
     }
-    return createDependent(cx, matchPairs[1], matchPairsInput->length(), out);
+    return createDependent(cx, matches[0].limit, matchesInput->length(), out);
 }
 
 inline void
 RegExpStatics::getParen(size_t pairNum, JSSubString *out) const
 {
-    checkParenNum(pairNum);
-    if (!pairIsPresent(pairNum)) {
+    JS_ASSERT(pairNum >= 1 && pairNum < matches.pairCount());
+    const MatchPair &pair = matches[pairNum];
+    if (pair.isUndefined()) {
         *out = js_EmptySubString;
         return;
     }
-    out->chars = matchPairsInput->chars() + get(pairNum, 0);
-    out->length = getParenLength(pairNum);
+    out->chars  = matchesInput->chars() + pair.start;
+    out->length = pair.length();
 }
 
 inline void
 RegExpStatics::getLastMatch(JSSubString *out) const
 {
-    if (!pairCount()) {
+    if (matches.empty()) {
         *out = js_EmptySubString;
         return;
     }
-    JS_ASSERT(matchPairsInput);
-    out->chars = matchPairsInput->chars() + get(0, 0);
-    JS_ASSERT(get(0, 1) >= get(0, 0));
-    out->length = get(0, 1) - get(0, 0);
+    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
 {
-    size_t pc = pairCount();
     /* Note: the first pair is the whole match. */
-    if (pc <= 1) {
+    if (matches.empty() || matches.pairCount() == 1) {
         *out = js_EmptySubString;
         return;
     }
-    getParen(pc - 1, out);
+    getParen(matches.parenCount(), out);
 }
 
 inline void
 RegExpStatics::getLeftContext(JSSubString *out) const
 {
-    if (!pairCount()) {
+    if (matches.empty()) {
         *out = js_EmptySubString;
         return;
     }
-    out->chars = matchPairsInput->chars();
-    out->length = get(0, 0);
+    out->chars = matchesInput->chars();
+    out->length = matches[0].start;
 }
 
 inline void
 RegExpStatics::getRightContext(JSSubString *out) const
 {
-    if (!pairCount()) {
+    if (matches.empty()) {
         *out = js_EmptySubString;
         return;
     }
-    out->chars = matchPairsInput->chars() + get(0, 1);
-    JS_ASSERT(get(0, 1) <= int(matchPairsInput->length()));
-    out->length = matchPairsInput->length() - get(0, 1);
+    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.matchPairs.clear();
-    /* 'save' has already reserved space in matchPairs */
-    dst.matchPairs.infallibleAppend(matchPairs);
-    dst.matchPairsInput = matchPairsInput;
+    dst.matches.initArrayFrom(matches);
+    dst.matchesInput = matchesInput;
     dst.pendingInput = pendingInput;
     dst.flags = flags;
 }
 
 inline void
 RegExpStatics::aboutToWrite()
 {
     if (bufferLink && !bufferLink->copied) {
@@ -195,45 +216,41 @@ inline void
 RegExpStatics::restore()
 {
     if (bufferLink->copied)
         bufferLink->copyTo(*this);
     bufferLink = bufferLink->bufferLink;
 }
 
 inline bool
-RegExpStatics::updateFromMatchPairs(JSContext *cx, JSLinearString *input, MatchPairs *newPairs)
+RegExpStatics::updateFromMatchPairs(JSContext *cx, JSLinearString *input, MatchPairs &newPairs)
 {
     JS_ASSERT(input);
     aboutToWrite();
+
     BarrieredSetPair<JSString, JSLinearString>(cx->compartment,
                                                pendingInput, input,
-                                               matchPairsInput, input);
+                                               matchesInput, input);
 
-    if (!matchPairs.resizeUninitialized(2 * newPairs->pairCount())) {
+    if (!matches.initArrayFrom(newPairs)) {
         js_ReportOutOfMemory(cx);
         return false;
     }
 
-    for (size_t i = 0; i < newPairs->pairCount(); ++i) {
-        matchPairs[2 * i] = newPairs->pair(i).start;
-        matchPairs[2 * i + 1] = newPairs->pair(i).limit;
-    }
-
     return true;
 }
 
 inline void
 RegExpStatics::clear()
 {
     aboutToWrite();
     flags = RegExpFlag(0);
     pendingInput = NULL;
-    matchPairsInput = NULL;
-    matchPairs.clear();
+    matchesInput = NULL;
+    matches.forgetArray();
 }
 
 inline void
 RegExpStatics::setPendingInput(JSString *newInput)
 {
     aboutToWrite();
     pendingInput = newInput;
 }
@@ -276,16 +293,43 @@ RegExpStatics::reset(JSContext *cx, JSSt
 {
     aboutToWrite();
     clear();
     pendingInput = newInput;
     setMultiline(cx, newMultiline);
     checkInvariants();
 }
 
+inline void
+RegExpStatics::checkInvariants()
+{
+#ifdef DEBUG
+    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();
+
+    /* Both members of the first pair must be non-negative. */
+    JS_ASSERT(!matches[0].isUndefined());
+    JS_ASSERT(matches[0].limit >= 0);
+
+    /* Present pairs must be valid. */
+    for (size_t i = 0; i < matches.pairCount(); i++) {
+        if (matches[i].isUndefined())
+            continue;
+        const MatchPair &pair = matches[i];
+        JS_ASSERT(mpiLen >= size_t(pair.limit) && pair.limit >= pair.start && pair.start >= 0);
+    }
+#endif /* DEBUG */
+}
+
 } /* namespace js */
 
 inline js::RegExpStatics *
 JSContext::regExpStatics()
 {
     return global()->getRegExpStatics();
 }
 
--- a/js/src/vm/RegExpStatics.cpp
+++ b/js/src/vm/RegExpStatics.cpp
@@ -4,16 +4,17 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "vm/RegExpStatics.h"
 
 #include "jsobjinlines.h"
 
+#include "vm/RegExpObject-inl.h"
 #include "vm/RegExpStatics-inl.h"
 
 using namespace js;
 
 /*
  * RegExpStatics allocates memory -- in order to keep the statics stored
  * per-global and not leak, we create a js::Class to wrap the C++ instance and
  * provide an appropriate finalizer. We store an instance of that js::Class in
--- a/js/src/vm/RegExpStatics.h
+++ b/js/src/vm/RegExpStatics.h
@@ -15,198 +15,112 @@
 #include "js/Vector.h"
 
 #include "vm/MatchPairs.h"
 
 namespace js {
 
 class RegExpStatics
 {
-    typedef Vector<int, 20, SystemAllocPolicy> Pairs;
-    Pairs                   matchPairs;
-    /* The input that was used to produce matchPairs. */
-    HeapPtr<JSLinearString> matchPairsInput;
-    /* The input last set on the statics. */
+    /* The latest RegExp output, set after execution. */
+    VectorMatchPairs        matches;
+    HeapPtr<JSLinearString> matchesInput;
+
+    /* The latest RegExp input, set before execution. */
     HeapPtr<JSString>       pendingInput;
     RegExpFlag              flags;
+
+    /* Linkage for preserving RegExpStatics during nested RegExp execution. */
     RegExpStatics           *bufferLink;
     bool                    copied;
 
-    bool createDependent(JSContext *cx, size_t start, size_t end, Value *out) const;
-
+  private:
+    inline void aboutToWrite();
     inline void copyTo(RegExpStatics &dst);
 
-    inline void aboutToWrite();
-
+    inline void restore();
     bool save(JSContext *cx, RegExpStatics *buffer) {
         JS_ASSERT(!buffer->copied && !buffer->bufferLink);
         buffer->bufferLink = bufferLink;
         bufferLink = buffer;
-        if (!buffer->matchPairs.reserve(matchPairs.length())) {
+        if (!buffer->matches.allocOrExpandArray(matches.length())) {
             js_ReportOutOfMemory(cx);
             return false;
         }
         return true;
     }
 
-    inline void restore();
-
-    void checkInvariants() {
-#if DEBUG
-        if (pairCount() == 0) {
-            JS_ASSERT(!matchPairsInput);
-            return;
-        }
-
-        /* Pair count is non-zero, so there must be match pairs input. */
-        JS_ASSERT(matchPairsInput);
-        size_t mpiLen = matchPairsInput->length();
-
-        /* Both members of the first pair must be non-negative. */
-        JS_ASSERT(pairIsPresent(0));
-        JS_ASSERT(get(0, 1) >= 0);
-
-        /* Present pairs must be valid. */
-        for (size_t i = 0; i < pairCount(); ++i) {
-            if (!pairIsPresent(i))
-                continue;
-            int start = get(i, 0);
-            int limit = get(i, 1);
-            JS_ASSERT(mpiLen >= size_t(limit) && limit >= start && start >= 0);
-        }
-#endif
-    }
-
-    /*
-     * Since the first pair indicates the whole match, the paren pair
-     * numbers have to be in the range [1, pairCount).
-     */
-    void checkParenNum(size_t pairNum) const {
-        JS_ASSERT(1 <= pairNum);
-        JS_ASSERT(pairNum < pairCount());
-    }
-
-    /* Precondition: paren is present. */
-    size_t getParenLength(size_t pairNum) const {
-        checkParenNum(pairNum);
-        JS_ASSERT(pairIsPresent(pairNum));
-        return get(pairNum, 1) - get(pairNum, 0);
-    }
-
-    int get(size_t pairNum, bool which) const {
-        JS_ASSERT(pairNum < pairCount());
-        return matchPairs[2 * pairNum + which];
-    }
+    inline void checkInvariants();
 
     /*
      * Check whether the index at |checkValidIndex| is valid (>= 0).
      * If so, construct a string for it and place it in |*out|.
      * If not, place undefined in |*out|.
      */
-    bool makeMatch(JSContext *cx, size_t checkValidIndex, size_t pairNum, Value *out) const;
+    bool makeMatch(JSContext *cx, size_t checkValidIndex, size_t pairNum, Value *out);
+    bool createDependent(JSContext *cx, size_t start, size_t end, Value *out);
 
     void markFlagsSet(JSContext *cx);
 
     struct InitBuffer {};
     explicit RegExpStatics(InitBuffer) : bufferLink(NULL), copied(false) {}
 
     friend class PreserveRegExpStatics;
 
   public:
     inline RegExpStatics();
 
     static JSObject *create(JSContext *cx, GlobalObject *parent);
 
     /* Mutators. */
 
-    inline bool updateFromMatchPairs(JSContext *cx, JSLinearString *input, MatchPairs *newPairs);
+    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);
 
-    /* Accessors. */
-
-    /*
-     * When there is a match present, the pairCount is at least 1 for the whole
-     * match. There is one additional pair per parenthesis.
-     *
-     * Getting a parenCount requires there to be a match result as a precondition.
-     */
-
-  private:
-    size_t pairCount() const {
-        JS_ASSERT(matchPairs.length() % 2 == 0);
-        return matchPairs.length() / 2;
-    }
-
   public:
-    size_t parenCount() const {
-        size_t pc = pairCount();
-        JS_ASSERT(pc);
-        return pc - 1;
+    /* Default match accessor. */
+    const MatchPairs &getMatches() const {
+        return matches;
     }
 
     JSString *getPendingInput() const { return pendingInput; }
+
     RegExpFlag getFlags() const { return flags; }
     bool multiline() const { return flags & MultilineFlag; }
 
-    size_t matchStart() const {
-        int start = get(0, 0);
-        JS_ASSERT(start >= 0);
-        return size_t(start);
-    }
-
-    size_t matchLimit() const {
-        int limit = get(0, 1);
-        JS_ASSERT(size_t(limit) >= matchStart() && limit >= 0);
-        return size_t(limit);
-    }
-
     /* Returns whether results for a non-empty match are present. */
     bool matched() const {
-        JS_ASSERT(pairCount() > 0);
-        JS_ASSERT_IF(get(0, 1) == -1, get(1, 1) == -1);
-        return get(0, 1) - get(0, 0) > 0;
+        JS_ASSERT(matches.pairCount() > 0);
+        return matches[0].limit - matches[0].start > 0;
     }
 
     void mark(JSTracer *trc) {
         if (pendingInput)
             MarkString(trc, &pendingInput, "res->pendingInput");
-        if (matchPairsInput)
-            MarkString(trc, &matchPairsInput, "res->matchPairsInput");
-    }
-
-    bool pairIsPresent(size_t pairNum) const {
-        return get(pairNum, 0) >= 0;
+        if (matchesInput)
+            MarkString(trc, &matchesInput, "res->matchesInput");
     }
 
     /* Value creators. */
 
-    bool createPendingInput(JSContext *cx, Value *out) const;
-    bool createLastMatch(JSContext *cx, Value *out) const { return makeMatch(cx, 0, 0, out); }
-    bool createLastParen(JSContext *cx, Value *out) const;
-    bool createLeftContext(JSContext *cx, Value *out) const;
-    bool createRightContext(JSContext *cx, Value *out) const;
+    bool createPendingInput(JSContext *cx, Value *out);
+    bool createLastMatch(JSContext *cx, Value *out);
+    bool createLastParen(JSContext *cx, Value *out);
+    bool createParen(JSContext *cx, size_t pairNum, Value *out);
+    bool createLeftContext(JSContext *cx, Value *out);
+    bool createRightContext(JSContext *cx, Value *out);
 
-    /* @param pairNum   Any number >= 1. */
-    bool createParen(JSContext *cx, size_t pairNum, Value *out) const {
-        JS_ASSERT(pairNum >= 1);
-        if (pairNum >= pairCount()) {
-            out->setString(cx->runtime->emptyString);
-            return true;
-        }
-        return makeMatch(cx, pairNum * 2, pairNum, out);
-    }
-
-    /* Substring creators. */
+    /* Infallible substring creators. */
 
     void getParen(size_t pairNum, JSSubString *out) const;
     void getLastMatch(JSSubString *out) const;
     void getLastParen(JSSubString *out) const;
     void getLeftContext(JSSubString *out) const;
     void getRightContext(JSSubString *out) const;
 
     /* PreserveRegExpStatics helpers. */