Bug 673188: Compile regexps lazily. (r=mrbkap)
authorChris Leary <cdleary@mozilla.com>
Fri, 07 Oct 2011 03:04:00 -0700
changeset 78732 61dd23c012ee
parent 78731 4b87afed1d1c
child 78733 d50bd6d5b097
push id21329
push usereakhgari@mozilla.com
push dateFri, 14 Oct 2011 14:37:50 +0000
treeherdermozilla-central@349f3d4b2d87 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmrbkap
bugs673188
milestone10.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 673188: Compile regexps lazily. (r=mrbkap)
js/src/builtin/RegExp.cpp
js/src/builtin/RegExp.h
js/src/ds/LifoAlloc.h
js/src/jsapi.cpp
js/src/jsprvtd.h
js/src/jsstr.cpp
js/src/methodjit/Compiler.cpp
js/src/vm/MatchPairs.h
js/src/vm/RegExpObject-inl.h
js/src/vm/RegExpObject.cpp
js/src/vm/RegExpObject.h
js/src/vm/RegExpStatics.h
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -43,66 +43,180 @@
 #include "builtin/RegExp.h"
 
 #include "vm/RegExpObject-inl.h"
 #include "vm/RegExpStatics-inl.h"
 
 using namespace js;
 using namespace js::types;
 
-/*
- * Return:
- * - The original if no escaping need be performed.
- * - A new string if escaping need be performed.
- * - NULL on error.
- */
-static JSString *
-EscapeNakedForwardSlashes(JSContext *cx, JSString *unescaped)
+class RegExpMatchBuilder
+{
+    JSContext   * const cx;
+    JSObject    * const array;
+
+    bool setProperty(JSAtom *name, Value v) {
+        return !!js_DefineProperty(cx, array, ATOM_TO_JSID(name), &v,
+                                   JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE);
+    }
+
+  public:
+    RegExpMatchBuilder(JSContext *cx, JSObject *array) : cx(cx), array(array) {}
+
+    bool append(uint32 index, Value v) {
+        JS_ASSERT(!array->getOps()->getElement);
+        return !!js_DefineElement(cx, array, index, &v, JS_PropertyStub, JS_StrictPropertyStub,
+                                  JSPROP_ENUMERATE);
+    }
+
+    bool setIndex(int index) {
+        return setProperty(cx->runtime->atomState.indexAtom, Int32Value(index));
+    }
+
+    bool setInput(JSString *str) {
+        JS_ASSERT(str);
+        return setProperty(cx->runtime->atomState.inputAtom, StringValue(str));
+    }
+};
+
+static bool
+CreateRegExpMatchResult(JSContext *cx, JSString *input, const jschar *chars, size_t length,
+                        MatchPairs *matchPairs, Value *rval)
+{
+    /*
+     * Create the (slow) result array for a match.
+     *
+     * Array contents:
+     *  0:              matched string
+     *  1..pairCount-1: paren matches
+     *  input:          input string
+     *  index:          start index for the match
+     */
+    JSObject *array = NewSlowEmptyArray(cx);
+    if (!array)
+        return false;
+
+    if (!input) {
+        input = js_NewStringCopyN(cx, chars, length);
+        if (!input)
+            return false;
+    }
+
+    RegExpMatchBuilder builder(cx, array);
+
+    for (size_t i = 0; i < matchPairs->pairCount(); ++i) {
+        MatchPair pair = matchPairs->pair(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());
+            if (!captured || !builder.append(i, StringValue(captured)))
+                return false;
+        }
+    }
+
+    if (!builder.setIndex(matchPairs->pair(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,
+                  const jschar *chars, size_t length,
+                  size_t *lastIndex, RegExpExecType type, Value *rval)
+{
+    LifoAllocScope allocScope(&cx->tempLifoAlloc());
+    MatchPairs *matchPairs = NULL;
+    RegExpRunStatus status = re->execute(cx, chars, length, lastIndex, allocScope, &matchPairs);
+
+    switch (status) {
+      case RegExpRunStatus_Error:
+        return false;
+      case RegExpRunStatus_Success_NotFound:
+        *rval = NullValue();
+        return true;
+      default:
+        JS_ASSERT(status == RegExpRunStatus_Success);
+        JS_ASSERT(matchPairs);
+    }
+
+    if (res)
+        res->updateFromMatchPairs(cx, input, matchPairs);
+
+    *lastIndex = matchPairs->pair(0).limit;
+
+    if (type == RegExpTest) {
+        *rval = BooleanValue(true);
+        return true;
+    }
+
+    return CreateRegExpMatchResult(cx, input, chars, length, matchPairs, rval);
+}
+
+bool
+js::ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpPrivate *rep, JSLinearString *input,
+                  const jschar *chars, size_t length,
+                  size_t *lastIndex, RegExpExecType type, Value *rval)
+{
+    return ExecuteRegExpImpl(cx, res, rep, input, chars, length, lastIndex, type, rval);
+}
+
+bool
+js::ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpObject *reobj, JSLinearString *input,
+                  const jschar *chars, size_t length,
+                  size_t *lastIndex, RegExpExecType type, Value *rval)
+{
+    return ExecuteRegExpImpl(cx, res, reobj, input, chars, length, lastIndex, type, rval);
+}
+
+/* Note: returns the original if no escaping need be performed. */
+static JSLinearString *
+EscapeNakedForwardSlashes(JSContext *cx, JSLinearString *unescaped)
 {
     size_t oldLen = unescaped->length();
-    const jschar *oldChars = unescaped->getChars(cx);
-    if (!oldChars)
-        return NULL;
+    const jschar *oldChars = unescaped->chars();
+
     JS::Anchor<JSString *> anchor(unescaped);
 
-    js::Vector<jschar, 128> newChars(cx);
+    /* We may never need to use |sb|. Start using it lazily. */
+    StringBuffer sb(cx);
+
     for (const jschar *it = oldChars; it < oldChars + oldLen; ++it) {
         if (*it == '/' && (it == oldChars || it[-1] != '\\')) {
-            if (!newChars.length()) {
-                if (!newChars.reserve(oldLen + 1))
+            /* There's a forward slash that needs escaping. */
+            if (sb.empty()) {
+                /* This is the first one we've seen, copy everything up to this point. */
+                if (!sb.reserve(oldLen + 1))
                     return NULL;
-                newChars.infallibleAppend(oldChars, size_t(it - oldChars));
+                sb.infallibleAppend(oldChars, size_t(it - oldChars));
             }
-            if (!newChars.append('\\'))
+            if (!sb.append('\\'))
                 return NULL;
         }
 
-        if (!newChars.empty() && !newChars.append(*it))
+        if (!sb.empty() && !sb.append(*it))
             return NULL;
     }
 
-    if (newChars.empty())
-        return unescaped;
-
-    size_t len = newChars.length();
-    if (!newChars.append('\0'))
-        return NULL;
-    jschar *chars = newChars.extractRawBuffer();
-    JSString *escaped = js_NewString(cx, chars, len);
-    if (!escaped)
-        cx->free_(chars);
-    return escaped;
+    return sb.empty() ? unescaped : sb.finishString();
 }
 
 static bool
 ResetRegExpObjectWithStatics(JSContext *cx, RegExpObject *reobj,
-                             JSString *str, RegExpFlag flags = RegExpFlag(0))
+                             JSLinearString *str, RegExpFlag flags = RegExpFlag(0))
 {
     flags = RegExpFlag(flags | cx->regExpStatics()->getFlags());
-    return ResetRegExpObject(cx, reobj, str, flags);
+    return reobj->reset(cx, str, flags);
 }
 
 /*
  * Compile a new |RegExpPrivate| for the |RegExpObject|.
  *
  * Per ECMAv5 15.10.4.1, we act on combinations of (pattern, flags) as
  * arguments:
  *
@@ -120,61 +234,62 @@ CompileRegExpObject(JSContext *cx, RegEx
         *rval = ObjectValue(*obj);
         return true;
     }
 
     Value sourceValue = argv[0];
     if (ValueIsRegExp(sourceValue)) {
         /*
          * If we get passed in a |RegExpObject| source we return a new
-         * object with the same |RegExpPrivate|.
+         * object with the same source/flags.
          *
          * Note: the regexp static flags are not taken into consideration here.
          */
         JSObject &sourceObj = sourceValue.toObject();
         if (argc >= 2 && !argv[1].isUndefined()) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NEWREGEXP_FLAGGED);
             return false;
         }
 
-        RegExpPrivate *rep = sourceObj.asRegExp()->getPrivate();
-        if (!rep)
-            return false;
-
-        rep->incref(cx);
-        if (!ResetRegExpObject(cx, obj, AlreadyIncRefed<RegExpPrivate>(rep)))
+        if (!obj->reset(cx, sourceObj.asRegExp()))
             return false;
         *rval = ObjectValue(*obj);
         return true;
     }
 
-    JSString *sourceStr;
+    JSLinearString *sourceStr;
     if (sourceValue.isUndefined()) {
         sourceStr = cx->runtime->emptyString;
     } else {
         /* Coerce to string and compile. */
-        sourceStr = js_ValueToString(cx, sourceValue);
+        JSString *str = js_ValueToString(cx, sourceValue);
+        if (!str)
+            return false;
+        sourceStr = str->ensureLinear(cx);
         if (!sourceStr)
             return false;
     }
 
     RegExpFlag flags = RegExpFlag(0);
     if (argc > 1 && !argv[1].isUndefined()) {
         JSString *flagStr = js_ValueToString(cx, argv[1]);
         if (!flagStr)
             return false;
         argv[1].setString(flagStr);
         if (!ParseRegExpFlags(cx, flagStr, &flags))
             return false;
     }
 
-    JSString *escapedSourceStr = EscapeNakedForwardSlashes(cx, sourceStr);
+    JSLinearString *escapedSourceStr = EscapeNakedForwardSlashes(cx, sourceStr);
     if (!escapedSourceStr)
         return false;
 
+    if (!RegExpPrivateCode::checkSyntax(cx, NULL, escapedSourceStr))
+        return false;
+
     if (!ResetRegExpObjectWithStatics(cx, obj, escapedSourceStr, flags))
         return false;
     *rval = ObjectValue(*obj);
     return true;
 }
 
 static JSBool
 regexp_compile(JSContext *cx, uintN argc, Value *vp)
