Bug 634654 - Add RegExpPrivate cache. (r=Waldo)
authorChris Leary <cdleary@mozilla.com>
Mon, 07 Nov 2011 13:42:31 -0800
changeset 79949 366d80e918160373ca4f72a0006bcbff0619bdc7
parent 79948 82f9cf1140908116feccfeb980f006636c7b3fa8
child 79950 bea7ecf9084e7a2285684725cfba3c5f4cc35dba
push id3217
push usercleary@mozilla.com
push dateMon, 07 Nov 2011 23:55:38 +0000
treeherdermozilla-inbound@366d80e91816 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersWaldo
bugs634654
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 634654 - Add RegExpPrivate cache. (r=Waldo)
js/src/builtin/RegExp.cpp
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jsgc.cpp
js/src/jsprvtd.h
js/src/jsstr.cpp
js/src/vm/RegExpObject-inl.h
js/src/vm/RegExpObject.cpp
js/src/vm/RegExpObject.h
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -201,42 +201,37 @@ EscapeNakedForwardSlashes(JSContext *cx,
 
         if (!sb.empty() && !sb.append(*it))
             return NULL;
     }
 
     return sb.empty() ? unescaped : sb.finishString();
 }
 
-static bool
-ResetRegExpObjectWithStatics(JSContext *cx, RegExpObject *reobj,
-                             JSLinearString *str, RegExpFlag flags = RegExpFlag(0))
-{
-    flags = RegExpFlag(flags | cx->regExpStatics()->getFlags());
-    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:
  *
  *  RegExp, undefined => flags := pattern.flags
  *  RegExp, _ => throw TypeError
  *  _ => pattern := ToString(pattern) if defined(pattern) else ''
  *       flags := ToString(flags) if defined(flags) else ''
  */
 static bool
-CompileRegExpObject(JSContext *cx, RegExpObject *obj, uintN argc, Value *argv, Value *rval)
+CompileRegExpObject(JSContext *cx, RegExpObjectBuilder &builder,
+                    uintN argc, Value *argv, Value *rval)
 {
     if (argc == 0) {
-        if (!ResetRegExpObjectWithStatics(cx, obj, cx->runtime->emptyString))
+        RegExpStatics *res = cx->regExpStatics();
+        RegExpObject *reobj = builder.build(cx->runtime->emptyString, res->getFlags());
+        if (!reobj)
             return false;
-        *rval = ObjectValue(*obj);
+        *rval = ObjectValue(*reobj);
         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 source/flags.
@@ -244,19 +239,20 @@ CompileRegExpObject(JSContext *cx, RegEx
          * 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;
         }
 
-        if (!obj->reset(cx, sourceObj.asRegExp()))
+        RegExpObject *reobj = builder.build(sourceObj.asRegExp());
+        if (!reobj)
             return false;
-        *rval = ObjectValue(*obj);
+        *rval = ObjectValue(*reobj);
         return true;
     }
 
     JSLinearString *sourceStr;
     if (sourceValue.isUndefined()) {
         sourceStr = cx->runtime->emptyString;
     } else {
         /* Coerce to string and compile. */
@@ -280,34 +276,37 @@ CompileRegExpObject(JSContext *cx, RegEx
 
     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);
+    RegExpStatics *res = cx->regExpStatics();
+    RegExpObject *reobj = builder.build(escapedSourceStr, RegExpFlag(flags | res->getFlags()));
+    if (!reobj)
+        return NULL;
+
+    *rval = ObjectValue(*reobj);
     return true;
 }
 
 static JSBool
 regexp_compile(JSContext *cx, uintN argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     bool ok;
     JSObject *obj = NonGenericMethodGuard(cx, args, regexp_compile, &RegExpClass, &ok);
     if (!obj)
         return ok;
 
-    RegExpObject *reobj = obj->asRegExp();
-    return CompileRegExpObject(cx, reobj, args.length(), args.array(), &args.rval());
+    RegExpObjectBuilder builder(cx, obj->asRegExp());
+    return CompileRegExpObject(cx, builder, 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)) {
@@ -317,24 +316,21 @@ regexp_construct(JSContext *cx, uintN ar
          * See ECMAv5 15.10.3.1.
          */
         if (argc >= 1 && ValueIsRegExp(argv[0]) && (argc == 1 || argv[1].isUndefined())) {
             *vp = argv[0];
             return true;
         }
     }
 
-    JSObject *obj = NewBuiltinClassInstance(cx, &RegExpClass);
-    if (!obj)
+    RegExpObjectBuilder builder(cx);
+    if (!CompileRegExpObject(cx, builder, argc, argv, &JS_RVAL(cx, vp)))
         return false;
 
-    if (!CompileRegExpObject(cx, obj->asRegExp(), argc, argv, &JS_RVAL(cx, vp)))
-        return false;
-
-    *vp = ObjectValue(*obj);
+    *vp = ObjectValue(*builder.reobj());
     return true;
 }
 
 static JSBool
 regexp_toString(JSContext *cx, uintN argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
@@ -457,19 +453,21 @@ js_InitRegExpClass(JSContext *cx, JSObje
 {
     JS_ASSERT(obj->isNative());
 
     GlobalObject *global = obj->asGlobal();
 
     JSObject *proto = global->createBlankPrototype(cx, &RegExpClass);
     if (!proto)
         return NULL;
+    proto->setPrivate(NULL);
 
     RegExpObject *reproto = proto->asRegExp();
-    if (!reproto->reset(cx, cx->runtime->emptyString, RegExpFlag(0)))
+    RegExpObjectBuilder builder(cx, reproto);
+    if (!builder.build(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)
@@ -521,16 +519,17 @@ ExecuteRegExp(JSContext *cx, Native nati
     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>(rep));
+
     RegExpStatics *res = cx->regExpStatics();
 
     /* Step 2. */
     JSString *input = js_ValueToString(cx, (args.length() > 0) ? args[0] : UndefinedValue());
     if (!input)
         return false;
 
     /* Step 3. */
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -106,16 +106,17 @@ ThreadData::ThreadData()
 #ifdef JS_TRACER
     onTraceCompartment(NULL),
     recordingCompartment(NULL),
     profilingCompartment(NULL),
     maxCodeCacheBytes(DEFAULT_JIT_CACHE_SIZE),
 #endif
     waiveGCQuota(false),
     tempLifoAlloc(TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
+    repCache(NULL),
     dtoaState(NULL),
     nativeStackBase(GetNativeStackBase()),
     pendingProxyOperation(NULL),
     interpreterFrames(NULL)
 {
 #ifdef DEBUG
     noGCOrAllocationCheck = 0;
 #endif
@@ -148,16 +149,41 @@ ThreadData::triggerOperationCallback(JSR
 
 #ifdef JS_THREADSAFE
     /* rt->interruptCounter does not reflect suspended threads. */
     if (requestDepth != 0)
         JS_ATOMIC_INCREMENT(&rt->interruptCounter);
 #endif
 }
 
+RegExpPrivateCache *
+ThreadData::createRegExpPrivateCache(JSRuntime *rt)
+{
+    JS_ASSERT(!repCache);
+    RegExpPrivateCache *newCache = rt->new_<RegExpPrivateCache>(rt);
+
+    if (!newCache || !newCache->init()) {
+        rt->delete_<RegExpPrivateCache>(newCache);
+        return NULL;
+    }
+
+    repCache = newCache;
+    return repCache;
+}
+
+void
+ThreadData::purgeRegExpPrivateCache(JSRuntime *rt)
+{
+    if (!repCache)
+        return;
+
+    rt->delete_<RegExpPrivateCache>(repCache);
+    repCache = NULL;
+}
+
 } /* namespace js */
 
 JSScript *
 js_GetCurrentScript(JSContext *cx)
 {
 #ifdef JS_TRACER
     VOUCH_DOES_NOT_REQUIRE_STACK();
     if (JS_ON_TRACE(cx)) {
@@ -310,16 +336,34 @@ js_PurgeThreads(JSContext *cx)
             thread->data.purge(cx);
         }
     }
 #else
     cx->runtime->threadData.purge(cx);
 #endif
 }
 
+void
+js_PurgeThreads_PostGlobalSweep(JSContext *cx)
+{
+#ifdef JS_THREADSAFE
+    for (JSThread::Map::Enum e(cx->runtime->threads);
+         !e.empty();
+         e.popFront())
+    {
+        JSThread *thread = e.front().value;
+
+        JS_ASSERT(!JS_CLIST_IS_EMPTY(&thread->contextList));
+        thread->data.purgeRegExpPrivateCache(cx->runtime);
+    }
+#else
+    cx->runtime->threadData.purgeRegExpPrivateCache(cx->runtime);
+#endif
+}
+
 JSContext *
 js_NewContext(JSRuntime *rt, size_t stackChunkSize)
 {
     JS_AbortIfWrongThread(rt);
 
     /*
      * We need to initialize the new context fully before adding it to the
      * runtime list. After that it can be accessed from another thread via
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -192,16 +192,35 @@ struct ThreadData {
      * because we want allocations to be infallible (except when we hit OOM).
      */
     bool                waiveGCQuota;
 
     /* Temporary arena pool used while compiling and decompiling. */
     static const size_t TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 1 << 12;
     LifoAlloc           tempLifoAlloc;
 
+  private:
+    js::RegExpPrivateCache       *repCache;
+
+    js::RegExpPrivateCache *createRegExpPrivateCache(JSRuntime *rt);
+
+  public:
+    js::RegExpPrivateCache *getRegExpPrivateCache() { return repCache; }
+
+    /* N.B. caller is responsible for reporting OOM. */
+    js::RegExpPrivateCache *getOrCreateRegExpPrivateCache(JSRuntime *rt) {
+        if (repCache)
+            return repCache;
+
+        return createRegExpPrivateCache(rt);
+    }
+
+    /* Called at the end of the global GC sweep phase to deallocate repCache memory. */
+    void purgeRegExpPrivateCache(JSRuntime *rt);
+
     /*
      * The GSN cache is per thread since even multi-cx-per-thread embeddings
      * do not interleave js_GetSrcNote calls.
      */
     GSNCache            gsnCache;
 
     /* Property cache for faster call/get/set invocation. */
     PropertyCache       propertyCache;
@@ -1209,16 +1228,18 @@ struct JSContext
 #ifdef JS_THREADSAFE
     /*
      * When non-null JSContext::free_ delegates the job to the background
      * thread.
      */
     js::GCHelperThread *gcBackgroundFree;
 #endif
 
+    js::ThreadData *threadData() { return JS_THREAD_DATA(this); }
+
     inline void* malloc_(size_t bytes) {
         return runtime->malloc_(bytes, this);
     }
 
     inline void* mallocNoReport(size_t bytes) {
         JS_ASSERT(bytes != 0);
         return runtime->malloc_(bytes, NULL);
     }
@@ -1885,16 +1906,19 @@ extern JSBool
 js_InitThreads(JSRuntime *rt);
 
 extern void
 js_FinishThreads(JSRuntime *rt);
 
 extern void
 js_PurgeThreads(JSContext *cx);
 
+extern void
+js_PurgeThreads_PostGlobalSweep(JSContext *cx);
+
 namespace js {
 
 #ifdef JS_THREADSAFE
 
 /* Iterator over ThreadData from all JSThread instances. */
 class ThreadDataIter : public JSThread::Map::Range
 {
   public:
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -2905,16 +2905,19 @@ GCCycle(JSContext *cx, JSCompartment *co
     if (gckind != GC_LAST_CONTEXT && rt->state != JSRTS_LANDING) {
         if (rt->gcHelperThread.prepareForBackgroundSweep(cx))
             cx->gcBackgroundFree = &rt->gcHelperThread;
     }
 #endif
 
     MarkAndSweep(cx, gckind);
 
+    if (!comp)
+        js_PurgeThreads_PostGlobalSweep(cx);
+
 #ifdef JS_THREADSAFE
     if (gckind != GC_LAST_CONTEXT && rt->state != JSRTS_LANDING) {
         JS_ASSERT(cx->gcBackgroundFree == &rt->gcHelperThread);
         cx->gcBackgroundFree = NULL;
         rt->gcHelperThread.startBackgroundSweep(gckind == GC_SHRINK);
     } else {
         JS_ASSERT(!cx->gcBackgroundFree);
     }
--- a/js/src/jsprvtd.h
+++ b/js/src/jsprvtd.h
@@ -119,16 +119,17 @@ class JSWrapper;
 
 namespace js {
 
 struct ArgumentsData;
 struct Class;
 
 class RegExpObject;
 class RegExpPrivate;
+class RegExpObjectBuilder;
 class RegExpStatics;
 class MatchPairs;
 
 enum RegExpFlag
 {
     IgnoreCaseFlag  = 0x01,
     GlobalFlag      = 0x02,
     MultilineFlag   = 0x04,
@@ -227,16 +228,19 @@ typedef Vector<UpvarCookie, 8> UpvarCook
 
 class Breakpoint;
 class BreakpointSite;
 typedef HashMap<jsbytecode *, BreakpointSite *, DefaultHasher<jsbytecode *>, RuntimeAllocPolicy>
     BreakpointSiteMap;
 class Debugger;
 class WatchpointMap;
 
+typedef HashMap<JSAtom *, RegExpPrivate *, DefaultHasher<JSAtom *>, RuntimeAllocPolicy>
+    RegExpPrivateCache;
+
 typedef JSNative             Native;
 typedef JSPropertyOp         PropertyOp;
 typedef JSStrictPropertyOp   StrictPropertyOp;
 typedef JSPropertyDescriptor PropertyDescriptor;
 
 namespace analyze {
 
 struct LifetimeVariable;
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -1259,28 +1259,35 @@ class FlatMatch
 
     /*
      * Note: The match is -1 when the match is performed successfully,
      * but no match is found.
      */
     int32 match() const { return match_; }
 };
 
+/*
+ * Some string methods operate on a RegExpObject, if it is present, but if it
+ * is absent create an internal regular expression matcher. This unifies the
+ * interface.
+ */
 class RegExpPair
 {
+    JSContext                   *cx;
     AutoRefCount<RegExpPrivate> rep_;
     RegExpObject                *reobj_;
 
     explicit RegExpPair(RegExpPair &);
     void operator=(const RegExpPair &);
 
   public:
-    explicit RegExpPair(JSContext *cx) : rep_(cx) {}
+    explicit RegExpPair(JSContext *cx) : cx(cx), rep_(cx) {}
 
     bool resetWithObject(JSContext *cx, RegExpObject *reobj) {
+        JS_ASSERT(cx == this->cx);
         reobj_ = reobj;
         RegExpPrivate *rep = reobj_->asRegExp()->getOrCreatePrivate(cx);
         if (!rep)
             return false;
         rep_.reset(NeedsIncRef<RegExpPrivate>(rep));
         return true;
     }
 
@@ -1297,19 +1304,18 @@ class RegExpPair
         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.
+ * |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.
  */
 class RegExpGuard
 {
     RegExpGuard(const RegExpGuard &);
     void operator=(const RegExpGuard &);
 
     JSContext   *cx;
     RegExpPair  rep;
@@ -1363,18 +1369,19 @@ class RegExpGuard
         }
         return true;
     }
 
     /*
      * Attempt to match |patstr| to |textstr|. A flags argument, metachars in the
      * pattern string, or a lengthy pattern string can thwart this process.
      *
-     * @param checkMetaChars    Look for regexp metachars in the pattern string.
-     * @return                  Whether flat matching could be used.
+     * |checkMetaChars| looks for regexp metachars in the pattern string.
+     *
+     * 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.null())
--- a/js/src/vm/RegExpObject-inl.h
+++ b/js/src/vm/RegExpObject-inl.h
@@ -84,79 +84,72 @@ HasRegExpMetaChars(const jschar *chars, 
 {
     for (size_t i = 0; i < length; ++i) {
         if (IsRegExpMetaChar(chars[i]))
             return true;
     }
     return false;
 }
 
+inline void
+RegExpObject::setPrivate(RegExpPrivate *rep)
+{
+    JSObject::setPrivate(rep);
+}
+
 inline RegExpObject *
 RegExpObject::create(JSContext *cx, RegExpStatics *res, const jschar *chars, size_t length,
                      RegExpFlag flags, TokenStream *tokenStream)
 {
     RegExpFlag staticsFlags = res->getFlags();
     return createNoStatics(cx, chars, length, RegExpFlag(flags | staticsFlags), tokenStream);
 }
 
 inline RegExpObject *
 RegExpObject::createNoStatics(JSContext *cx, const jschar *chars, size_t length,
                               RegExpFlag flags, TokenStream *tokenStream)
 {
-    JSLinearString *source = js_NewStringCopyN(cx, chars, length);
+    JSAtom *source = js_AtomizeChars(cx, chars, length);
     if (!source)
         return NULL;
 
-    /* |NewBuiltinClassInstance| can GC. */
-    JS::Anchor<JSString *> anchor(source);
+    return createNoStatics(cx, source, flags, tokenStream);
+}
 
+inline RegExpObject *
+RegExpObject::createNoStatics(JSContext *cx, JSAtom *source, RegExpFlag flags,
+                              TokenStream *tokenStream)
+{
     if (!RegExpPrivateCode::checkSyntax(cx, tokenStream, source))
         return NULL;
 
-    JSObject *obj = NewBuiltinClassInstance(cx, &RegExpClass);
-    if (!obj)
-        return NULL;
+    RegExpObjectBuilder builder(cx);
+    return builder.build(source, flags);
+}
 
-    RegExpObject *reobj = obj->asRegExp();
-    return reobj->reset(cx, source, flags) ? reobj : NULL;
+inline void
+RegExpObject::purge(JSContext *cx)
+{
+    if (RegExpPrivate *rep = getPrivate()) {
+        rep->decref(cx);
+        setPrivate(NULL);
+    }
 }
 
 inline void
 RegExpObject::finalize(JSContext *cx)
 {
-    if (RegExpPrivate *rep = getPrivate())
-        rep->decref(cx);
+    purge(cx);
 #ifdef DEBUG
-    setPrivate((void *) 0x1); /* Non-null but still in the zero page. */
+    setPrivate((RegExpPrivate *) 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)
+RegExpObject::init(JSContext *cx, JSLinearString *source, RegExpFlag flags)
 {
     if (nativeEmpty()) {
         const js::Shape **shapep = &cx->compartment->initialRegExpShape;
         if (!*shapep) {
             *shapep = assignInitialShape(cx);
             if (!*shapep)
                 return false;
         }
@@ -169,46 +162,93 @@ RegExpObject::reset(JSContext *cx, JSLin
     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);
 
+    JS_ASSERT(!getPrivate());
     zeroLastIndex();
-    setPrivate(NULL);
     setSource(source);
     setGlobal(flags & GlobalFlag);
     setIgnoreCase(flags & IgnoreCaseFlag);
     setMultiline(flags & MultilineFlag);
     setSticky(flags & StickyFlag);
     return true;
 }
 
 /* RegExpPrivate inlines. */
 
 inline AlreadyIncRefed<RegExpPrivate>
 RegExpPrivate::create(JSContext *cx, JSLinearString *source, RegExpFlag flags, TokenStream *ts)
 {
     typedef AlreadyIncRefed<RegExpPrivate> RetType;
 
+    /*
+     * We choose to only cache |RegExpPrivate|s who have atoms as
+     * sources, under the unverified premise that non-atoms will have a
+     * low hit rate (both hit ratio and absolute number of hits).
+     */
+    bool cacheable = source->isAtom();
+
+    /*
+     * Refcount note: not all |RegExpPrivate|s are cached so we need to
+     * keep a refcount. The cache holds a "weak ref", where the
+     * |RegExpPrivate|'s deallocation decref will first cause it to
+     * remove itself from the cache.
+     */
+
+    JSRuntime *rt = cx->runtime;
+    RegExpPrivateCache *cache = NULL; /* Quell "may be used uninitialized". */
+    RegExpPrivateCache::AddPtr addPtr;
+    if (cacheable) {
+        cache = cx->threadData()->getOrCreateRegExpPrivateCache(rt);
+        if (!cache) {
+            js_ReportOutOfMemory(cx);
+            return RetType(NULL);
+        }
+
+        addPtr = cache->lookupForAdd(&source->asAtom());
+        if (addPtr) {
+            RegExpPrivate *cached = addPtr->value;
+            if (cached->getFlags() == flags) {
+                cached->incref(cx);
+                return RetType(cached);
+            }
+            /* Note: on flag mismatch, we clobber the existing entry. */
+        }
+    }
+
     JSLinearString *flatSource = source->ensureLinear(cx);
     if (!flatSource)
         return RetType(NULL);
 
-    RegExpPrivate *self = cx->new_<RegExpPrivate>(flatSource, flags, cx->compartment);
+    RegExpPrivate *self = cx->new_<RegExpPrivate>(flatSource, flags);
     if (!self)
         return RetType(NULL);
 
     if (!self->compile(cx, ts)) {
         Foreground::delete_(self);
         return RetType(NULL);
     }
 
+    if (cacheable) {
+        if (addPtr) {
+            JS_ASSERT(addPtr->key == &self->getSource()->asAtom());
+            addPtr->value = self;
+        } else {
+            if (!cache->add(addPtr, &source->asAtom(), self)) {
+                js_ReportOutOfMemory(cx);
+                return RetType(NULL);
+            }
+        }
+    }
+
     return RetType(self);
 }
 
 /* This function should be deleted once bad Android platforms phase out. See bug 604774. */
 inline bool
 RegExpPrivateCode::isJITRuntimeEnabled(JSContext *cx)
 {
 #if defined(ANDROID) && defined(JS_TRACER) && defined(JS_METHODJIT)
@@ -326,47 +366,30 @@ RegExpPrivateCode::execute(JSContext *cx
 
     JS_ASSERT(result >= 0);
     return RegExpRunStatus_Success;
 }
 
 inline void
 RegExpPrivate::incref(JSContext *cx)
 {
-#ifdef DEBUG
-    assertSameCompartment(cx, compartment);
-#endif
     ++refCount;
 }
 
 inline void
 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;
+    if (--refCount != 0)
+        return;
 
-    return makePrivate(cx) ? getPrivate() : NULL;
-}
+    RegExpPrivateCache *cache;
+    if (source->isAtom() && (cache = cx->threadData()->getRegExpPrivateCache())) {
+        RegExpPrivateCache::Ptr ptr = cache->lookup(&source->asAtom());
+        if (ptr && ptr->value == this)
+            cache->remove(ptr);
+    }
 
-inline RegExpPrivate *
-RegExpObject::getPrivate() const
-{
-    RegExpPrivate *rep = static_cast<RegExpPrivate *>(JSObject::getPrivate());
-#ifdef DEBUG
-    if (rep)
-        CompartmentChecker::check(compartment(), rep->compartment);
-#endif
-    return rep;
+    cx->delete_(this);
 }
 
 } /* namespace js */
 
 #endif
--- a/js/src/vm/RegExpObject.cpp
+++ b/js/src/vm/RegExpObject.cpp
@@ -54,16 +54,116 @@ 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);
 
+/* RegExpObjectBuilder */
+
+bool
+RegExpObjectBuilder::getOrCreate()
+{
+    if (reobj_)
+        return true;
+
+    JSObject *obj = NewBuiltinClassInstance(cx, &RegExpClass);
+    if (!obj)
+        return false;
+    obj->setPrivate(NULL);
+
+    reobj_ = obj->asRegExp();
+    return true;
+}
+
+bool
+RegExpObjectBuilder::getOrCreateClone(RegExpObject *proto)
+{
+    JS_ASSERT(!reobj_);
+
+    JSObject *clone = NewNativeClassInstance(cx, &RegExpClass, proto, proto->getParent());
+    if (!clone)
+        return false;
+    clone->setPrivate(NULL);
+
+    reobj_ = clone->asRegExp();
+    return true;
+}
+
+RegExpObject *
+RegExpObjectBuilder::build(AlreadyIncRefed<RegExpPrivate> rep)
+{
+    if (!getOrCreate()) {
+        rep->decref(cx);
+        return NULL;
+    }
+
+    reobj_->purge(cx);
+    if (!reobj_->init(cx, rep->getSource(), rep->getFlags())) {
+        rep->decref(cx);
+        return NULL;
+    }
+    reobj_->setPrivate(rep.get());
+
+    return reobj_;
+}
+
+RegExpObject *
+RegExpObjectBuilder::build(JSLinearString *source, RegExpFlag flags)
+{
+    if (!getOrCreate())
+        return NULL;
+
+    reobj_->purge(cx);
+    return reobj_->init(cx, source, flags) ? reobj_ : NULL;
+}
+
+RegExpObject *
+RegExpObjectBuilder::build(RegExpObject *other)
+{
+    RegExpPrivate *rep = other->getOrCreatePrivate(cx);
+    if (!rep)
+        return NULL;
+
+    /* Now, incref it for the RegExpObject being built. */
+    rep->incref(cx);
+    return build(AlreadyIncRefed<RegExpPrivate>(rep));
+}
+
+RegExpObject *
+RegExpObjectBuilder::clone(RegExpObject *other, RegExpObject *proto)
+{
+    if (!getOrCreateClone(proto))
+        return NULL;
+
+    /*
+     * 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|.
+     */
+    RegExpStatics *res = cx->regExpStatics();
+    RegExpFlag origFlags = other->getFlags();
+    RegExpFlag staticsFlags = res->getFlags();
+    if ((origFlags & staticsFlags) != staticsFlags) {
+        RegExpFlag newFlags = RegExpFlag(origFlags | staticsFlags);
+        return build(other->getSource(), newFlags);
+    }
+
+    RegExpPrivate *toShare = other->getOrCreatePrivate(cx);
+    if (!toShare)
+        return NULL;
+
+    toShare->incref(cx);
+    return build(AlreadyIncRefed<RegExpPrivate>(toShare));
+}
+
+/* MatchPairs */
+
 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);
@@ -125,26 +225,26 @@ RegExpPrivate::execute(JSContext *cx, co
     matchPairs->checkAgainst(origLength);
 
     *lastIndex = matchPairs->pair(0).limit;
     *output = matchPairs;
 
     return RegExpRunStatus_Success;
 }
 
-bool
+RegExpPrivate *
 RegExpObject::makePrivate(JSContext *cx)
 {
     JS_ASSERT(!getPrivate());
     AlreadyIncRefed<RegExpPrivate> rep = RegExpPrivate::create(cx, getSource(), getFlags(), NULL);
     if (!rep)
-        return false;
+        return NULL;
 
     setPrivate(rep.get());
-    return true;
+    return rep.get();
 }
 
 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;
@@ -204,23 +304,21 @@ js_XDRRegExpObject(JSXDRState *xdr, JSOb
         JS_ASSERT(objp);
         RegExpObject *reobj = (*objp)->asRegExp();
         source = reobj->getSource();
         flagsword = reobj->getFlags();
     }
     if (!JS_XDRString(xdr, &source) || !JS_XDRUint32(xdr, &flagsword))
         return false;
     if (xdr->mode == JSXDR_DECODE) {
-        JS::Anchor<JSString *> anchor(source);
-        const jschar *chars = source->getChars(xdr->cx);
-        if (!chars)
+        JSAtom *atom = js_AtomizeString(xdr->cx, source);
+        if (!atom)
             return false;
-        size_t len = source->length();
-        RegExpObject *reobj = RegExpObject::createNoStatics(xdr->cx, chars, len,
-                                                            RegExpFlag(flagsword), NULL);
+        RegExpObject *reobj = RegExpObject::createNoStatics(xdr->cx, atom, RegExpFlag(flagsword),
+                                                            NULL);
         if (!reobj)
             return false;
 
         reobj->clearParent();
         reobj->clearType();
         *objp = reobj;
     }
     return true;
@@ -233,16 +331,22 @@ js_XDRRegExpObject(JSXDRState *xdr, JSOb
 #endif /* !JS_HAS_XDR */
 
 static void
 regexp_finalize(JSContext *cx, JSObject *obj)
 {
     obj->asRegExp()->finalize(cx);
 }
 
+static void
+regexp_trace(JSTracer *trc, JSObject *obj)
+{
+    obj->asRegExp()->purge(trc->context);
+}
+
 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 */
     JS_PropertyStub,         /* delProperty */
     JS_PropertyStub,         /* getProperty */
@@ -252,17 +356,17 @@ Class js::RegExpClass = {
     JS_ConvertStub,
     regexp_finalize,
     NULL,                    /* reserved0 */
     NULL,                    /* checkAccess */
     NULL,                    /* call */
     NULL,                    /* construct */
     js_XDRRegExpObject,
     NULL,                    /* hasInstance */
-    NULL                     /* trace */
+    regexp_trace
 };
 
 #if ENABLE_YARR_JIT
 void
 RegExpPrivateCode::reportYarrError(JSContext *cx, TokenStream *ts, ErrorCode error)
 {
     switch (error) {
       case JSC::Yarr::NoError:
@@ -373,64 +477,24 @@ RegExpPrivate::create(JSContext *cx, JSL
 
     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 *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, 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|.
-     */
-    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;
-    }
-
-    RegExpPrivate *toShare = reobj->getOrCreatePrivate(cx);
-    if (!toShare)
-        return NULL;
-
-    toShare->incref(cx);
-    if (!reclone->reset(cx, AlreadyIncRefed<RegExpPrivate>(toShare))) {
-        toShare->decref(cx);
-        return NULL;
-    }
-
-    return reclone;
-}
-
 JSObject * JS_FASTCALL
 js_CloneRegExpObject(JSContext *cx, JSObject *obj, JSObject *proto)
 {
     JS_ASSERT(obj->isRegExp());
     JS_ASSERT(proto->isRegExp());
 
-    return RegExpObject::clone(cx, obj->asRegExp(), proto->asRegExp());
+    RegExpObjectBuilder builder(cx);
+    return builder.clone(obj->asRegExp(), proto->asRegExp());
 }
 
 #ifdef JS_TRACER
 JS_DEFINE_CALLINFO_3(extern, OBJECT, js_CloneRegExpObject, CONTEXT, OBJECT, OBJECT, 0,
                      ACCSET_STORE_ANY)
 #endif
 
 JSFlatString *
--- a/js/src/vm/RegExpObject.h
+++ b/js/src/vm/RegExpObject.h
@@ -77,23 +77,24 @@ class RegExpObject : public ::JSObject
 
     /*
      * Note: The regexp statics flags are OR'd into the provided flags,
      * so this function is really meant for object creation during code
      * execution, as opposed to during something like XDR.
      */
     static inline RegExpObject *
     create(JSContext *cx, RegExpStatics *res, const jschar *chars, size_t length, RegExpFlag flags,
-           TokenStream *ts);
+           TokenStream *tokenStream);
 
     static inline RegExpObject *
     createNoStatics(JSContext *cx, const jschar *chars, size_t length, RegExpFlag flags,
-                    TokenStream *ts);
+                    TokenStream *tokenStream);
 
-    static RegExpObject *clone(JSContext *cx, RegExpObject *obj, RegExpObject *proto);
+    static inline RegExpObject *
+    createNoStatics(JSContext *cx, JSAtom *atom, RegExpFlag flags, TokenStream *tokenStream);
 
     /* 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,
@@ -143,52 +144,86 @@ class RegExpObject : public ::JSObject
     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);
 
+    /* Clear out lazy |RegExpPrivate|. */
+    inline void purge(JSContext *x);
+
+    RegExpPrivate *getOrCreatePrivate(JSContext *cx) {
+        if (RegExpPrivate *rep = getPrivate())
+            return rep;
+
+        return makePrivate(cx);
+    }
+
   private:
+    friend class RegExpObjectBuilder;
+
+    inline bool init(JSContext *cx, JSLinearString *source, RegExpFlag flags);
+
     /* The |RegExpPrivate| is lazily created at the time of use. */
-    inline RegExpPrivate *getPrivate() const;
+    RegExpPrivate *getPrivate() const {
+        return static_cast<RegExpPrivate *>(JSObject::getPrivate());
+    }
+
+    inline void setPrivate(RegExpPrivate *rep);
 
     /*
      * Precondition: the syntax for |source| has already been validated.
      * Side effect: sets the private field.
      */
-    bool makePrivate(JSContext *cx);
+    RegExpPrivate *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 */
 
+/* Either builds a new RegExpObject or re-initializes an existing one. */
+class RegExpObjectBuilder
+{
+    JSContext       *cx;
+    RegExpObject    *reobj_;
+
+    bool getOrCreate();
+    bool getOrCreateClone(RegExpObject *proto);
+
+  public:
+    RegExpObjectBuilder(JSContext *cx, RegExpObject *reobj = NULL)
+      : cx(cx), reobj_(reobj)
+    { }
+
+    RegExpObject *reobj() { return reobj_; }
+
+    /* Note: In case of failure, |rep| will be decrefed. */
+    RegExpObject *build(AlreadyIncRefed<RegExpPrivate> rep);
+
+    RegExpObject *build(JSLinearString *str, RegExpFlag flags);
+    RegExpObject *build(RegExpObject *other);
+
+    /* Perform a VM-internal clone. */
+    RegExpObject *clone(RegExpObject *other, RegExpObject *proto);
+};
+
 /* 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;
@@ -238,59 +273,57 @@ class RegExpPrivateCode
 
 #if ENABLE_YARR_JIT
     static inline bool isJITRuntimeEnabled(JSContext *cx);
     static void reportYarrError(JSContext *cx, TokenStream *ts, JSC::Yarr::ErrorCode error);
 #else
     static void reportPCREError(JSContext *cx, int error);
 #endif
 
-    inline bool compile(JSContext *cx, JSLinearString &pattern, TokenStream *ts, uintN *parenCount,
-                        RegExpFlag flags);
-
-    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
     }
+
+    inline bool compile(JSContext *cx, JSLinearString &pattern, TokenStream *ts, uintN *parenCount,
+                        RegExpFlag flags);
+
+
+    inline RegExpRunStatus execute(JSContext *cx, const jschar *chars, size_t length, size_t start,
+                                   int *output, size_t outputCount);
 };
 
 /*
  * The "meat" of the builtin regular expression objects: it contains the
  * mini-program that represents the source of the regular expression.
  * Excepting refcounts, this is an immutable datastructure after
  * compilation.
  *
  * Non-atomic refcounting is used, so single-thread invariants must be
- * maintained: we check regexp operations are performed in a single
- * compartment.
+ * maintained. |RegExpPrivate|s are currently shared within a single
+ * |ThreadData|.
  *
  * Note: refCount cannot overflow because that would require more
  * referring regexp objects than there is space for in addressable
  * memory.
  */
 class RegExpPrivate
 {
     RegExpPrivateCode   code;
     JSLinearString      *source;
     size_t              refCount;
     uintN               parenCount;
     RegExpFlag          flags;
 
-  public:
-    DebugOnly<JSCompartment *> compartment;
-
   private:
-    RegExpPrivate(JSLinearString *source, RegExpFlag flags, JSCompartment *compartment)
-      : source(source), refCount(1), parenCount(0), flags(flags), compartment(compartment)
+    RegExpPrivate(JSLinearString *source, RegExpFlag flags)
+      : source(source), refCount(1), parenCount(0), flags(flags)
     { }
 
     JS_DECLARE_ALLOCATION_FRIENDS_FOR_PRIVATE_CONSTRUCTOR;
 
     bool compile(JSContext *cx, TokenStream *ts);
     static inline void checkMatchPairs(JSString *input, int *buf, size_t matchItemCount);
 
   public: