Bug 702426: Hoist executable allocator for better RegExp code sharing, take 2. (r=luke)
authorChris Leary <cdleary@mozilla.com>
Tue, 15 Nov 2011 14:33:54 -0800
changeset 81975 6157fc56d02ceb752695879005c9737d3b6c7695
parent 81974 666ba50edd30d23c3615c252ee0e4977b769582f
child 81976 cdf1844ffd59de32a87c25b67676f941e3ca0344
push idunknown
push userunknown
push dateunknown
reviewersluke
bugs702426
milestone11.0a1
Bug 702426: Hoist executable allocator for better RegExp code sharing, take 2. (r=luke)
js/src/jsapi.cpp
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jscompartment.cpp
js/src/jscompartment.h
js/src/jsprvtd.h
js/src/vm/RegExpObject-inl.h
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -718,16 +718,18 @@ JSRuntime::JSRuntime()
 #endif
     thousandsSeparator(0),
     decimalSeparator(0),
     numGrouping(0),
     anynameObject(NULL),
     functionNamespaceObject(NULL),
 #ifdef JS_THREADSAFE
     interruptCounter(0),
+#else
+    threadData(thisFromCtor()),
 #endif
     trustedPrincipals_(NULL),
     shapeGen(0),
     wrapObjectCallback(NULL),
     preWrapObjectCallback(NULL),
     inOOMReport(0)
 {
     /* Initialize infallibly first, so we can goto bad and JS_DestroyRuntime. */
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -82,69 +82,78 @@
 #include "jsstr.h"
 #include "jstracer.h"
 
 #ifdef JS_METHODJIT
 # include "assembler/assembler/MacroAssembler.h"
 #endif
 #include "frontend/TokenStream.h"
 #include "frontend/ParseMaps.h"
+#include "yarr/BumpPointerAllocator.h"
 
 #include "jsatominlines.h"
 #include "jscntxtinlines.h"
 #include "jscompartment.h"
 #include "jsobjinlines.h"
 
 using namespace js;
 using namespace js::gc;
 
 namespace js {
 
-ThreadData::ThreadData()
-  : interruptFlags(0),
+ThreadData::ThreadData(JSRuntime *rt)
+  : rt(rt),
+    interruptFlags(0),
 #ifdef JS_THREADSAFE
     requestDepth(0),
 #endif
 #ifdef JS_TRACER
     onTraceCompartment(NULL),
     recordingCompartment(NULL),
     profilingCompartment(NULL),
     maxCodeCacheBytes(DEFAULT_JIT_CACHE_SIZE),
 #endif
     waiveGCQuota(false),
     tempLifoAlloc(TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
+    execAlloc(NULL),
+    bumpAlloc(NULL),
     repCache(NULL),
     dtoaState(NULL),
     nativeStackBase(GetNativeStackBase()),
     pendingProxyOperation(NULL),
     interpreterFrames(NULL)
 {
 #ifdef DEBUG
     noGCOrAllocationCheck = 0;
 #endif
 }
 
 ThreadData::~ThreadData()
 {
     JS_ASSERT(!repCache);
 
+    rt->delete_<JSC::ExecutableAllocator>(execAlloc);
+    rt->delete_<WTF::BumpPointerAllocator>(bumpAlloc);
+
     if (dtoaState)
         js_DestroyDtoaState(dtoaState);
 }
 
 bool
 ThreadData::init()
 {
     JS_ASSERT(!repCache);
     return stackSpace.init() && !!(dtoaState = js_NewDtoaState());
 }
 
 void
 ThreadData::triggerOperationCallback(JSRuntime *rt)
 {
+    JS_ASSERT(rt == this->rt);
+
     /*
      * Use JS_ATOMIC_SET and JS_ATOMIC_INCREMENT in the hope that it ensures
      * the write will become immediately visible to other processors polling
      * the flag.  Note that we only care about visibility here, not read/write
      * ordering: this field can only be written with the GC lock held.
      */
     if (interruptFlags)
         return;
@@ -152,33 +161,60 @@ ThreadData::triggerOperationCallback(JSR
 
 #ifdef JS_THREADSAFE
     /* rt->interruptCounter does not reflect suspended threads. */
     if (requestDepth != 0)
         JS_ATOMIC_INCREMENT(&rt->interruptCounter);
 #endif
 }
 
+JSC::ExecutableAllocator *
+ThreadData::createExecutableAllocator(JSContext *cx)
+{
+    JS_ASSERT(!execAlloc);
+    JS_ASSERT(cx->runtime == rt);
+
+    execAlloc = rt->new_<JSC::ExecutableAllocator>();
+    if (!execAlloc)
+        js_ReportOutOfMemory(cx);
+    return execAlloc;
+}
+
+WTF::BumpPointerAllocator *
+ThreadData::createBumpPointerAllocator(JSContext *cx)
+{
+    JS_ASSERT(!bumpAlloc);
+    JS_ASSERT(cx->runtime == rt);
+
+    bumpAlloc = rt->new_<WTF::BumpPointerAllocator>();
+    if (!bumpAlloc)
+        js_ReportOutOfMemory(cx);
+    return bumpAlloc;
+}
+
 RegExpPrivateCache *
-ThreadData::createRegExpPrivateCache(JSRuntime *rt)
+ThreadData::createRegExpPrivateCache(JSContext *cx)
 {
     JS_ASSERT(!repCache);
+    JS_ASSERT(cx->runtime == rt);
+
     RegExpPrivateCache *newCache = rt->new_<RegExpPrivateCache>(rt);
 
     if (!newCache || !newCache->init()) {
+        js_ReportOutOfMemory(cx);
         rt->delete_<RegExpPrivateCache>(newCache);
         return NULL;
     }
 
     repCache = newCache;
     return repCache;
 }
 
 void
-ThreadData::purgeRegExpPrivateCache(JSRuntime *rt)
+ThreadData::purgeRegExpPrivateCache()
 {
     rt->delete_<RegExpPrivateCache>(repCache);
     repCache = NULL;
 }
 
 } /* namespace js */
 
 JSScript *
@@ -218,17 +254,17 @@ js_CurrentThreadAndLockGC(JSRuntime *rt)
          * If thread has no contexts, it might be left over from a previous
          * thread with the same id but a different stack address.
          */
         if (JS_CLIST_IS_EMPTY(&thread->contextList))
             thread->data.nativeStackBase = GetNativeStackBase();
     } else {
         JS_UNLOCK_GC(rt);
 
-        thread = OffTheBooks::new_<JSThread>(id);
+        thread = OffTheBooks::new_<JSThread>(rt, id);
         if (!thread || !thread->init()) {
             Foreground::delete_(thread);
             return NULL;
         }
         JS_LOCK_GC(rt);
         js_WaitForGC(rt);
         if (!rt->threads.relookupOrAdd(p, id, thread)) {
             JS_UNLOCK_GC(rt);
@@ -347,20 +383,20 @@ js_PurgeThreads_PostGlobalSweep(JSContex
 #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);
+        thread->data.purgeRegExpPrivateCache();
     }
 #else
-    cx->runtime->threadData.purgeRegExpPrivateCache(cx->runtime);
+    cx->runtime->threadData.purgeRegExpPrivateCache();
 #endif
 }
 
 JSContext *
 js_NewContext(JSRuntime *rt, size_t stackChunkSize)
 {
     JS_AbortIfWrongThread(rt);
 
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -148,16 +148,18 @@ inline GSNCache *
 GetGSNCache(JSContext *cx);
 
 struct PendingProxyOperation {
     PendingProxyOperation   *next;
     JSObject                *object;
 };
 
 struct ThreadData {
+    JSRuntime           *rt;
+
     /*
      * If non-zero, we were been asked to call the operation callback as soon
      * as possible.  If the thread has an active request, this contributes
      * towards rt->interruptCounter.
      */
     volatile int32      interruptFlags;
 
 #ifdef JS_THREADSAFE
@@ -193,33 +195,55 @@ struct ThreadData {
      */
     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);
+    /*
+     * Both of these allocators are used for regular expression code which is shared at the
+     * thread-data level.
+     */
+    JSC::ExecutableAllocator    *execAlloc;
+    WTF::BumpPointerAllocator   *bumpAlloc;
+    js::RegExpPrivateCache      *repCache;
+
+    JSC::ExecutableAllocator *createExecutableAllocator(JSContext *cx);
+    WTF::BumpPointerAllocator *createBumpPointerAllocator(JSContext *cx);
+    js::RegExpPrivateCache *createRegExpPrivateCache(JSContext *cx);
 
   public:
-    js::RegExpPrivateCache *getRegExpPrivateCache() { return repCache; }
-
-    /* N.B. caller is responsible for reporting OOM. */
-    js::RegExpPrivateCache *getOrCreateRegExpPrivateCache(JSRuntime *rt) {
+    JSC::ExecutableAllocator *getOrCreateExecutableAllocator(JSContext *cx) {
+        if (execAlloc)
+            return execAlloc;
+
+        return createExecutableAllocator(cx);
+    }
+
+    WTF::BumpPointerAllocator *getOrCreateBumpPointerAllocator(JSContext *cx) {
+        if (bumpAlloc)
+            return bumpAlloc;
+
+        return createBumpPointerAllocator(cx);
+    }
+
+    js::RegExpPrivateCache *getRegExpPrivateCache() {
+        return repCache;
+    }
+    js::RegExpPrivateCache *getOrCreateRegExpPrivateCache(JSContext *cx) {
         if (repCache)
             return repCache;
 
-        return createRegExpPrivateCache(rt);
+        return createRegExpPrivateCache(cx);
     }
 
     /* Called at the end of the global GC sweep phase to deallocate repCache memory. */
-    void purgeRegExpPrivateCache(JSRuntime *rt);
+    void purgeRegExpPrivateCache();
 
     /*
      * 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. */
@@ -235,17 +259,17 @@ struct ThreadData {
     PendingProxyOperation *pendingProxyOperation;
 
     ConservativeGCThreadData conservativeGC;
 
 #ifdef DEBUG
     size_t              noGCOrAllocationCheck;
 #endif
 
-    ThreadData();
+    ThreadData(JSRuntime *rt);
     ~ThreadData();
 
     bool init();
 
     void mark(JSTracer *trc) {
         stackSpace.mark(trc);
     }
 
@@ -292,22 +316,23 @@ struct JSThread {
 
 # ifdef DEBUG
     unsigned            checkRequestDepth;
 # endif
 
     /* Factored out of JSThread for !JS_THREADSAFE embedding in JSRuntime. */
     js::ThreadData      data;
 
-    JSThread(void *id)
+    JSThread(JSRuntime *rt, void *id)
       : id(id),
-        suspendCount(0)
+        suspendCount(0),
 # ifdef DEBUG
-      , checkRequestDepth(0)
+        checkRequestDepth(0),
 # endif
+        data(rt)
     {
         JS_INIT_CLIST(&contextList);
     }
 
     ~JSThread() {
         /* The thread must have zero contexts. */
         JS_ASSERT(JS_CLIST_IS_EMPTY(&contextList));
     }
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -83,19 +83,16 @@ JSCompartment::JSCompartment(JSRuntime *
     traceMonitor_(NULL),
 #endif
     data(NULL),
     active(false),
     hasDebugModeCodeToDrop(false),
 #ifdef JS_METHODJIT
     jaegerCompartment_(NULL),
 #endif
-#if ENABLE_YARR_JIT
-    regExpAllocator(NULL),
-#endif
     propertyTree(thisForCtor()),
     emptyArgumentsShape(NULL),
     emptyBlockShape(NULL),
     emptyCallShape(NULL),
     emptyDeclEnvShape(NULL),
     emptyEnumeratorShape(NULL),
     emptyWithShape(NULL),
     initialRegExpShape(NULL),
@@ -105,20 +102,16 @@ JSCompartment::JSCompartment(JSRuntime *
     breakpointSites(rt),
     watchpointMap(NULL)
 {
     PodArrayZero(evalCache);
 }
 
 JSCompartment::~JSCompartment()
 {
-#if ENABLE_YARR_JIT
-    Foreground::delete_(regExpAllocator);
-#endif
-
 #ifdef JS_METHODJIT
     Foreground::delete_(jaegerCompartment_);
 #endif
 
 #ifdef JS_TRACER
     Foreground::delete_(traceMonitor_);
 #endif
 
@@ -138,20 +131,16 @@ JSCompartment::init(JSContext *cx)
     types.init(cx);
 
     if (!crossCompartmentWrappers.init())
         return false;
 
     if (!scriptFilenameTable.init())
         return false;
 
-    regExpAllocator = rt->new_<WTF::BumpPointerAllocator>();
-    if (!regExpAllocator)
-        return false;
-
     if (!backEdgeTable.init())
         return false;
 
     return debuggees.init() && breakpointSites.init();
 }
 
 #ifdef JS_METHODJIT
 bool
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -48,19 +48,16 @@
 #include "jsobj.h"
 #include "vm/GlobalObject.h"
 
 #ifdef _MSC_VER
 #pragma warning(push)
 #pragma warning(disable:4251) /* Silence warning about JS_FRIEND_API and data members. */
 #endif
 
-namespace JSC { class ExecutableAllocator; }
-namespace WTF { class BumpPointerAllocator; }
-
 namespace js {
 
 /* Holds the number of recording attemps for an address. */
 typedef HashMap<jsbytecode*,
                 size_t,
                 DefaultHasher<jsbytecode*>,
                 SystemAllocPolicy> RecordAttemptMap;
 
@@ -468,17 +465,16 @@ struct JS_FRIEND_API(JSCompartment) {
         JS_ASSERT(jaegerCompartment_);
         return jaegerCompartment_;
     }
 
     bool ensureJaegerCompartmentExists(JSContext *cx);
 
     void getMjitCodeStats(size_t& method, size_t& regexp, size_t& unused) const;
 #endif
-    WTF::BumpPointerAllocator    *regExpAllocator;
 
     /*
      * Shared scope property tree, and arena-pool for allocating its nodes.
      */
     js::PropertyTree             propertyTree;
 
 #ifdef DEBUG
     /* Property metering. */
--- a/js/src/jsprvtd.h
+++ b/js/src/jsprvtd.h
@@ -264,16 +264,28 @@ class TypeSet;
 struct TypeCallsite;
 struct TypeObject;
 struct TypeCompartment;
 
 } /* namespace types */
 
 } /* namespace js */
 
+namespace JSC {
+
+class ExecutableAllocator;
+
+} /* namespace JSC */
+
+namespace WTF {
+
+class BumpPointerAllocator;
+
+} /* namespace WTF */
+
 } /* export "C++" */
 
 #else
 
 typedef struct JSAtom JSAtom;
 
 #endif  /* __cplusplus */
 
--- a/js/src/vm/RegExpObject-inl.h
+++ b/js/src/vm/RegExpObject-inl.h
@@ -243,17 +243,17 @@ RegExpObject::setSticky(bool enabled)
     setSlot(STICKY_FLAG_SLOT, BooleanValue(enabled));
 }
 
 /* RegExpPrivate inlines. */
 
 inline RegExpPrivateCache *
 detail::RegExpPrivate::getOrCreateCache(JSContext *cx)
 {
-    if (RegExpPrivateCache *cache = cx->threadData()->getOrCreateRegExpPrivateCache(cx->runtime))
+    if (RegExpPrivateCache *cache = cx->threadData()->getOrCreateRegExpPrivateCache(cx))
         return cache;
 
     js_ReportOutOfMemory(cx);
     return NULL;
 }
 
 inline bool
 detail::RegExpPrivate::cacheLookup(JSContext *cx, JSAtom *atom, RegExpFlag flags,
@@ -374,28 +374,37 @@ detail::RegExpPrivateCode::compile(JSCon
     /*
      * The YARR JIT compiler attempts to compile the parsed pattern. If
      * it cannot, it informs us via |codeBlock.isFallBack()|, in which
      * case we have to bytecode compile it.
      */
 
 #ifdef JS_METHODJIT
     if (isJITRuntimeEnabled(cx) && !yarrPattern.m_containsBackreferences) {
-        if (!cx->compartment->ensureJaegerCompartmentExists(cx))
+        JSC::ExecutableAllocator *execAlloc = cx->threadData()->getOrCreateExecutableAllocator(cx);
+        if (!execAlloc) {
+            js_ReportOutOfMemory(cx);
             return false;
+        }
 
-        JSGlobalData globalData(cx->compartment->jaegerCompartment()->execAlloc());
+        JSGlobalData globalData(execAlloc);
         jitCompile(yarrPattern, &globalData, codeBlock);
         if (!codeBlock.isFallBack())
             return true;
     }
 #endif
 
+    WTF::BumpPointerAllocator *bumpAlloc = cx->threadData()->getOrCreateBumpPointerAllocator(cx);
+    if (!bumpAlloc) {
+        js_ReportOutOfMemory(cx);
+        return false;
+    }
+
     codeBlock.setFallBack(true);
-    byteCode = byteCompile(yarrPattern, cx->compartment->regExpAllocator).get();
+    byteCode = byteCompile(yarrPattern, bumpAlloc).get();
     return true;
 #else /* !defined(ENABLE_YARR_JIT) */
     int error = 0;
     compiled = jsRegExpCompile(pattern.chars(), pattern.length(),
                   ignoreCase() ? JSRegExpIgnoreCase : JSRegExpDoNotIgnoreCase,
                   multiline() ? JSRegExpMultiline : JSRegExpSingleLine,
                   parenCount, &error);
     if (error) {