@@ -182,19 +297,17 @@ regexp_compile(JSContext *cx, uintN argc
     CallArgs args = CallArgsFromVp(argc, vp);
 
     bool ok;
     JSObject *obj = NonGenericMethodGuard(cx, args, regexp_compile, &RegExpClass, &ok);
     if (!obj)
         return ok;
 
     RegExpObject *reobj = obj->asRegExp();
-    ok = CompileRegExpObject(cx, reobj, args.length(), args.array(), &args.rval());
-    JS_ASSERT_IF(ok, reobj->getPrivate());
-    return ok;
+    return CompileRegExpObject(cx, reobj, args.length(), args.array(), &args.rval());
 }
 
 static JSBool
 regexp_construct(JSContext *cx, uintN argc, Value *vp)
 {
     Value *argv = JS_ARGV(cx, vp);
 
     if (!IsConstructing(vp)) {
@@ -208,26 +321,20 @@ regexp_construct(JSContext *cx, uintN ar
             return true;
         }
     }
 
     JSObject *obj = NewBuiltinClassInstance(cx, &RegExpClass);
     if (!obj)
         return false;
 
-    PreInitRegExpObject pireo(obj);
-    RegExpObject *reobj = pireo.get();
+    if (!CompileRegExpObject(cx, obj->asRegExp(), argc, argv, &JS_RVAL(cx, vp)))
+        return false;
 
-    if (!CompileRegExpObject(cx, reobj, argc, argv, &JS_RVAL(cx, vp))) {
-        pireo.fail();
-        return false;
-    }
-
-    pireo.succeed();
-    *vp = ObjectValue(*reobj);
+    *vp = ObjectValue(*obj);
     return true;
 }
 
 static JSBool
 regexp_toString(JSContext *cx, uintN argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
@@ -351,40 +458,19 @@ js_InitRegExpClass(JSContext *cx, JSObje
     JS_ASSERT(obj->isNative());
 
     GlobalObject *global = obj->asGlobal();
 
     JSObject *proto = global->createBlankPrototype(cx, &RegExpClass);
     if (!proto)
         return NULL;
 
-    {
-        AlreadyIncRefed<RegExpPrivate> rep =
-          RegExpPrivate::create(cx, cx->runtime->emptyString, RegExpFlag(0), NULL);
-        if (!rep)
-            return NULL;
-
-        /*
-         * Associate the empty regular expression with |RegExp.prototype|, and define
-         * the initial non-method properties of any regular expression instance.
-         * These must be added before methods to preserve slot layout.
-         */
-#ifdef DEBUG
-        assertSameCompartment(cx, proto, rep->compartment);
-#endif
-
-        PreInitRegExpObject pireo(proto);
-        RegExpObject *reproto = pireo.get();
-        if (!ResetRegExpObject(cx, reproto, rep)) {
-            pireo.fail();
-            return NULL;
-        }
-
-        pireo.succeed();
-    }
+    RegExpObject *reproto = proto->asRegExp();
+    if (!reproto->reset(cx, cx->runtime->emptyString, RegExpFlag(0)))
+        return NULL;
 
     if (!DefinePropertiesAndBrand(cx, proto, NULL, regexp_methods))
         return NULL;
 
     JSFunction *ctor = global->createConstructor(cx, regexp_construct, &RegExpClass,
                                                  CLASS_ATOM(cx, RegExp), 2);
     if (!ctor)
         return NULL;
@@ -414,73 +500,78 @@ js_InitRegExpClass(JSContext *cx, JSObje
 }
 
 /*
  * 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 JSBool
+static bool
 ExecuteRegExp(JSContext *cx, Native native, uintN argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     /* Step 1. */
     bool ok;
     JSObject *obj = NonGenericMethodGuard(cx, args, native, &RegExpClass, &ok);
     if (!obj)
         return ok;
 
     RegExpObject *reobj = obj->asRegExp();
-    RegExpPrivate *re = reobj->getPrivate();
-    if (!re)
+    RegExpPrivate *rep = reobj->getOrCreatePrivate(cx);
+    if (!rep)
         return true;
 
     /*
      * Code execution under this call could swap out the guts of |reobj|, so we
      * have to take a defensive refcount here.
      */
-    AutoRefCount<RegExpPrivate> arc(cx, NeedsIncRef<RegExpPrivate>(re));
+    AutoRefCount<RegExpPrivate> arc(cx, NeedsIncRef<RegExpPrivate>(rep));
     RegExpStatics *res = cx->regExpStatics();
 
     /* Step 2. */
-    JSString *input = js_ValueToString(cx, args.length() > 0 ?  args[0] : UndefinedValue());    
+    JSString *input = js_ValueToString(cx, args.length() > 0 ?  args[0] : UndefinedValue());
     if (!input)
         return false;
-    
+
     /* Step 3. */
+    JSLinearString *linearInput = input->ensureLinear(cx);
+    const jschar *chars = linearInput->chars();
     size_t length = input->length();
 
     /* Step 4. */
     const Value &lastIndex = reobj->getLastIndex();
 
     /* Step 5. */
     jsdouble i;
     if (!ToInteger(cx, lastIndex, &i))
         return false;
 
     /* Steps 6-7 (with sticky extension). */
-    if (!re->global() && !re->sticky())
+    if (!rep->global() && !rep->sticky())
         i = 0;
 
     /* Step 9a. */
     if (i < 0 || i > length) {
         reobj->zeroLastIndex();
         args.rval() = NullValue();
         return true;
     }
 
     /* Steps 8-21. */
+    RegExpExecType execType = native == regexp_test ? RegExpTest : RegExpExec;
     size_t lastIndexInt(i);
-    if (!re->execute(cx, res, input, &lastIndexInt, native == regexp_test, &args.rval()))
+    if (!ExecuteRegExp(cx, res, rep, linearInput, chars, length, &lastIndexInt, execType,
+                       &args.rval())) {
         return false;
+    }
 
     /* Step 11 (with sticky extension). */
-    if (re->global() || (!args.rval().isNull() && re->sticky())) {
+    if (rep->global() || (!args.rval().isNull() && rep->sticky())) {
         if (args.rval().isNull())
             reobj->zeroLastIndex();
         else
             reobj->setLastIndex(lastIndexInt);
     }
 
     return true;
 }
--- a/js/src/builtin/RegExp.h
+++ b/js/src/builtin/RegExp.h
@@ -48,16 +48,31 @@ js_InitRegExpClass(JSContext *cx, JSObje
 
 /*
  * The following builtin natives are extern'd for pointer comparison in
  * other parts of the engine.
  */
 
 namespace js {
 
+/* 
+ * |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, JSLinearString *input,
+              const jschar *chars, size_t length,
+              size_t *lastIndex, RegExpExecType type, Value *rval);
+
+bool
+ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpPrivate *rep, JSLinearString *input,
+              const jschar *chars, size_t length,
+              size_t *lastIndex, RegExpExecType type, Value *rval);
+
 extern JSBool
 regexp_exec(JSContext *cx, uintN argc, Value *vp);
 
 extern JSBool
 regexp_test(JSContext *cx, uintN argc, Value *vp);
 
 } /* namespace js */
 
--- a/js/src/ds/LifoAlloc.h
+++ b/js/src/ds/LifoAlloc.h
@@ -319,16 +319,20 @@ class LifoAllocScope {
         mark = lifoAlloc->mark();
     }
 
     ~LifoAllocScope() {
         if (shouldRelease)
             lifoAlloc->release(mark);
     }
 
+    LifoAlloc &alloc() {
+        return *lifoAlloc;
+    }
+
     void releaseEarly() {
         JS_ASSERT(shouldRelease);
         lifoAlloc->release(mark);
         shouldRelease = false;
     }
 };
 
 } /* namespace js */
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -5956,26 +5956,19 @@ JS_ClearRegExpStatics(JSContext *cx, JSO
 }
 
 JS_PUBLIC_API(JSBool)
 JS_ExecuteRegExp(JSContext *cx, JSObject *obj, JSObject *reobj, jschar *chars, size_t length,
                  size_t *indexp, JSBool test, jsval *rval)
 {
     CHECK_REQUEST(cx);
 
-    RegExpPrivate *rep = reobj->asRegExp()->getPrivate();
-    if (!rep)
-        return false;
-
-    JSString *str = js_NewStringCopyN(cx, chars, length);
-    if (!str)
-        return false;
-
     RegExpStatics *res = obj->asGlobal()->getRegExpStatics();
-    return rep->execute(cx, res, str, indexp, test, rval);
+    return ExecuteRegExp(cx, res, reobj->asRegExp(), NULL, chars, length,
+                         indexp, test ? RegExpTest : RegExpExec, rval);
 }
 
 JS_PUBLIC_API(JSObject *)
 JS_NewRegExpObjectNoStatics(JSContext *cx, char *bytes, size_t length, uintN flags)
 {
     CHECK_REQUEST(cx);
     jschar *chars = InflateString(cx, bytes, &length);
     if (!chars)
@@ -5993,25 +5986,18 @@ JS_NewUCRegExpObjectNoStatics(JSContext 
 }
 
 JS_PUBLIC_API(JSBool)
 JS_ExecuteRegExpNoStatics(JSContext *cx, JSObject *obj, jschar *chars, size_t length,
                           size_t *indexp, JSBool test, jsval *rval)
 {
     CHECK_REQUEST(cx);
 
-    RegExpPrivate *rep = obj->asRegExp()->getPrivate();
-    if (!rep)
-        return false;
-
-    JSString *str = js_NewStringCopyN(cx, chars, length);
-    if (!str)
-        return false;
-
-    return rep->executeNoStatics(cx, str, indexp, test, rval);
+    return ExecuteRegExp(cx, NULL, obj->asRegExp(), NULL, chars, length, indexp,
+                         test ? RegExpTest : RegExpExec, rval);
 }
 
 JS_PUBLIC_API(JSBool)
 JS_ObjectIsRegExp(JSContext *cx, JSObject *obj)
 {
     JS_ASSERT(obj);
     return obj->isRegExp();
 }
--- a/js/src/jsprvtd.h
+++ b/js/src/jsprvtd.h
@@ -123,27 +123,35 @@ class JSAtom;
 struct JSDefinition;
 class JSWrapper;
 
 namespace js {
 
 struct ArgumentsData;
 struct Class;
 
+class RegExpObject;
 class RegExpPrivate;
 class RegExpStatics;
+class MatchPairs;
 
 enum RegExpFlag
 {
     IgnoreCaseFlag  = JS_BIT(0),
     GlobalFlag      = JS_BIT(1),
     MultilineFlag   = JS_BIT(2),
     StickyFlag      = JS_BIT(3)
 };
 
+enum RegExpExecType
+{
+    RegExpExec,
+    RegExpTest
+};
+
 class AutoStringRooter;
 class ExecuteArgsGuard;
 class InvokeFrameGuard;
 class InvokeArgsGuard;
 class StringBuffer;
 class TraceRecorder;
 struct TraceMonitor;
 
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -67,16 +67,17 @@
 #include "jsnum.h"
 #include "jsobj.h"
 #include "jsopcode.h"
 #include "jsprobes.h"
 #include "jsscope.h"
 #include "jsstr.h"
 #include "jsversion.h"
 
+#include "builtin/RegExp.h"
 #include "vm/GlobalObject.h"
 #include "vm/RegExpObject.h"
 
 #include "jsinferinlines.h"
 #include "jsobjinlines.h"
 #include "jsautooplen.h"        // generated headers last
 
 #include "vm/RegExpObject-inl.h"
@@ -512,31 +513,31 @@ js_str_toString(JSContext *cx, uintN arg
 
     args.rval().setString(str);
     return true;
 }
 
 /*
  * Java-like string native methods.
  */
- 
+
 JS_ALWAYS_INLINE bool
 ValueToIntegerRange(JSContext *cx, const Value &v, int32 *out)
 {
     if (v.isInt32()) {
         *out = v.toInt32();
     } else {
         double d;
         if (!ToInteger(cx, v, &d))
             return false;
         if (d > INT32_MAX)
             *out = INT32_MAX;
         else if (d < INT32_MIN)
             *out = INT32_MIN;
-        else 
+        else
             *out = int32(d);
     }
 
     return true;
 }
 
 static JSBool
 str_substring(JSContext *cx, uintN argc, Value *vp)
@@ -1248,53 +1249,59 @@ class FlatMatch
     const jschar    *pat;
     size_t          patlen;
     int32           match_;
 
     friend class RegExpGuard;
 
   public:
     FlatMatch() : patstr(NULL) {} /* Old GCC wants this initialization. */
-    JSString *pattern() const { return patstr; }
+    JSLinearString *pattern() const { return patstr; }
     size_t patternLength() const { return patlen; }
 
     /*
      * Note: The match is -1 when the match is performed successfully,
      * but no match is found.
      */
     int32 match() const { return match_; }
 };
 
-/* A regexp and optional associated object. */
 class RegExpPair
 {
     AutoRefCount<RegExpPrivate> rep_;
-    JSObject                    *reobj_;
+    RegExpObject                *reobj_;
 
     explicit RegExpPair(RegExpPair &);
+    void operator=(const RegExpPair &);
 
   public:
     explicit RegExpPair(JSContext *cx) : rep_(cx) {}
 
-    void reset(JSObject &obj) {
-        reobj_ = &obj;
-        RegExpPrivate *rep = reobj_->asRegExp()->getPrivate();
-        JS_ASSERT(rep);
+    bool resetWithObject(JSContext *cx, RegExpObject *reobj) {
+        reobj_ = reobj;
+        RegExpPrivate *rep = reobj_->asRegExp()->getOrCreatePrivate(cx);
+        if (!rep)
+            return false;
         rep_.reset(NeedsIncRef<RegExpPrivate>(rep));
+        return true;
     }
 
-    void reset(AlreadyIncRefed<RegExpPrivate> rep) {
+    void resetWithPrivate(AlreadyIncRefed<RegExpPrivate> rep) {
         reobj_ = NULL;
         rep_.reset(rep);
     }
 
-    /* Note: May be null. */
-    JSObject *reobj() const { return reobj_; }
-    bool hasRegExp() const { return !rep_.null(); }
-    RegExpPrivate &re() const { JS_ASSERT(hasRegExp()); return *rep_; }
+    bool null() const { return rep_.null(); }
+
+    RegExpObject *reobj() const { return reobj_; }
+
+    RegExpPrivate *getPrivate() const {
+        JS_ASSERT(!null());
+        return rep_.get();
+    }
 };
 
 /*
  * RegExpGuard factors logic out of String regexp operations.
  *
  * @param optarg    Indicates in which argument position RegExp
  *                  flags will be found, if present. This is a Mozilla
  *                  extension and not part of any ECMA spec.
@@ -1309,17 +1316,17 @@ class RegExpGuard
     FlatMatch   fm;
 
     /*
      * Upper bound on the number of characters we are willing to potentially
      * waste on searching for RegExp meta-characters.
      */
     static const size_t MAX_FLAT_PAT_LEN = 256;
 
-    static JSString *flattenPattern(JSContext *cx, JSLinearString *patstr) {
+    static JSLinearString *flattenPattern(JSContext *cx, JSLinearString *patstr) {
         StringBuffer sb(cx);
         if (!sb.reserve(patstr->length()))
             return NULL;
 
         static const jschar ESCAPE_CHAR = '\\';
         const jschar *chars = patstr->chars();
         size_t len = patstr->length();
         for (const jschar *it = chars; it != chars + len; ++it) {
@@ -1338,23 +1345,23 @@ class RegExpGuard
     explicit RegExpGuard(JSContext *cx) : cx(cx), rep(cx) {}
     ~RegExpGuard() {}
 
     /* init must succeed in order to call tryFlatMatch or normalizeRegExp. */
     bool
     init(uintN argc, Value *vp, bool convertVoid = false)
     {
         if (argc != 0 && ValueIsRegExp(vp[2])) {
-            rep.reset(vp[2].toObject());
+            rep.resetWithObject(cx, vp[2].toObject().asRegExp());
         } else {
             if (convertVoid && (argc == 0 || vp[2].isUndefined())) {
                 fm.patstr = cx->runtime->emptyString;
                 return true;
             }
-            
+
             fm.patstr = ArgToRootedString(cx, argc, vp, 0);
             if (!fm.patstr)
                 return false;
         }
         return true;
     }
 
     /*
@@ -1365,17 +1372,17 @@ class RegExpGuard
      * @return                  Whether flat matching could be used.
      *
      * N.B. tryFlatMatch returns NULL on OOM, so the caller must check cx->isExceptionPending().
      */
     const FlatMatch *
     tryFlatMatch(JSContext *cx, JSString *textstr, uintN optarg, uintN argc,
                  bool checkMetaChars = true)
     {
-        if (rep.hasRegExp())
+        if (!rep.null())
             return NULL;
 
         fm.pat = fm.patstr->chars();
         fm.patlen = fm.patstr->length();
 
         if (optarg < argc)
             return NULL;
 
@@ -1398,56 +1405,56 @@ class RegExpGuard
         }
         return &fm;
     }
 
     /* If the pattern is not already a regular expression, make it so. */
     const RegExpPair *
     normalizeRegExp(bool flat, uintN optarg, uintN argc, Value *vp)
     {
-        if (rep.hasRegExp())
+        if (!rep.null())
             return &rep;
 
         /* Build RegExp from pattern string. */
         JSString *opt;
         if (optarg < argc) {
             opt = js_ValueToString(cx, vp[2 + optarg]);
             if (!opt)
                 return NULL;
         } else {
             opt = NULL;
         }
 
-        JSString *patstr;
+        JSLinearString *patstr;
         if (flat) {
             patstr = flattenPattern(cx, fm.patstr);
             if (!patstr)
                 return NULL;
         } else {
             patstr = fm.patstr;
         }
         JS_ASSERT(patstr);
 
-        AlreadyIncRefed<RegExpPrivate> re = RegExpPrivate::createFlagged(cx, patstr, opt, NULL);
+        AlreadyIncRefed<RegExpPrivate> re = RegExpPrivate::create(cx, patstr, opt, NULL);
         if (!re)
             return NULL;
-        rep.reset(re);
+        rep.resetWithPrivate(re);
         return &rep;
     }
 
 #if DEBUG
-    bool hasRegExpPair() const { return rep.hasRegExp(); }
+    bool hasRegExpPair() const { return !rep.null(); }
 #endif
 };
 
-/* js_ExecuteRegExp indicates success in two ways, based on the 'test' flag. */
+/* ExecuteRegExp indicates success in two ways, based on the 'test' flag. */
 static JS_ALWAYS_INLINE bool
-Matched(bool test, const Value &v)
+Matched(RegExpExecType type, const Value &v)
 {
-    return test ? v.isTrue() : !v.isNull();
+    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.
  */
@@ -1458,43 +1465,50 @@ enum MatchControlFlags {
 
    MATCH_ARGS    = TEST_GLOBAL_BIT,
    MATCHALL_ARGS = CALLBACK_ON_SINGLE_BIT,
    REPLACE_ARGS  = TEST_GLOBAL_BIT | TEST_SINGLE_BIT | CALLBACK_ON_SINGLE_BIT
 };
 
 /* Factor out looping and matching logic. */
 static bool
-DoMatch(JSContext *cx, RegExpStatics *res, JSString *str, const RegExpPair &rep,
+DoMatch(JSContext *cx, RegExpStatics *res, JSString *str, const RegExpPair &regExpPair,
         DoMatchCallback callback, void *data, MatchControlFlags flags, Value *rval)
 {
-    RegExpPrivate &re = rep.re();
-    if (re.global()) {
+    RegExpPrivate *rep = regExpPair.getPrivate();
+    JSLinearString *linearStr = str->ensureLinear(cx);
+    if (!linearStr)
+        return false;
+    const jschar *chars = linearStr->chars();
+    size_t length = linearStr->length();
+
+    if (rep->global()) {
         /* global matching ('g') */
-        bool testGlobal = flags & TEST_GLOBAL_BIT;
-        if (rep.reobj())
-            rep.reobj()->asRegExp()->zeroLastIndex();
+        RegExpExecType type = flags & TEST_GLOBAL_BIT ? RegExpTest : RegExpExec;
+        if (RegExpObject *reobj = regExpPair.reobj())
+            reobj->zeroLastIndex();
+
         for (size_t count = 0, i = 0, length = str->length(); i <= length; ++count) {
-            if (!re.execute(cx, res, str, &i, testGlobal, rval))
+            if (!ExecuteRegExp(cx, res, rep, linearStr, chars, length, &i, type, rval))
                 return false;
-            if (!Matched(testGlobal, *rval))
+            if (!Matched(type, *rval))
                 break;
             if (!callback(cx, res, count, data))
                 return false;
             if (!res->matched())
                 ++i;
         }
     } else {
         /* single match */
-        bool testSingle = !!(flags & TEST_SINGLE_BIT),
-             callbackOnSingle = !!(flags & CALLBACK_ON_SINGLE_BIT);
+        RegExpExecType type = (flags & TEST_SINGLE_BIT) ? RegExpTest : RegExpExec;
+        bool callbackOnSingle = !!(flags & CALLBACK_ON_SINGLE_BIT);
         size_t i = 0;
-        if (!re.execute(cx, res, str, &i, testSingle, rval))
+        if (!ExecuteRegExp(cx, res, rep, linearStr, chars, length, &i, type, rval))
             return false;
-        if (callbackOnSingle && Matched(testSingle, *rval) && !callback(cx, res, 0, data))
+        if (callbackOnSingle && Matched(type, *rval) && !callback(cx, res, 0, data))
             return false;
     }
     return true;
 }
 
 static bool
 BuildFlatMatchArray(JSContext *cx, JSString *textstr, const FlatMatch &fm, Value *vp)
 {
@@ -1539,17 +1553,17 @@ MatchCallback(JSContext *cx, RegExpStati
 }
 
 JSBool
 js::str_match(JSContext *cx, uintN argc, Value *vp)
 {
     JSString *str = ThisToStringForStringProto(cx, vp);
     if (!str)
         return false;
-    
+
     RegExpGuard g(cx);
     if (!g.init(argc, vp, true))
         return false;
     if (const FlatMatch *fm = g.tryFlatMatch(cx, str, 1, argc))
         return BuildFlatMatchArray(cx, str, *fm, vp);
     if (cx->isExceptionPending())  /* from tryFlatMatch */
         return false;
 
@@ -1559,17 +1573,17 @@ js::str_match(JSContext *cx, uintN argc,
 
     AutoObjectRooter array(cx);
     MatchArgType arg = array.addr();
     RegExpStatics *res = cx->regExpStatics();
     Value rval;
     if (!DoMatch(cx, res, str, *rep, MatchCallback, arg, MATCH_ARGS, &rval))
         return false;
 
-    if (rep->re().global())
+    if (rep->getPrivate()->global())
         vp->setObjectOrNull(array.object());
     else
         *vp = rval;
     return true;
 }
 
 JSBool
 js::str_search(JSContext *cx, uintN argc, Value *vp)
@@ -1582,24 +1596,30 @@ js::str_search(JSContext *cx, uintN argc
     if (!g.init(argc, vp, true))
         return false;
     if (const FlatMatch *fm = g.tryFlatMatch(cx, str, 1, argc)) {
         vp->setInt32(fm->match());
         return true;
     }
     if (cx->isExceptionPending())  /* from tryFlatMatch */
         return false;
-    
+
     const RegExpPair *rep = g.normalizeRegExp(false, 1, argc, vp);
     if (!rep)
         return false;
 
+    JSLinearString *linearStr = str->ensureLinear(cx);
+    if (!linearStr)
+        return false;
+    const jschar *chars = linearStr->chars();
+    size_t length = linearStr->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;
-    if (!rep->re().execute(cx, res, str, &i, true, vp))
+    if (!ExecuteRegExp(cx, res, rep->getPrivate(), linearStr, chars, length, &i, RegExpTest, vp))
         return false;
 
     if (vp->isTrue())
         vp->setInt32(res->matchStart());
     else
         vp->setInt32(-1);
     return true;
 }
@@ -1652,17 +1672,17 @@ InterpretDollar(JSContext *cx, RegExpSta
         }
         if (num == 0)
             return false;
 
         *skip = cp - dp;
 
         JS_ASSERT(num <= res->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;
     }
 
     *skip = 2;
@@ -1810,17 +1830,17 @@ FindReplaceLength(JSContext *cx, RegExpS
         } else {
             dp++;
         }
     }
     *sizep = replen;
     return true;
 }
 
-/* 
+/*
  * Precondition: |rdata.sb| already has necessary growth space reserved (as
  * derived from FindReplaceLength).
  */
 static void
 DoReplace(JSContext *cx, RegExpStatics *res, ReplaceData &rdata)
 {
     JSLinearString *repstr = rdata.repstr;
     const jschar *cp;
@@ -2207,17 +2227,17 @@ js::str_replace(JSContext *cx, uintN arg
     if (fm->match() < 0) {
         vp->setString(rdata.str);
         return true;
     }
 
     if (rdata.lambda)
         return str_replace_flat_lambda(cx, argc, vp, rdata, *fm);
 
-    /* 
+    /*
      * Note: we could optimize the text.length == pattern.length case if we wanted,
      * even in the presence of dollar metachars.
      */
     if (rdata.dollar)
         return BuildDollarReplacement(cx, rdata.str, rdata.repstr, rdata.dollar, *fm, vp);
 
     return BuildFlatReplacement(cx, rdata.str, rdata.repstr, *fm, vp);
 }
@@ -2378,30 +2398,32 @@ SplitHelper(JSContext *cx, JSLinearStrin
  * The SplitMatch operation from ES5 15.5.4.14 is implemented using different
  * matchers for regular expression and string separators.
  *
  * The algorithm differs from the spec in that the matchers return the next
  * index at which a match happens.
  */
 class SplitRegExpMatcher {
     RegExpStatics *res;
-    RegExpPrivate *re;
+    RegExpPrivate *rep;
 
   public:
     static const bool returnsCaptures = true;
-    SplitRegExpMatcher(RegExpPrivate *re, RegExpStatics *res) : res(res), re(re) {}
+    SplitRegExpMatcher(RegExpPrivate *rep, RegExpStatics *res) : res(res), rep(rep) {}
 
     inline bool operator()(JSContext *cx, JSLinearString *str, size_t index,
                            SplitMatchResult *result) {
         Value rval
 #ifdef __GNUC__ /* quell GCC overwarning */
             = UndefinedValue()
 #endif
         ;
-        if (!re->execute(cx, res, str, &index, true, &rval))
+        const jschar *chars = str->chars();
+        size_t length = str->length();
+        if (!ExecuteRegExp(cx, res, rep, str, chars, length, &index, RegExpTest, &rval))
             return false;
         if (!rval.isTrue()) {
             result->setFailure();
             return true;
         }
         JSSubString sep;
         res->getLastMatch(&sep);
 
@@ -2460,17 +2482,19 @@ js::str_split(JSContext *cx, uintN argc,
     }
 
     /* Step 8. */
     RegExpPrivate *re = NULL;
     JSLinearString *sepstr = NULL;
     bool sepUndefined = (argc == 0 || vp[2].isUndefined());
     if (!sepUndefined) {
         if (ValueIsRegExp(vp[2])) {
-            re = vp[2].toObject().asRegExp()->getPrivate();
+            re = vp[2].toObject().asRegExp()->getOrCreatePrivate(cx);
+            if (!re)
+                return false;
         } else {
             JSString *sep = js_ValueToString(cx, vp[2]);
             if (!sep)
                 return false;
             vp[2].setString(sep);
 
             sepstr = sep->ensureLinear(cx);
             if (!sepstr)
@@ -2541,17 +2565,17 @@ str_substr(JSContext *cx, uintN argc, Va
             begin += length; /* length + INT_MIN will always be less then 0 */
             if (begin < 0)
                 begin = 0;
         }
 
         if (argc == 1 || vp[3].isUndefined()) {
             len = length - begin;
         } else {
-            if (!ValueToIntegerRange(cx, vp[3], &len))  
+            if (!ValueToIntegerRange(cx, vp[3], &len))
                 return false;
 
             if (len <= 0) {
                 str = cx->runtime->emptyString;
                 goto out;
             }
 
             if (uint32(length) < uint32(begin + len))
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -6588,19 +6588,19 @@ mjit::Compiler::jsop_regexp()
                                        types::OBJECT_FLAG_REGEXP_FLAGS_SET)) {
         prepareStubCall(Uses(0));
         masm.move(ImmPtr(obj), Registers::ArgReg1);
         INLINE_STUBCALL(stubs::RegExp, REJOIN_FALLTHROUGH);
         frame.pushSynced(JSVAL_TYPE_OBJECT);
         return;
     }
 
-    RegExpPrivate *regexp = static_cast<RegExpObject *>(obj)->getPrivate();
-
-    DebugOnly<uint32> origFlags = regexp->getFlags();
+    RegExpObject *reobj = obj->asRegExp();
+
+    DebugOnly<uint32> origFlags = reobj->getFlags();
     DebugOnly<uint32> staticsFlags = res->getFlags();
     JS_ASSERT((origFlags & staticsFlags) == staticsFlags);
 
     /*
      * JS semantics require regular expression literals to create different
      * objects every time they execute. We only need to do this cloning if the
      * script could actually observe the effect of such cloning, by getting
      * or setting properties on it. Particular RegExp and String natives take
@@ -6642,20 +6642,16 @@ mjit::Compiler::jsop_regexp()
     Jump emptyFreeList = masm.getNewObject(cx, result, obj);
 
     stubcc.linkExit(emptyFreeList, Uses(0));
     stubcc.leave();
 
     stubcc.masm.move(ImmPtr(obj), Registers::ArgReg1);
     OOL_STUBCALL(stubs::RegExp, REJOIN_FALLTHROUGH);
 
-    /* Bump the refcount on the wrapped RegExp. */
-    size_t *refcount = regexp->addressOfRefCount();
-    masm.add32(Imm32(1), AbsoluteAddress(refcount));
-
     frame.pushTypedPayload(JSVAL_TYPE_OBJECT, result);
 
     stubcc.rejoin(Changes(1));
 }
 
 bool
 mjit::Compiler::startLoop(jsbytecode *head, Jump entry, jsbytecode *entryTarget)
 {
new file mode 100644
--- /dev/null
+++ b/js/src/vm/MatchPairs.h
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sw=4 et tw=99 ft=cpp:
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla SpiderMonkey JavaScript code.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Chris Leary <cdleary@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef MatchPairs_h__
+#define MatchPairs_h__
+
+/*
+ * RegExp match results are succinctly represented by pairs of integer
+ * indices delimiting (start, limit] segments of the input string.
+ *
+ * The pair count for a given RegExp match is the capturing parentheses
+ * count plus one for the "0 capturing paren" whole text match.
+ */
+
+namespace js {
+
+struct MatchPair
+{
+    int start;
+    int limit;
+
+    MatchPair(int start, int limit) : start(start), limit(limit) {}
+
+    size_t length() const {
+        JS_ASSERT(!isUndefined());
+        return limit - start;
+    }
+
+    bool isUndefined() const {
+        return start == -1;
+    }
+
+    void check() const {
+        JS_ASSERT(limit >= start);
+        JS_ASSERT_IF(!isUndefined(), start >= 0);
+    }
+};
+
+class MatchPairs
+{
+    size_t  pairCount_;
+    int     buffer_[1];
+
+    explicit MatchPairs(size_t pairCount) : pairCount_(pairCount) {
+        initPairValues();
+    }
+
+    void initPairValues() {
+        for (int *it = buffer_; it < buffer_ + 2 * pairCount_; ++it)
+            *it = -1;
+    }
+
+    static size_t calculateSize(size_t backingPairCount) {
+        return sizeof(MatchPairs) - sizeof(int) + sizeof(int) * backingPairCount;
+    }
+
+    int *buffer() { return buffer_; }
+
+    friend class RegExpPrivate;
+
+  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);
+
+    size_t pairCount() const { return pairCount_; }
+
+    MatchPair pair(size_t i) {
+        JS_ASSERT(i < pairCount());
+        return MatchPair(buffer_[2 * i], buffer_[2 * i + 1]);
+    }
+
+    void displace(size_t amount) {
+        if (!amount)
+            return;
+
+        for (int *it = buffer_; it < buffer_ + 2 * pairCount_; ++it)
+            *it = *it < 0 ? -1 : *it + amount;
+    }
+
+    inline void checkAgainst(size_t length);
+};
+
+} /* namespace js */
+
+#endif
--- a/js/src/vm/RegExpObject-inl.h
+++ b/js/src/vm/RegExpObject-inl.h
@@ -47,59 +47,21 @@
 #include "jsobjinlines.h"
 #include "jsstrinlines.h"
 #include "RegExpStatics-inl.h"
 
 inline js::RegExpObject *
 JSObject::asRegExp()
 {
     JS_ASSERT(isRegExp());
-    js::RegExpObject *reobj = static_cast<js::RegExpObject *>(this);
-    JS_ASSERT(reobj->getPrivate());
-    return reobj;
+    return static_cast<js::RegExpObject *>(this);
 }
 
 namespace js {
 
-/*
- * Maintains the post-initialization invariant of having a RegExpPrivate.
- *
- * N.B. If initialization fails, the |RegExpPrivate| will be null, so
- * finalization must consider that as a possibility.
- */
-class PreInitRegExpObject
-{
-    RegExpObject    *reobj;
-    DebugOnly<bool> gotResult;
-
-  public:
-    explicit PreInitRegExpObject(JSObject *obj) {
-        JS_ASSERT(obj->isRegExp());
-        reobj = static_cast<RegExpObject *>(obj);
-        gotResult = false;
-    }
-
-    ~PreInitRegExpObject() {
-        JS_ASSERT(gotResult);
-    }
-
-    RegExpObject *get() { return reobj; }
-
-    void succeed() {
-        JS_ASSERT(!gotResult);
-        JS_ASSERT(reobj->getPrivate());
-        gotResult = true;
-    }
-
-    void fail() {
-        JS_ASSERT(!gotResult);
-        gotResult = true;
-    }
-};
-
 inline bool
 ValueIsRegExp(const Value &v)
 {
     return !v.isPrimitive() && v.toObject().isRegExp();
 }
 
 inline bool
 IsRegExpMetaChar(jschar c)
@@ -120,78 +82,80 @@ HasRegExpMetaChars(const jschar *chars, 
 {
     for (size_t i = 0; i < length; ++i) {
         if (IsRegExpMetaChar(chars[i]))
             return true;
     }
     return false;
 }
 
-inline bool
-ResetRegExpObject(JSContext *cx, RegExpObject *reobj, JSString *str, RegExpFlag flags)
-{
-    AlreadyIncRefed<RegExpPrivate> rep = RegExpPrivate::create(cx, str, flags, NULL);
-    if (!rep)
-        return false;
-
-    return reobj->reset(cx, rep);
-}
-
-inline bool
-ResetRegExpObject(JSContext *cx, RegExpObject *reobj, AlreadyIncRefed<RegExpPrivate> rep)
-{
-    return reobj->reset(cx, rep);
-}
-
 inline RegExpObject *
 RegExpObject::create(JSContext *cx, RegExpStatics *res, const jschar *chars, size_t length,
-                     RegExpFlag flags, TokenStream *ts)
+                     RegExpFlag flags, TokenStream *tokenStream)
 {
     RegExpFlag staticsFlags = res->getFlags();
-    return createNoStatics(cx, chars, length, RegExpFlag(flags | staticsFlags), ts);
+    return createNoStatics(cx, chars, length, RegExpFlag(flags | staticsFlags), tokenStream);
 }
 
 inline RegExpObject *
 RegExpObject::createNoStatics(JSContext *cx, const jschar *chars, size_t length,
-                              RegExpFlag flags, TokenStream *ts)
+                              RegExpFlag flags, TokenStream *tokenStream)
 {
-    JSString *str = js_NewStringCopyN(cx, chars, length);
-    if (!str)
+    JSLinearString *source = js_NewStringCopyN(cx, chars, length);
+    if (!source)
         return NULL;
 
-    /*
-     * |NewBuiltinClassInstance| can GC before we store |re| in the
-     * private field of the object. At that point the only reference to
-     * the source string could be from the malloc-allocated GC-invisible
-     * |re|. So we must anchor.
-     */
-    JS::Anchor<JSString *> anchor(str);
-    AlreadyIncRefed<RegExpPrivate> rep = RegExpPrivate::create(cx, str, flags, ts);
-    if (!rep)
+    /* |NewBuiltinClassInstance| can GC. */
+    JS::Anchor<JSString *> anchor(source);
+
+    if (!RegExpPrivateCode::checkSyntax(cx, tokenStream, source))
         return NULL;
 
     JSObject *obj = NewBuiltinClassInstance(cx, &RegExpClass);
     if (!obj)
         return NULL;
 
-    PreInitRegExpObject pireo(obj);
-    RegExpObject *reobj = pireo.get();
-    if (!ResetRegExpObject(cx, reobj, rep)) {
+    RegExpObject *reobj = obj->asRegExp();
+    return reobj->reset(cx, source, flags) ? reobj : NULL;
+}
+
+inline void
+RegExpObject::finalize(JSContext *cx)
+{
+    if (RegExpPrivate *rep = getPrivate())
         rep->decref(cx);
-        pireo.fail();
-        return NULL;
-    }
-
-    pireo.succeed();
-    return reobj;
+#ifdef DEBUG
+    setPrivate((void *) 0x1); /* Non-null but still in the zero page. */
+#endif
 }
 
 inline bool
 RegExpObject::reset(JSContext *cx, AlreadyIncRefed<RegExpPrivate> rep)
 {
+    if (!reset(cx, rep->getSource(), rep->getFlags()))
+        return false;
+
+    setPrivate(rep.get());
+    return true;
+}
+
+inline bool
+RegExpObject::reset(JSContext *cx, RegExpObject *other)
+{
+    if (RegExpPrivate *rep = other->getPrivate()) {
+        rep->incref(cx);
+        return reset(cx, AlreadyIncRefed<RegExpPrivate>(rep));
+    }
+
+    return reset(cx, other->getSource(), other->getFlags());
+}
+
+inline bool
+RegExpObject::reset(JSContext *cx, JSLinearString *source, RegExpFlag flags)
+{
     if (nativeEmpty()) {
         const js::Shape **shapep = &cx->compartment->initialRegExpShape;
         if (!*shapep) {
             *shapep = assignInitialShape(cx);
             if (!*shapep)
                 return false;
         }
         setLastProperty(*shapep);
@@ -203,114 +167,30 @@ RegExpObject::reset(JSContext *cx, Alrea
     JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->sourceAtom))->slot == SOURCE_SLOT);
     JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->globalAtom))->slot == GLOBAL_FLAG_SLOT);
     JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->ignoreCaseAtom))->slot ==
                                  IGNORE_CASE_FLAG_SLOT);
     JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->multilineAtom))->slot ==
                                  MULTILINE_FLAG_SLOT);
     JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->stickyAtom))->slot == STICKY_FLAG_SLOT);
 
-    setPrivate(rep.get());
     zeroLastIndex();
-    setSource(rep->getSource());
-    setGlobal(rep->global());
-    setIgnoreCase(rep->ignoreCase());
-    setMultiline(rep->multiline());
-    setSticky(rep->sticky());
+    setPrivate(NULL);
+    setSource(source);
+    setGlobal(flags & GlobalFlag);
+    setIgnoreCase(flags & IgnoreCaseFlag);
+    setMultiline(flags & MultilineFlag);
+    setSticky(flags & StickyFlag);
     return true;
 }
 
 /* RegExpPrivate inlines. */
 
