From 24dee0ab532e9a666a961aded9b82ed3789969c2 Mon Sep 17 00:00:00 2001 From: Igor Bukanov Date: Mon, 7 Jun 2010 11:17:15 +0200 Subject: [PATCH] bug 557538 - custom GC chunk allocation. r=gal,jorendorff --- js/src/Makefile.in | 1 + js/src/jsapi-tests/Makefile.in | 2 + js/src/jsapi-tests/testGCChunkAlloc.cpp | 111 ++++++++++++++++++++++++ js/src/jsapi-tests/tests.h | 8 +- js/src/jsapi.cpp | 1 + js/src/jscntxt.h | 9 ++ js/src/jsgc.cpp | 4 +- js/src/jsgcchunk.cpp | 6 +- js/src/jsgcchunk.h | 36 +++++++- 9 files changed, 171 insertions(+), 7 deletions(-) create mode 100644 js/src/jsapi-tests/testGCChunkAlloc.cpp diff --git a/js/src/Makefile.in b/js/src/Makefile.in index 14132d78e8a..c2de2fd6432 100644 --- a/js/src/Makefile.in +++ b/js/src/Makefile.in @@ -197,6 +197,7 @@ INSTALLED_HEADERS = \ jsemit.h \ jsfun.h \ jsgc.h \ + jsgcchunk.h \ jshash.h \ jsinterp.h \ jsinttypes.h \ diff --git a/js/src/jsapi-tests/Makefile.in b/js/src/jsapi-tests/Makefile.in index 22f317ebbf0..d6c3a2f2e49 100644 --- a/js/src/jsapi-tests/Makefile.in +++ b/js/src/jsapi-tests/Makefile.in @@ -45,6 +45,7 @@ VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk PROGRAM = jsapi-tests$(BIN_SUFFIX) + CPPSRCS = \ tests.cpp \ selfTest.cpp \ @@ -53,6 +54,7 @@ CPPSRCS = \ testDefineGetterSetterNonEnumerable.cpp \ testDefineProperty.cpp \ testExtendedEq.cpp \ + testGCChunkAlloc.cpp \ testIntString.cpp \ testIsAboutToBeFinalized.cpp \ testLookup.cpp \ diff --git a/js/src/jsapi-tests/testGCChunkAlloc.cpp b/js/src/jsapi-tests/testGCChunkAlloc.cpp new file mode 100644 index 00000000000..828e31e9826 --- /dev/null +++ b/js/src/jsapi-tests/testGCChunkAlloc.cpp @@ -0,0 +1,111 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=4 sw=4 et tw=99: + * + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + * Contributor: Igor Bukanov + */ + +#include "tests.h" +#include "jsgcchunk.h" +#include "jscntxt.h" + +/* We allow to allocate only single chunk. */ + +class CustomGCChunkAllocator: public js::GCChunkAllocator { + public: + CustomGCChunkAllocator() : pool(NULL) {} + void *pool; + + private: + + virtual void *doAlloc() { + if (!pool) + return NULL; + void *chunk = pool; + pool = NULL; + return chunk; + } + + virtual void doFree(void *chunk) { + JS_ASSERT(!pool); + pool = chunk; + } +}; + +static CustomGCChunkAllocator customGCChunkAllocator; + +static unsigned errorCount = 0; + +static void +ErrorCounter(JSContext *cx, const char *message, JSErrorReport *report) +{ + ++errorCount; +} + +BEGIN_TEST(testGCChunkAlloc) +{ + JS_SetErrorReporter(cx, ErrorCounter); + + jsvalRoot root(cx); + + /* + * We loop until out-of-memory happens during the chunk allocation. But + * we have to disable the jit since it cannot tolerate OOM during the + * chunk allocation. + */ + JS_ToggleOptions(cx, JSOPTION_JIT); + + static const char source[] = + "var max = 0; (function() {" + " var array = [];" + " for (; ; ++max)" + " array.push(max + 0.1);" + "})();"; + JSBool ok = JS_EvaluateScript(cx, global, source, strlen(source), "", 1, + root.addr()); + + /* Check that we get OOM. */ + CHECK(!ok); + CHECK(!JS_IsExceptionPending(cx)); + CHECK(errorCount == 1); + CHECK(!customGCChunkAllocator.pool); + JS_GC(cx); + JS_ToggleOptions(cx, JSOPTION_JIT); + EVAL("(function() {" + " var array = [];" + " for (var i = max >> 1; i != 0;) {" + " --i;" + " array.push(i + 0.1);" + " }" + "})();", root.addr()); + CHECK(errorCount == 1); + return true; +} + +virtual JSRuntime * createRuntime() { + /* + * To test failure of chunk allocation allow to use GC twice the memory + * the single chunk contains. + */ + JSRuntime *rt = JS_NewRuntime(2 * js::GC_CHUNK_SIZE); + if (!rt) + return NULL; + + customGCChunkAllocator.pool = js::AllocGCChunk(); + JS_ASSERT(customGCChunkAllocator.pool); + + rt->setCustomGCChunkAllocator(&customGCChunkAllocator); + return rt; +} + +virtual void destroyRuntime() { + JS_DestroyRuntime(rt); + + /* We should get the initial chunk back at this point. */ + JS_ASSERT(customGCChunkAllocator.pool); + js::FreeGCChunk(customGCChunkAllocator.pool); + customGCChunkAllocator.pool = NULL; +} + +END_TEST(testGCChunkAlloc) diff --git a/js/src/jsapi-tests/tests.h b/js/src/jsapi-tests/tests.h index d094b3747f0..f45ee2c9062 100644 --- a/js/src/jsapi-tests/tests.h +++ b/js/src/jsapi-tests/tests.h @@ -141,7 +141,7 @@ public: cx = NULL; } if (rt) { - JS_DestroyRuntime(rt); + destroyRuntime(); rt = NULL; } } @@ -214,6 +214,12 @@ protected: return JS_NewRuntime(8L * 1024 * 1024); } + virtual void destroyRuntime() { + JS_ASSERT(!cx); + JS_ASSERT(rt); + JS_DestroyRuntime(rt); + } + static void reportError(JSContext *cx, const char *message, JSErrorReport *report) { fprintf(stderr, "%s:%u:%s\n", report->filename ? report->filename : "", diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index a41bf969fc0..083914d5083 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -539,6 +539,7 @@ static JSBool js_NewRuntimeWasCalled = JS_FALSE; #endif JSRuntime::JSRuntime() + : gcChunkAllocator(&defaultGCChunkAllocator) { /* Initialize infallibly first, so we can goto bad and JS_DestroyRuntime. */ JS_INIT_CLIST(&contextList); diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index b33387a36a0..76d4dbe07e3 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -59,6 +59,7 @@ #include "jsdhash.h" #include "jsdtoa.h" #include "jsgc.h" +#include "jsgcchunk.h" #include "jshashtable.h" #include "jsinterp.h" #include "jsobj.h" @@ -1272,6 +1273,14 @@ struct JSRuntime { JSBackgroundThread gcHelperThread; #endif + js::GCChunkAllocator *gcChunkAllocator; + + void setCustomGCChunkAllocator(js::GCChunkAllocator *allocator) { + JS_ASSERT(allocator); + JS_ASSERT(state == JSRTS_DOWN); + gcChunkAllocator = allocator; + } + /* * The trace operation and its data argument to trace embedding-specific * GC roots. diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index e8b0c921bab..1fa11fa3843 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -610,7 +610,7 @@ static jsrefcount destroyChunkCount = 0; inline void * GetGCChunk(JSRuntime *rt) { - void *p = AllocGCChunk(); + void *p = rt->gcChunkAllocator->alloc(); #ifdef MOZ_GCTIMER if (p) JS_ATOMIC_INCREMENT(&newChunkCount); @@ -630,7 +630,7 @@ ReleaseGCChunk(JSRuntime *rt, jsuword chunk) #endif JS_ASSERT(rt->gcStats.nchunks != 0); METER(rt->gcStats.nchunks--); - FreeGCChunk(p); + rt->gcChunkAllocator->free(p); } static JSGCArena * diff --git a/js/src/jsgcchunk.cpp b/js/src/jsgcchunk.cpp index 283f317f348..495ad761009 100644 --- a/js/src/jsgcchunk.cpp +++ b/js/src/jsgcchunk.cpp @@ -242,6 +242,8 @@ UnmapPages(void *addr, size_t size) namespace js { +GCChunkAllocator defaultGCChunkAllocator; + inline void * FindChunkStart(void *p) { @@ -250,7 +252,7 @@ FindChunkStart(void *p) return reinterpret_cast(addr); } -void * +JS_FRIEND_API(void *) AllocGCChunk() { void *p; @@ -297,7 +299,7 @@ AllocGCChunk() return p; } -void +JS_FRIEND_API(void) FreeGCChunk(void *p) { JS_ASSERT(p); diff --git a/js/src/jsgcchunk.h b/js/src/jsgcchunk.h index bb8622d9b68..11fe250cc73 100644 --- a/js/src/jsgcchunk.h +++ b/js/src/jsgcchunk.h @@ -55,12 +55,44 @@ const size_t GC_CHUNK_SHIFT = 20; const size_t GC_CHUNK_SIZE = size_t(1) << GC_CHUNK_SHIFT; const size_t GC_CHUNK_MASK = GC_CHUNK_SIZE - 1; -void * +JS_FRIEND_API(void *) AllocGCChunk(); -void +JS_FRIEND_API(void) FreeGCChunk(void *p); +class GCChunkAllocator { + public: + GCChunkAllocator() {} + + void *alloc() { + void *chunk = doAlloc(); + JS_ASSERT(!(reinterpret_cast(chunk) & GC_CHUNK_MASK)); + return chunk; + } + + void free(void *chunk) { + JS_ASSERT(chunk); + JS_ASSERT(!(reinterpret_cast(chunk) & GC_CHUNK_MASK)); + doFree(chunk); + } + + private: + virtual void *doAlloc() { + return AllocGCChunk(); + } + + virtual void doFree(void *chunk) { + FreeGCChunk(chunk); + } + + /* No copy or assignment semantics. */ + GCChunkAllocator(const GCChunkAllocator &); + void operator=(const GCChunkAllocator &); +}; + +extern GCChunkAllocator defaultGCChunkAllocator; + } #endif /* jsgchunk_h__ */