-class RegExpMatchBuilder
-{
-    JSContext   * const cx;
-    JSObject    * const array;
-
-    bool setProperty(JSAtom *name, Value v) {
-        return !!js_DefineProperty(cx, array, ATOM_TO_JSID(name), &v,
-                                   JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE);
-    }
-
-  public:
-    RegExpMatchBuilder(JSContext *cx, JSObject *array) : cx(cx), array(array) {}
-
-    bool append(uint32 index, Value v) {
-        JS_ASSERT(!array->getOps()->getElement);
-        return !!js_DefineElement(cx, array, index, &v, JS_PropertyStub, JS_StrictPropertyStub,
-                                  JSPROP_ENUMERATE);
-    }
-
-    bool setIndex(int index) {
-        return setProperty(cx->runtime->atomState.indexAtom, Int32Value(index));
-    }
-
-    bool setInput(JSString *str) {
-        JS_ASSERT(str);
-        return setProperty(cx->runtime->atomState.inputAtom, StringValue(str));
-    }
-};
-
-inline void
-RegExpPrivate::checkMatchPairs(JSString *input, int *buf, size_t matchItemCount)
-{
-#if DEBUG
-    size_t inputLength = input->length();
-    for (size_t i = 0; i < matchItemCount; i += 2) {
-        int start = buf[i];
-        int limit = buf[i + 1];
-        JS_ASSERT(limit >= start); /* Limit index must be larger than the start index. */
-        if (start == -1)
-            continue;
-        JS_ASSERT(start >= 0);
-        JS_ASSERT(size_t(limit) <= inputLength);
-    }
-#endif
-}
-
-inline JSObject *
-RegExpPrivate::createResult(JSContext *cx, JSString *input, int *buf, size_t matchItemCount)
-{
-    /*
-     * Create the result array for a match. Array contents:
-     *  0:              matched string
-     *  1..pairCount-1: paren matches
-     */
-    JSObject *array = NewSlowEmptyArray(cx);
-    if (!array)
-        return NULL;
-
-    RegExpMatchBuilder builder(cx, array);
-    for (size_t i = 0; i < matchItemCount; i += 2) {
-        int start = buf[i];
-        int end = buf[i + 1];
-
-        JSString *captured;
-        if (start >= 0) {
-            JS_ASSERT(start <= end);
-            JS_ASSERT(unsigned(end) <= input->length());
-            captured = js_NewDependentString(cx, input, start, end - start);
-            if (!captured || !builder.append(i / 2, StringValue(captured)))
-                return NULL;
-        } else {
-            /* Missing parenthesized match. */
-            JS_ASSERT(i != 0); /* Since we had a match, first pair must be present. */
-            if (!builder.append(i / 2, UndefinedValue()))
-                return NULL;
-        }
-    }
-
-    if (!builder.setIndex(buf[0]) || !builder.setInput(input))
-        return NULL;
-
-    return array;
-}
-
 inline AlreadyIncRefed<RegExpPrivate>
-RegExpPrivate::create(JSContext *cx, JSString *source, RegExpFlag flags, TokenStream *ts)
+RegExpPrivate::create(JSContext *cx, JSLinearString *source, RegExpFlag flags, TokenStream *ts)
 {
     typedef AlreadyIncRefed<RegExpPrivate> RetType;
 
     JSLinearString *flatSource = source->ensureLinear(cx);
     if (!flatSource)
         return RetType(NULL);
 
     RegExpPrivate *self = cx->new_<RegExpPrivate>(flatSource, flags, cx->compartment);
@@ -411,43 +291,43 @@ RegExpPrivate::compile(JSContext *cx, To
     sb.infallibleAppend(postfix, JS_ARRAY_LENGTH(postfix));
 
     JSLinearString *fakeySource = sb.finishString();
     if (!fakeySource)
         return false;
     return code.compile(cx, *fakeySource, ts, &parenCount, getFlags());
 }
 
-inline RegExpPrivateCode::ExecuteResult
-RegExpPrivateCode::execute(JSContext *cx, const jschar *chars, size_t start, size_t length,
+inline RegExpRunStatus
+RegExpPrivateCode::execute(JSContext *cx, const jschar *chars, size_t length, size_t start,
                            int *output, size_t outputCount)
 {
     int result;
 #if ENABLE_YARR_JIT
     (void) cx; /* Unused. */
     if (codeBlock.isFallBack())
         result = JSC::Yarr::interpret(byteCode, chars, start, length, output);
     else
         result = JSC::Yarr::execute(codeBlock, chars, start, length, output);
 #else
     result = jsRegExpExecute(cx, compiled, chars, length, start, output, outputCount);
 #endif
 
     if (result == -1)
-        return Success_NotFound;
+        return RegExpRunStatus_Success_NotFound;
 
 #if !ENABLE_YARR_JIT
     if (result < 0) {
         reportPCREError(cx, result);
-        return Error;
+        return RegExpRunStatus_Error;
     }
 #endif
 
     JS_ASSERT(result >= 0);
-    return Success;
+    return RegExpRunStatus_Success;
 }
 
 inline void
 RegExpPrivate::incref(JSContext *cx)
 {
 #ifdef DEBUG
     assertSameCompartment(cx, compartment);
 #endif
@@ -460,16 +340,25 @@ RegExpPrivate::decref(JSContext *cx)
 #ifdef DEBUG
     assertSameCompartment(cx, compartment);
 #endif
     if (--refCount == 0)
         cx->delete_(this);
 }
 
 inline RegExpPrivate *
+RegExpObject::getOrCreatePrivate(JSContext *cx)
+{
+    if (RegExpPrivate *rep = getPrivate())
+        return rep;
+
+    return makePrivate(cx) ? getPrivate() : NULL;
+}
+
+inline RegExpPrivate *
 RegExpObject::getPrivate() const
 {
     RegExpPrivate *rep = static_cast<RegExpPrivate *>(JSObject::getPrivate());
 #ifdef DEBUG
     if (rep)
         CompartmentChecker::check(compartment(), rep->compartment);
 #endif
     return rep;
--- a/js/src/vm/RegExpObject.cpp
+++ b/js/src/vm/RegExpObject.cpp
@@ -36,16 +36,17 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "jsscan.h"
 
 #include "vm/RegExpStatics.h"
+#include "vm/MatchPairs.h"
 
 #include "jsobjinlines.h"
 #include "jsstrinlines.h"
 
 #include "vm/RegExpObject-inl.h"
 
 #ifdef JS_TRACER
 #include "jstracer.h"
@@ -54,100 +55,109 @@ using namespace nanojit;
 
 using namespace js;
 
 JS_STATIC_ASSERT(IgnoreCaseFlag == JSREG_FOLD);
 JS_STATIC_ASSERT(GlobalFlag == JSREG_GLOB);
 JS_STATIC_ASSERT(MultilineFlag == JSREG_MULTILINE);
 JS_STATIC_ASSERT(StickyFlag == JSREG_STICKY);
 
-bool
-RegExpPrivate::executeInternal(JSContext *cx, RegExpStatics *res, JSString *inputstr,
-                               size_t *lastIndex, bool test, Value *rval)
+MatchPairs *
+MatchPairs::create(LifoAlloc &alloc, size_t pairCount, size_t backingPairCount)
+{
+    void *mem = alloc.alloc(calculateSize(backingPairCount));
+    if (!mem)
+        return NULL;
+
+    return new (mem) MatchPairs(pairCount);
+}
+
+inline void
+MatchPairs::checkAgainst(size_t inputLength)
 {
-    const size_t pairCount = parenCount + 1;
-    const size_t matchItemCount = pairCount * 2;
-    const size_t bufCount = RegExpPrivateCode::getOutputSize(pairCount);
+#if DEBUG
+    for (size_t i = 0; i < pairCount(); ++i) {
+        MatchPair p = pair(i);
+        p.check();
+        if (p.isUndefined())
+            continue;
+        JS_ASSERT(size_t(p.limit) <= inputLength);
+    }
+#endif
+}
 
-    LifoAllocScope las(&cx->tempLifoAlloc());
-    int *buf = cx->tempLifoAlloc().newArray<int>(bufCount);
-    if (!buf)
-        return false;
+RegExpRunStatus
+RegExpPrivate::execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
+                       LifoAllocScope &allocScope, MatchPairs **output)
+{
+    const size_t origLength = length;
+    size_t backingPairCount = RegExpPrivateCode::getOutputSize(pairCount());
+
+    MatchPairs *matchPairs = MatchPairs::create(allocScope.alloc(), pairCount(), backingPairCount);
+    if (!matchPairs)
+        return RegExpRunStatus_Error;
 
     /*
-     * The JIT regexp procedure doesn't always initialize matchPair values.
-     * Maybe we can make this faster by ensuring it does?
+     * |displacement| emulates sticky mode by matching from this offset
+     * into the char buffer and subtracting the delta off at the end.
      */
-    for (int *it = buf; it != buf + matchItemCount; ++it)
-        *it = -1;
-    
-    JSLinearString *input = inputstr->ensureLinear(cx);
-    if (!input)
-        return false;
-
-    JS::Anchor<JSString *> anchor(input);
-    size_t len = input->length();
-    const jschar *chars = input->chars();
-
-    /* 
-     * inputOffset emulates sticky mode by matching from this offset into the char buf and
-     * subtracting the delta off at the end.
-     */
-    size_t inputOffset = 0;
+    size_t start = *lastIndex;
+    size_t displacement = 0;
 
     if (sticky()) {
-        /* Sticky matches at the last index for the regexp object. */
-        chars += *lastIndex;
-        len -= *lastIndex;
-        inputOffset = *lastIndex;
+        displacement = *lastIndex;
+        chars += displacement;
+        length -= displacement;
+        start = 0;
     }
 
-    size_t start = *lastIndex - inputOffset;
-    RegExpPrivateCode::ExecuteResult result = code.execute(cx, chars, start, len, buf, bufCount);
+    RegExpRunStatus status = code.execute(cx, chars, length, start,
+                                          matchPairs->buffer(), backingPairCount);
 
-    switch (result) {
-      case RegExpPrivateCode::Error:
-        return false;
-      case RegExpPrivateCode::Success_NotFound:
-        *rval = NullValue();
-        return true;
+    switch (status) {
+      case RegExpRunStatus_Error:
+        return status;
+      case RegExpRunStatus_Success_NotFound:
+        *output = matchPairs;
+        return status;
       default:
-        JS_ASSERT(result == RegExpPrivateCode::Success);
+        JS_ASSERT(status == RegExpRunStatus_Success);
     }
 
-    /* 
-     * Adjust buf for the inputOffset. Use of sticky is rare and the matchItemCount is small, so
-     * just do another pass.
-     */
-    if (JS_UNLIKELY(inputOffset)) {
-        for (size_t i = 0; i < matchItemCount; ++i)
-            buf[i] = buf[i] < 0 ? -1 : buf[i] + inputOffset;
-    }
+    matchPairs->displace(displacement);
+    matchPairs->checkAgainst(origLength);
 
-    /* Make sure the populated contents of |buf| are sane values against |input|. */
-    checkMatchPairs(input, buf, matchItemCount);
+    *lastIndex = matchPairs->pair(0).limit;
+    *output = matchPairs;
 
-    if (res)
-        res->updateFromMatch(cx, input, buf, matchItemCount);
-
-    *lastIndex = buf[1];
+    return RegExpRunStatus_Success;
+}
 
-    if (test) {
-        *rval = BooleanValue(true);
-        return true;
-    }
-
-    JSObject *array = createResult(cx, input, buf, matchItemCount);
-    if (!array)
+bool
+RegExpObject::makePrivate(JSContext *cx)
+{
+    JS_ASSERT(!getPrivate());
+    AlreadyIncRefed<RegExpPrivate> rep = RegExpPrivate::create(cx, getSource(), getFlags(), NULL);
+    if (!rep)
         return false;
 
-    *rval = ObjectValue(*array);
+    setPrivate(rep.get());
     return true;
 }
 
+RegExpRunStatus
+RegExpObject::execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
+                      LifoAllocScope &allocScope, MatchPairs **output)
+{
+    if (!getPrivate() && !makePrivate(cx))
+        return RegExpRunStatus_Error;
+
+    return getPrivate()->execute(cx, chars, length, lastIndex, allocScope, output);
+}
+
 const Shape *
 RegExpObject::assignInitialShape(JSContext *cx)
 {
     JS_ASSERT(!cx->compartment->initialRegExpShape);
     JS_ASSERT(isRegExp());
     JS_ASSERT(nativeEmpty());
 
     JS_STATIC_ASSERT(LAST_INDEX_SLOT == 0);
@@ -221,18 +231,17 @@ js_XDRRegExpObject(JSXDRState *xdr, JSOb
 
 #define js_XDRRegExpObject NULL
 
 #endif /* !JS_HAS_XDR */
 
 static void
 regexp_finalize(JSContext *cx, JSObject *obj)
 {
-    if (RegExpPrivate *rep = static_cast<RegExpObject *>(obj)->getPrivate())
-        rep->decref(cx);
+    obj->asRegExp()->finalize(cx);
 }
 
 Class js::RegExpClass = {
     js_RegExp_str,
     JSCLASS_HAS_PRIVATE |
     JSCLASS_HAS_RESERVED_SLOTS(RegExpObject::RESERVED_SLOTS) |
     JSCLASS_HAS_CACHED_PROTO(JSProto_RegExp),
     JS_PropertyStub,         /* addProperty */
@@ -289,19 +298,19 @@ RegExpPrivateCode::reportYarrError(JSCon
 void
 RegExpPrivateCode::reportPCREError(JSContext *cx, int error)
 {
 #define REPORT(msg_) \
     JS_ReportErrorFlagsAndNumberUC(cx, JSREPORT_ERROR, js_GetErrorMessage, NULL, msg_); \
     return
     switch (error) {
       case -2: REPORT(JSMSG_REGEXP_TOO_COMPLEX);
-      case 0: JS_NOT_REACHED("Precondition violation: an error must have occurred."); 
+      case 0: JS_NOT_REACHED("Precondition violation: an error must have occurred.");
       case 1: REPORT(JSMSG_TRAILING_SLASH);
-      case 2: REPORT(JSMSG_TRAILING_SLASH); 
+      case 2: REPORT(JSMSG_TRAILING_SLASH);
       case 3: REPORT(JSMSG_REGEXP_TOO_COMPLEX);
       case 4: REPORT(JSMSG_BAD_QUANTIFIER);
       case 5: REPORT(JSMSG_BAD_QUANTIFIER);
       case 6: REPORT(JSMSG_BAD_CLASS_RANGE);
       case 7: REPORT(JSMSG_REGEXP_TOO_COMPLEX);
       case 8: REPORT(JSMSG_BAD_CLASS_RANGE);
       case 9: REPORT(JSMSG_BAD_QUANTIFIER);
       case 10: REPORT(JSMSG_UNMATCHED_RIGHT_PAREN);
@@ -312,16 +321,17 @@ RegExpPrivateCode::reportPCREError(JSCon
       case 15: REPORT(JSMSG_BAD_BACKREF);
       case 16: REPORT(JSMSG_REGEXP_TOO_COMPLEX);
       case 17: REPORT(JSMSG_REGEXP_TOO_COMPLEX);
       default:
         JS_NOT_REACHED("Precondition violation: unknown PCRE error code.");
     }
 #undef REPORT
 }
+
 #endif /* ENABLE_YARR_JIT */
 
 bool
 js::ParseRegExpFlags(JSContext *cx, JSString *flagStr, RegExpFlag *flagsOut)
 {
     size_t n = flagStr->length();
     const jschar *s = flagStr->getChars(cx);
     if (!s)
@@ -352,74 +362,68 @@ js::ParseRegExpFlags(JSContext *cx, JSSt
           }
         }
 #undef HANDLE_FLAG
     }
     return true;
 }
 
 AlreadyIncRefed<RegExpPrivate>
-RegExpPrivate::createFlagged(JSContext *cx, JSString *str, JSString *opt, TokenStream *ts)
+RegExpPrivate::create(JSContext *cx, JSLinearString *str, JSString *opt, TokenStream *ts)
 {
     if (!opt)
         return create(cx, str, RegExpFlag(0), ts);
 
     RegExpFlag flags = RegExpFlag(0);
     if (!ParseRegExpFlags(cx, opt, &flags))
         return AlreadyIncRefed<RegExpPrivate>(NULL);
 
     return create(cx, str, flags, ts);
 }
 
 RegExpObject *
-RegExpObject::clone(JSContext *cx, RegExpObject *obj, RegExpObject *proto)
+RegExpObject::clone(JSContext *cx, RegExpObject *reobj, RegExpObject *proto)
 {
     JSObject *clone = NewNativeClassInstance(cx, &RegExpClass, proto, proto->getParent());
     if (!clone)
         return NULL;
 
     /*
      * This clone functionality does not duplicate the JIT'd code blob,
      * which is necessary for cross-compartment cloning functionality.
      */
-    assertSameCompartment(cx, obj, clone);
+    assertSameCompartment(cx, reobj, clone);
 
     RegExpStatics *res = cx->regExpStatics();
+    RegExpObject *reclone = clone->asRegExp();
 
-    /* 
+    /*
      * Check that the RegExpPrivate for the original is okay to use in
-     * the clone -- if the RegExpStatics provides more flags we'll need
-     * a different RegExpPrivate.
+     * the clone -- if the |RegExpStatics| provides more flags we'll
+     * need a different |RegExpPrivate|.
      */
-    AlreadyIncRefed<RegExpPrivate> rep(NULL);
-    {
-        RegExpFlag origFlags = obj->getFlags();
-        RegExpFlag staticsFlags = res->getFlags();
-        if ((origFlags & staticsFlags) != staticsFlags) {
-            RegExpFlag newFlags = RegExpFlag(origFlags | staticsFlags);
-            rep = RegExpPrivate::create(cx, obj->getSource(), newFlags, NULL);
-            if (!rep)
-                return NULL;
-        } else {
-            RegExpPrivate *toShare = obj->getPrivate();
-            toShare->incref(cx);
-            rep = AlreadyIncRefed<RegExpPrivate>(toShare);
-        }
+    RegExpFlag origFlags = reobj->getFlags();
+    RegExpFlag staticsFlags = res->getFlags();
+    if ((origFlags & staticsFlags) != staticsFlags) {
+        RegExpFlag newFlags = RegExpFlag(origFlags | staticsFlags);
+        return reclone->reset(cx, reobj->getSource(), newFlags) ? reclone : NULL;
     }
 
-    JS_ASSERT(rep);
-
-    PreInitRegExpObject pireo(clone);
-    RegExpObject *reclone = pireo.get();
-    if (!ResetRegExpObject(cx, reclone, rep)) {
-        pireo.fail();
-        return NULL;
+    RegExpPrivate *toShare = reobj->getPrivate();
+    if (toShare) {
+        toShare->incref(cx);
+        if (!reclone->reset(cx, AlreadyIncRefed<RegExpPrivate>(toShare))) {
+            toShare->decref(cx);
+            return NULL;
+        }
+    } else {
+        if (!reclone->reset(cx, reobj))
+            return NULL;
     }
 
-    pireo.succeed();
     return reclone;
 }
 
 JSObject * JS_FASTCALL
 js_CloneRegExpObject(JSContext *cx, JSObject *obj, JSObject *proto)
 {
     JS_ASSERT(obj->isRegExp());
     JS_ASSERT(proto->isRegExp());
@@ -430,35 +434,31 @@ js_CloneRegExpObject(JSContext *cx, JSOb
 #ifdef JS_TRACER
 JS_DEFINE_CALLINFO_3(extern, OBJECT, js_CloneRegExpObject, CONTEXT, OBJECT, OBJECT, 0,
                      ACCSET_STORE_ANY)
 #endif
 
 JSFlatString *
 RegExpObject::toString(JSContext *cx) const
 {
-    RegExpPrivate *rep = getPrivate();
-    if (!rep)
-        return cx->runtime->emptyString;
-
-    JSLinearString *src = rep->getSource();
+    JSLinearString *src = getSource();
     StringBuffer sb(cx);
     if (size_t len = src->length()) {
         if (!sb.reserve(len + 2))
             return NULL;
         sb.infallibleAppend('/');
         sb.infallibleAppend(src->chars(), len);
         sb.infallibleAppend('/');
     } else {
         if (!sb.append("/(?:)/"))
             return NULL;
     }
-    if (rep->global() && !sb.append('g'))
+    if (global() && !sb.append('g'))
         return NULL;
-    if (rep->ignoreCase() && !sb.append('i'))
+    if (ignoreCase() && !sb.append('i'))
         return NULL;
-    if (rep->multiline() && !sb.append('m'))
+    if (multiline() && !sb.append('m'))
         return NULL;
-    if (rep->sticky() && !sb.append('y'))
+    if (sticky() && !sb.append('y'))
         return NULL;
 
     return sb.finishString();
 }
--- a/js/src/vm/RegExpObject.h
+++ b/js/src/vm/RegExpObject.h
@@ -44,23 +44,29 @@
 #include <stddef.h>
 #include "jsobj.h"
 
 #include "js/TemplateLib.h"
 
 #include "yarr/Yarr.h"
 #if ENABLE_YARR_JIT
 #include "yarr/YarrJIT.h"
+#include "yarr/YarrSyntaxChecker.h"
 #else
 #include "yarr/pcre/pcre.h"
 #endif
 
 namespace js {
 
-class RegExpPrivate;
+enum RegExpRunStatus
+{
+    RegExpRunStatus_Error,
+    RegExpRunStatus_Success,
+    RegExpRunStatus_Success_NotFound
+};
 
 class RegExpObject : public ::JSObject
 {
     static const uintN LAST_INDEX_SLOT          = 0;
     static const uintN SOURCE_SLOT              = 1;
     static const uintN GLOBAL_FLAG_SLOT         = 2;
     static const uintN IGNORE_CASE_FLAG_SLOT    = 3;
     static const uintN MULTILINE_FLAG_SLOT      = 4;
@@ -82,79 +88,107 @@ class RegExpObject : public ::JSObject
     createNoStatics(JSContext *cx, const jschar *chars, size_t length, RegExpFlag flags,
                     TokenStream *ts);
 
     static RegExpObject *clone(JSContext *cx, RegExpObject *obj, RegExpObject *proto);
 
     /* Note: fallible. */
     JSFlatString *toString(JSContext *cx) const;
 
+    /*
+     * 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, const jschar *chars, size_t length, size_t *lastIndex,
+                            LifoAllocScope &allocScope, MatchPairs **output);
+
     /* Accessors. */
 
     const Value &getLastIndex() const {
         return getSlot(LAST_INDEX_SLOT);
     }
     void setLastIndex(const Value &v) {
         setSlot(LAST_INDEX_SLOT, v);
     }
     void setLastIndex(double d) {
         setSlot(LAST_INDEX_SLOT, NumberValue(d));
     }
     void zeroLastIndex() {
         setSlot(LAST_INDEX_SLOT, Int32Value(0));
     }
 
-    JSString *getSource() const {
-        return getSlot(SOURCE_SLOT).toString();
+    JSLinearString *getSource() const {
+        return &getSlot(SOURCE_SLOT).toString()->asLinear();
     }
-    void setSource(JSString *source) {
+    void setSource(JSLinearString *source) {
         setSlot(SOURCE_SLOT, StringValue(source));
     }
     RegExpFlag getFlags() const {
         uintN flags = 0;
-        flags |= getSlot(GLOBAL_FLAG_SLOT).toBoolean() ? GlobalFlag : 0;
-        flags |= getSlot(IGNORE_CASE_FLAG_SLOT).toBoolean() ? IgnoreCaseFlag : 0;
-        flags |= getSlot(MULTILINE_FLAG_SLOT).toBoolean() ? MultilineFlag : 0;
-        flags |= getSlot(STICKY_FLAG_SLOT).toBoolean() ? StickyFlag : 0;
+        flags |= global() ? GlobalFlag : 0;
+        flags |= ignoreCase() ? IgnoreCaseFlag : 0;
+        flags |= multiline() ? MultilineFlag : 0;
+        flags |= sticky() ? StickyFlag : 0;
         return RegExpFlag(flags);
     }
-    inline RegExpPrivate *getPrivate() const;
 
     /* Flags. */
 
     void setIgnoreCase(bool enabled)    { setSlot(IGNORE_CASE_FLAG_SLOT, BooleanValue(enabled)); }
     void setGlobal(bool enabled)        { setSlot(GLOBAL_FLAG_SLOT, BooleanValue(enabled)); }
     void setMultiline(bool enabled)     { setSlot(MULTILINE_FLAG_SLOT, BooleanValue(enabled)); }
     void setSticky(bool enabled)        { setSlot(STICKY_FLAG_SLOT, BooleanValue(enabled)); }
+    bool ignoreCase() const { return getSlot(IGNORE_CASE_FLAG_SLOT).toBoolean(); }
+    bool global() const     { return getSlot(GLOBAL_FLAG_SLOT).toBoolean(); }
+    bool multiline() const  { return getSlot(MULTILINE_FLAG_SLOT).toBoolean(); }
+    bool sticky() const     { return getSlot(STICKY_FLAG_SLOT).toBoolean(); }
+
+    /*
+     * N.B. |RegExpObject|s can be mutated in place because of |RegExp.prototype.compile|, hence
+     * |reset| for re-initialization.
+     */
+
+    inline bool reset(JSContext *cx, RegExpObject *other);
+    inline bool reset(JSContext *cx, AlreadyIncRefed<RegExpPrivate> rep);
+    inline bool reset(JSContext *cx, JSLinearString *source, RegExpFlag flags);
+
+    inline RegExpPrivate *getOrCreatePrivate(JSContext *cx);
+    inline void finalize(JSContext *cx);
 
   private:
-    /* N.B. |RegExpObject|s can be mutated in place because of |RegExp.prototype.compile|. */
-    inline bool reset(JSContext *cx, AlreadyIncRefed<RegExpPrivate> rep);
+    /* The |RegExpPrivate| is lazily created at the time of use. */
+    inline RegExpPrivate *getPrivate() const;
 
-    friend bool ResetRegExpObject(JSContext *, RegExpObject *, JSString *, RegExpFlag);
+    /*
+     * Precondition: the syntax for |source| has already been validated.
+     * Side effect: sets the private field.
+     */
+    bool makePrivate(JSContext *cx);
+
+    friend bool ResetRegExpObject(JSContext *, RegExpObject *, JSLinearString *, RegExpFlag);
     friend bool ResetRegExpObject(JSContext *, RegExpObject *, AlreadyIncRefed<RegExpPrivate>);
 
     /*
      * Compute the initial shape to associate with fresh RegExp objects,
      * encoding their initial properties. Return the shape after
      * changing this regular expression object's last property to it.
      */
     const Shape *assignInitialShape(JSContext *cx);
 
     RegExpObject();
     RegExpObject &operator=(const RegExpObject &reo);
 }; /* class RegExpObject */
 
-inline bool
-ResetRegExpObject(JSContext *cx, RegExpObject *reobj, JSString *str, RegExpFlag flags);
-
-/* N.B. On failure, caller must decref |rep|. */
-inline bool
-ResetRegExpObject(JSContext *cx, AlreadyIncRefed<RegExpPrivate> rep);
-
 /* Abstracts away the gross |RegExpPrivate| backend details. */
 class RegExpPrivateCode
 {
 #if ENABLE_YARR_JIT
     typedef JSC::Yarr::BytecodePattern BytecodePattern;
     typedef JSC::Yarr::ErrorCode ErrorCode;
     typedef JSC::Yarr::JSGlobalData JSGlobalData;
     typedef JSC::Yarr::YarrCodeBlock YarrCodeBlock;
@@ -184,30 +218,41 @@ class RegExpPrivateCode
         if (byteCode)
             Foreground::delete_<BytecodePattern>(byteCode);
 #else
         if (compiled)
             jsRegExpFree(compiled);
 #endif
     }
 
+    static bool checkSyntax(JSContext *cx, TokenStream *tokenStream, JSLinearString *source) {
+#if ENABLE_YARR_JIT
+        ErrorCode error = JSC::Yarr::checkSyntax(*source);
+        if (error == JSC::Yarr::NoError)
+            return true;
+
+        reportYarrError(cx, tokenStream, error);
+        return false;
+#else
+# error "Syntax checking not implemented for !ENABLE_YARR_JIT"
+#endif
+    }
+
 #if ENABLE_YARR_JIT
     static inline bool isJITRuntimeEnabled(JSContext *cx);
-    void reportYarrError(JSContext *cx, TokenStream *ts, JSC::Yarr::ErrorCode error);
+    static void reportYarrError(JSContext *cx, TokenStream *ts, JSC::Yarr::ErrorCode error);
 #else
-    void reportPCREError(JSContext *cx, int error);
+    static void reportPCREError(JSContext *cx, int error);
 #endif
 
     inline bool compile(JSContext *cx, JSLinearString &pattern, TokenStream *ts, uintN *parenCount,
                         RegExpFlag flags);
 
-    enum ExecuteResult { Error, Success, Success_NotFound };
-
-    inline ExecuteResult execute(JSContext *cx, const jschar *chars, size_t start, size_t length,
-                                 int *output, size_t outputCount);
+    inline RegExpRunStatus execute(JSContext *cx, const jschar *chars, size_t length, size_t start,
+                                   int *output, size_t outputCount);
 
     static size_t getOutputSize(size_t pairCount) {
 #if ENABLE_YARR_JIT
         return pairCount * 2;
 #else
         return pairCount * 3; /* Should be x2, but PCRE has... needs. */
 #endif
     }
@@ -242,66 +287,49 @@ class RegExpPrivate
     RegExpPrivate(JSLinearString *source, RegExpFlag flags, JSCompartment *compartment)
       : source(source), refCount(1), parenCount(0), flags(flags), compartment(compartment)
     { }
 
     JS_DECLARE_ALLOCATION_FRIENDS_FOR_PRIVATE_CONSTRUCTOR;
 
     bool compile(JSContext *cx, TokenStream *ts);
     static inline void checkMatchPairs(JSString *input, int *buf, size_t matchItemCount);
-    static JSObject *createResult(JSContext *cx, JSString *input, int *buf, size_t matchItemCount);
-    bool executeInternal(JSContext *cx, RegExpStatics *res, JSString *input,
-                         size_t *lastIndex, bool test, Value *rval);
 
   public:
-    /*
-     * Execute regexp on |input| at |*lastIndex|.
-     *
-     * On match:    Update |*lastIndex| and RegExp class statics.
-     *              Return true if test is true. Place an array in |*rval| if test is false.
-     * On mismatch: Make |*rval| null.
-     */
-    bool execute(JSContext *cx, RegExpStatics *res, JSString *input, size_t *lastIndex, bool test,
-                 Value *rval) {
-        JS_ASSERT(res);
-        return executeInternal(cx, res, input, lastIndex, test, rval);
-    }
+    static AlreadyIncRefed<RegExpPrivate>
+    create(JSContext *cx, JSLinearString *source, RegExpFlag flags, TokenStream *ts);
 
-    bool executeNoStatics(JSContext *cx, JSString *input, size_t *lastIndex, bool test,
-                          Value *rval) {
-        return executeInternal(cx, NULL, input, lastIndex, test, rval);
-    }
-
-    /* Factories */
+    static AlreadyIncRefed<RegExpPrivate>
+    create(JSContext *cx, JSLinearString *source, JSString *flags, TokenStream *ts);
 
-    static AlreadyIncRefed<RegExpPrivate> create(JSContext *cx, JSString *source, RegExpFlag flags,
-                                                 TokenStream *ts);
-
-    /* Would overload |create|, but |0| resolves ambiguously against pointer and uint. */
-    static AlreadyIncRefed<RegExpPrivate> createFlagged(JSContext *cx, JSString *source,
-                                                        JSString *flags, TokenStream *ts);
+    RegExpRunStatus execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
+                            LifoAllocScope &allocScope, MatchPairs **output);
 
     /* Mutators */
 
     void incref(JSContext *cx);
     void decref(JSContext *cx);
 
     /* For JIT access. */
     size_t *addressOfRefCount() { return &refCount; }
 
     /* Accessors */
 
-    JSLinearString *getSource() const { return source; }
-    size_t getParenCount() const { return parenCount; }
-    RegExpFlag getFlags() const { return flags; }
+    JSLinearString *getSource() const   { return source; }
+    size_t getParenCount() const        { return parenCount; }
+
+    /* Accounts for the "0" (whole match) pair. */
+    size_t pairCount() const            { return parenCount + 1; }
+
+    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; }
-}; /* class RegExpPrivate */
+};
 
 /*
  * Parse regexp flags. Report an error and return false if an invalid
  * sequence of flags is encountered (repeat/invalid flag).
  *
  * N.B. flagStr must be rooted.
  */
 bool
--- a/js/src/vm/RegExpStatics.h
+++ b/js/src/vm/RegExpStatics.h
@@ -40,22 +40,24 @@
 
 #ifndef RegExpStatics_h__
 #define RegExpStatics_h__
 
 #include "jscntxt.h"
 
 #include "js/Vector.h"
 
+#include "vm/MatchPairs.h"
+
 namespace js {
 
 class RegExpStatics
 {
-    typedef Vector<int, 20, SystemAllocPolicy> MatchPairs;
-    MatchPairs      matchPairs;
+    typedef Vector<int, 20, SystemAllocPolicy> Pairs;
+    Pairs           matchPairs;
     /* The input that was used to produce matchPairs. */
     JSLinearString  *matchPairsInput;
     /* The input last set on the statics. */
     JSString        *pendingInput;
     RegExpFlag      flags;
     RegExpStatics   *bufferLink;
     bool            copied;
 
@@ -157,27 +159,30 @@ class RegExpStatics
 
   public:
     RegExpStatics() : bufferLink(NULL), copied(false) { clear(); }
 
     static JSObject *create(JSContext *cx, GlobalObject *parent);
 
     /* Mutators. */
 
-    bool updateFromMatch(JSContext *cx, JSLinearString *input, int *buf, size_t matchItemCount) {
+    bool updateFromMatchPairs(JSContext *cx, JSLinearString *input, MatchPairs *newPairs) {
+        JS_ASSERT(input);
         aboutToWrite();
         pendingInput = input;
 
-        if (!matchPairs.resizeUninitialized(matchItemCount)) {
+        if (!matchPairs.resizeUninitialized(2 * newPairs->pairCount())) {
             js_ReportOutOfMemory(cx);
             return false;
         }
 
-        for (size_t i = 0; i < matchItemCount; ++i)
-            matchPairs[i] = buf[i];
+        for (size_t i = 0; i < newPairs->pairCount(); ++i) {
+            matchPairs[2 * i] = newPairs->pair(i).start;
+            matchPairs[2 * i + 1] = newPairs->pair(i).limit;
+        }
 
         matchPairsInput = input;
         return true;
     }
 
     inline void setMultiline(JSContext *cx, bool enabled);
 
     void clear() {