bug 519476 - replacing JSSTRING_DEFLATED with scanning of the deflated cache. r=jwalden,dmandelin

This commit is contained in:
Igor Bukanov 2010-03-13 10:01:09 +03:00
parent b84116c011
commit e8a9de6f10
9 changed files with 216 additions and 232 deletions

View File

@ -565,10 +565,14 @@ JSRuntime::init(uint32 maxbytes)
{
if (!js_InitDtoa() ||
!js_InitGC(this, maxbytes) ||
!js_InitAtomState(this) ||
!js_InitDeflatedStringCache(this)) {
!js_InitAtomState(this)) {
return false;
}
deflatedStringCache = new js::DeflatedStringCache();
if (!deflatedStringCache || !deflatedStringCache->init())
return false;
#ifdef JS_THREADSAFE
gcLock = JS_NEW_LOCK();
if (!gcLock)
@ -629,7 +633,7 @@ JSRuntime::~JSRuntime()
* Finish the deflated string cache after the last GC and after
* calling js_FinishAtomState, which finalizes strings.
*/
js_FinishDeflatedStringCache(this);
delete deflatedStringCache;
js_FinishGC(this);
#ifdef JS_THREADSAFE
if (gcLock)
@ -5063,7 +5067,7 @@ JS_NewString(JSContext *cx, char *bytes, size_t nbytes)
}
/* Hand off bytes to the deflated string cache, if possible. */
if (!js_SetStringBytes(cx, str, bytes, nbytes))
if (!cx->runtime->deflatedStringCache->setBytes(cx, str, bytes))
cx->free(bytes);
return str;
}
@ -5192,7 +5196,7 @@ JS_GetStringChars(JSString *str)
if (s) {
memcpy(s, str->dependentChars(), n * sizeof *s);
s[n] = 0;
str->reinitFlat(s, n);
str->initFlat(s, n);
} else {
s = str->dependentChars();
}

View File

@ -501,6 +501,7 @@ js_InitCommonAtoms(JSContext *cx)
JS_ASSERT((uint8 *)atoms - (uint8 *)state == LAZY_ATOM_OFFSET_START);
memset(atoms, 0, ATOM_OFFSET_LIMIT - LAZY_ATOM_OFFSET_START);
cx->runtime->emptyString = ATOM_TO_STRING(state->emptyAtom);
return JS_TRUE;
}
@ -516,8 +517,8 @@ js_atom_unpinner(JSDHashTable *table, JSDHashEntryHdr *hdr,
void
js_FinishCommonAtoms(JSContext *cx)
{
cx->runtime->emptyString = NULL;
JSAtomState *state = &cx->runtime->atomState;
JS_DHashTableEnumerate(&state->stringAtoms, js_atom_unpinner, NULL);
#ifdef DEBUG
memset(COMMON_ATOMS_START(state), JS_FREE_PATTERN,

View File

@ -545,8 +545,6 @@ js_NewContext(JSRuntime *rt, size_t stackChunkSize)
ok = js_InitRuntimeScriptState(rt);
if (ok)
ok = js_InitRuntimeNumberState(cx);
if (ok)
ok = js_InitRuntimeStringState(cx);
if (ok)
ok = JSScope::initRuntimeState(cx);
@ -778,7 +776,6 @@ js_DestroyContext(JSContext *cx, JSDestroyContextMode mode)
JSScope::finishRuntimeState(cx);
js_FinishRuntimeNumberState(cx);
js_FinishRuntimeStringState(cx);
/* Unpin all common atoms before final GC. */
js_FinishCommonAtoms(cx);

View File

@ -820,13 +820,7 @@ struct JSRuntime {
jsval negativeInfinityValue;
jsval positiveInfinityValue;
#ifdef JS_THREADSAFE
JSLock *deflatedStringCacheLock;
#endif
JSHashTable *deflatedStringCache;
#ifdef DEBUG
uint32 deflatedStringCacheBytes;
#endif
js::DeflatedStringCache *deflatedStringCache;
JSString *emptyString;

View File

@ -2622,8 +2622,6 @@ FinalizeString(JSContext *cx, JSString *str, unsigned thingKind)
*/
cx->free(str->flatChars());
}
if (str->isDeflated())
js_PurgeDeflatedStringCache(cx->runtime, str);
}
inline void
@ -2643,8 +2641,6 @@ FinalizeExternalString(JSContext *cx, JSString *str, unsigned thingKind)
JSStringFinalizeOp finalizer = str_finalizers[type];
if (finalizer)
finalizer(cx, str);
if (str->isDeflated())
js_PurgeDeflatedStringCache(cx->runtime, str);
}
/*
@ -2686,8 +2682,6 @@ js_FinalizeStringRT(JSRuntime *rt, JSString *str)
}
}
}
if (str->isDeflated())
js_PurgeDeflatedStringCache(rt, str);
}
template<typename T,
@ -3195,6 +3189,13 @@ js_GC(JSContext *cx, JSGCInvocationKind gckind)
#if JS_HAS_XML_SUPPORT
FinalizeArenaList<JSXML, FinalizeXML>(cx, FINALIZE_XML, &emptyArenas);
#endif
/*
* We sweep the deflated cache before we finalize the strings so the
* cache can safely use js_IsAboutToBeFinalized..
*/
rt->deflatedStringCache->sweep(cx);
FinalizeArenaList<JSString, FinalizeString>
(cx, FINALIZE_STRING, &emptyArenas);
for (unsigned i = FINALIZE_EXTERNAL_STRING0;

View File

@ -172,6 +172,8 @@ template <class T,
class AllocPolicy = ContextAllocPolicy>
class HashSet;
class DeflatedStringCache;
} /* namespace js */
/* Common instantiations. */

View File

@ -104,7 +104,7 @@ MinimizeDependentStrings(JSString *str, int level, JSString **basep)
} while (base->isDependent());
}
length = str->dependentLength();
str->reinitDependent(base, start, length);
str->initDependent(base, start, length);
}
*basep = base;
return start;
@ -187,7 +187,7 @@ js_ConcatStrings(JSContext *cx, JSString *left, JSString *right)
/* Morph left into a dependent string if we realloc'd its buffer. */
if (ldep) {
ldep->reinitDependent(str, 0, ln);
ldep->initDependent(str, 0, ln);
#ifdef DEBUG
{
JSRuntime *rt = cx->runtime;
@ -219,7 +219,7 @@ js_UndependString(JSContext *cx, JSString *str)
js_strncpy(s, str->dependentChars(), n);
s[n] = 0;
str->reinitFlat(s, n);
str->initFlat(s, n);
#ifdef DEBUG
{
@ -3015,66 +3015,6 @@ static JSFunctionSpec string_static_methods[] = {
JS_FS_END
};
static JSHashNumber
js_hash_string_pointer(const void *key)
{
return (JSHashNumber)JS_PTR_TO_UINT32(key) >> JSVAL_TAGBITS;
}
JSBool
js_InitRuntimeStringState(JSContext *cx)
{
JSRuntime *rt;
rt = cx->runtime;
rt->emptyString = ATOM_TO_STRING(rt->atomState.emptyAtom);
return JS_TRUE;
}
JSBool
js_InitDeflatedStringCache(JSRuntime *rt)
{
JSHashTable *cache;
/* Initialize string cache */
JS_ASSERT(!rt->deflatedStringCache);
cache = JS_NewHashTable(8, js_hash_string_pointer,
JS_CompareValues, JS_CompareValues,
NULL, NULL);
if (!cache)
return JS_FALSE;
rt->deflatedStringCache = cache;
#ifdef JS_THREADSAFE
JS_ASSERT(!rt->deflatedStringCacheLock);
rt->deflatedStringCacheLock = JS_NEW_LOCK();
if (!rt->deflatedStringCacheLock)
return JS_FALSE;
#endif
return JS_TRUE;
}
void
js_FinishRuntimeStringState(JSContext *cx)
{
cx->runtime->emptyString = NULL;
}
void
js_FinishDeflatedStringCache(JSRuntime *rt)
{
if (rt->deflatedStringCache) {
JS_HashTableDestroy(rt->deflatedStringCache);
rt->deflatedStringCache = NULL;
}
#ifdef JS_THREADSAFE
if (rt->deflatedStringCacheLock) {
JS_DESTROY_LOCK(rt->deflatedStringCacheLock);
rt->deflatedStringCacheLock = NULL;
}
#endif
}
JSObject *
js_InitStringClass(JSContext *cx, JSObject *obj)
{
@ -3264,26 +3204,6 @@ js_NewStringCopyZ(JSContext *cx, const jschar *s)
return str;
}
void
js_PurgeDeflatedStringCache(JSRuntime *rt, JSString *str)
{
JSHashNumber hash;
JSHashEntry *he, **hep;
hash = js_hash_string_pointer(str);
JS_ACQUIRE_LOCK(rt->deflatedStringCacheLock);
hep = JS_HashTableRawLookup(rt->deflatedStringCache, hash, str);
he = *hep;
if (he) {
#ifdef DEBUG
rt->deflatedStringCacheBytes -= str->length();
#endif
js_free(he->value);
JS_HashTableRawRemove(rt->deflatedStringCache, hep, he);
}
JS_RELEASE_LOCK(rt->deflatedStringCacheLock);
}
JS_FRIEND_API(const char *)
js_ValueToPrintable(JSContext *cx, jsval v, JSValueToStringFun v2sfun)
{
@ -3794,42 +3714,154 @@ bufferTooSmall:
return JS_FALSE;
}
JSBool
js_SetStringBytes(JSContext *cx, JSString *str, char *bytes, size_t length)
namespace js {
DeflatedStringCache::DeflatedStringCache()
{
JSRuntime *rt;
JSHashTable *cache;
JSBool ok;
JSHashNumber hash;
JSHashEntry **hep;
rt = cx->runtime;
JS_ACQUIRE_LOCK(rt->deflatedStringCacheLock);
cache = rt->deflatedStringCache;
hash = js_hash_string_pointer(str);
hep = JS_HashTableRawLookup(cache, hash, str);
JS_ASSERT(*hep == NULL);
ok = JS_HashTableRawAdd(cache, hep, hash, str, bytes) != NULL;
if (ok) {
str->setDeflated();
#ifdef DEBUG
rt->deflatedStringCacheBytes += length;
#ifdef JS_THREADSAFE
lock = NULL;
#endif
}
bool
DeflatedStringCache::init()
{
#ifdef JS_THREADSAFE
JS_ASSERT(!lock);
lock = JS_NEW_LOCK();
if (!lock)
return false;
#endif
/*
* Make room for 2K deflated strings that a typical browser session
* creates.
*/
return map.init(2048);
}
DeflatedStringCache::~DeflatedStringCache()
{
#ifdef JS_THREADSAFE
if (lock)
JS_DESTROY_LOCK(lock);
#endif
}
void
DeflatedStringCache::sweep(JSContext *cx)
{
/*
* We must take a lock even during the GC as JS_GetStringBytes() can be
* called outside the request.
*/
JS_ACQUIRE_LOCK(lock);
for (Map::Enum e(map); !e.empty(); e.popFront()) {
JSString *str = e.front().key;
if (js_IsAboutToBeFinalized(str)) {
char *chars = e.front().value;
e.removeFront();
cx->free(chars);
}
}
JS_RELEASE_LOCK(rt->deflatedStringCacheLock);
JS_RELEASE_LOCK(lock);
}
void
DeflatedStringCache::remove(JSString *str)
{
JS_ACQUIRE_LOCK(lock);
Map::Ptr p = map.lookup(str);
if (p) {
js_free(p->value);
map.remove(p);
}
JS_RELEASE_LOCK(lock);
}
bool
DeflatedStringCache::setBytes(JSContext *cx, JSString *str, char *bytes)
{
JS_ACQUIRE_LOCK(lock);
Map::AddPtr p = map.lookupForAdd(str);
JS_ASSERT(!p);
bool ok = map.add(p, str, bytes);
JS_RELEASE_LOCK(lock);
if (!ok)
js_ReportOutOfMemory(cx);
return ok;
}
char *
DeflatedStringCache::getBytes(JSContext *cx, JSString *str)
{
JS_ACQUIRE_LOCK(lock);
char *bytes;
do {
Map::AddPtr p = map.lookupForAdd(str);
if (p) {
bytes = p->value;
break;
}
#ifdef JS_THREADSAFE
unsigned generation = map.generation();
JS_RELEASE_LOCK(lock);
#endif
bytes = js_DeflateString(cx, str->chars(), str->length());
if (!bytes)
return NULL;
#ifdef JS_THREADSAFE
JS_ACQUIRE_LOCK(lock);
if (generation != map.generation()) {
p = map.lookupForAdd(str);
if (p) {
/* Some other thread has asked for str bytes .*/
if (cx)
cx->free(bytes);
else
js_free(bytes);
bytes = p->value;
break;
}
}
#endif
if (!map.add(p, str, bytes)) {
JS_RELEASE_LOCK(lock);
if (cx) {
cx->free(bytes);
js_ReportOutOfMemory(cx);
} else {
js_free(bytes);
}
return NULL;
}
} while (false);
JS_ASSERT(bytes);
/* Try to catch failure to JS_ShutDown between runtime epochs. */
JS_ASSERT_IF(!js_CStringsAreUTF8 && *bytes != (char) str->chars()[0],
*bytes == '\0' && str->empty());
JS_RELEASE_LOCK(lock);
return bytes;
}
} /* namespace js */
const char *
js_GetStringBytes(JSContext *cx, JSString *str)
{
JSRuntime *rt;
JSHashTable *cache;
char *bytes;
JSHashNumber hash;
JSHashEntry *he, **hep;
if (JSString::isUnitString(str)) {
#ifdef IS_LITTLE_ENDIAN
@ -3859,50 +3891,7 @@ js_GetStringBytes(JSContext *cx, JSString *str)
rt = js_GetGCStringRuntime(str);
}
#ifdef JS_THREADSAFE
if (!rt->deflatedStringCacheLock) {
/*
* Called from last GC (see js_DestroyContext), after runtime string
* state has been finalized. We have no choice but to leak here.
*/
return js_DeflateString(NULL, str->chars(), str->length());
}
#endif
JS_ACQUIRE_LOCK(rt->deflatedStringCacheLock);
cache = rt->deflatedStringCache;
hash = js_hash_string_pointer(str);
hep = JS_HashTableRawLookup(cache, hash, str);
he = *hep;
if (he) {
bytes = (char *) he->value;
/* Try to catch failure to JS_ShutDown between runtime epochs. */
if (!js_CStringsAreUTF8) {
JS_ASSERT_IF(*bytes != (char) str->chars()[0],
*bytes == '\0' && str->empty());
}
} else {
bytes = js_DeflateString(cx, str->chars(), str->length());
if (bytes) {
if (JS_HashTableRawAdd(cache, hep, hash, str, bytes)) {
#ifdef DEBUG
rt->deflatedStringCacheBytes += str->length();
#endif
str->setDeflated();
} else {
if (cx)
cx->free(bytes);
else
js_free(bytes);
bytes = NULL;
}
}
}
JS_RELEASE_LOCK(rt->deflatedStringCacheLock);
return bytes;
return rt->deflatedStringCache->getBytes(cx, str);
}
/*

View File

@ -51,6 +51,7 @@
#include <ctype.h>
#include "jspubtd.h"
#include "jsprvtd.h"
#include "jshashtable.h"
#include "jslock.h"
JS_BEGIN_EXTERN_C
@ -87,10 +88,6 @@ JS_STATIC_ASSERT(JS_BITS_PER_WORD >= 32);
* A flat string with the ATOMIZED flag means that the string is hashed as
* an atom. This flag is used to avoid re-hashing the already-atomized string.
*
* Any string with the DEFLATED flag means that the string has an entry in the
* deflated string cache. The GC uses this flag to optimize string finalization
* and avoid an expensive cache lookup for strings that were never deflated.
*
* When the DEPENDENT flag is set, the string depends on characters of another
* string strongly referenced by the mBase field. The base member may point to
* another dependent string if chars() has not been called yet.
@ -124,7 +121,6 @@ struct JSString {
static const size_t DEPENDENT = JSSTRING_BIT(1);
static const size_t MUTABLE = JSSTRING_BIT(2);
static const size_t ATOMIZED = JSSTRING_BIT(3);
static const size_t DEFLATED = JSSTRING_BIT(4);
inline bool hasFlag(size_t flag) const {
return (mFlags & flag) != 0;
@ -145,14 +141,6 @@ struct JSString {
return !isDependent();
}
inline bool isDeflated() const {
return hasFlag(DEFLATED);
}
inline void setDeflated() {
JS_ATOMIC_SET_MASK(&mFlags, DEFLATED);
}
inline bool isMutable() const {
return !isDependent() && hasFlag(MUTABLE);
}
@ -201,18 +189,6 @@ struct JSString {
return length();
}
/*
* Special flat string initializer that preserves the DEFLATED flag.
* Use this method when reinitializing an existing string which may be
* hashed to its deflated bytes. Newborn strings must use initFlat.
*/
void reinitFlat(jschar *chars, size_t length) {
mLength = length;
mOffset = 0;
mFlags = mFlags & DEFLATED;
mChars = chars;
}
/*
* Methods to manipulate atomized and mutable flags of flat strings. It is
* safe to use these without extra locking due to the following properties:
@ -264,15 +240,6 @@ struct JSString {
mBase = bstr;
}
/* See JSString::reinitFlat. */
inline void reinitDependent(JSString *bstr, size_t off, size_t len) {
JS_ASSERT(len <= MAX_LENGTH);
mLength = len;
mOffset = off;
mFlags = DEPENDENT | (mFlags & DEFLATED);
mBase = bstr;
}
inline JSString *dependentBase() const {
JS_ASSERT(isDependent());
return mBase;
@ -503,19 +470,6 @@ JS_ISSPACE(jschar c)
#define JS7_UNHEX(c) (uintN)(JS7_ISDEC(c) ? (c) - '0' : 10 + tolower(c) - 'a')
#define JS7_ISLET(c) ((c) < 128 && isalpha(c))
/* Initialize per-runtime string state for the first context in the runtime. */
extern JSBool
js_InitRuntimeStringState(JSContext *cx);
extern JSBool
js_InitDeflatedStringCache(JSRuntime *rt);
extern void
js_FinishRuntimeStringState(JSContext *cx);
extern void
js_FinishDeflatedStringCache(JSRuntime *rt);
/* Initialize the String class, returning its prototype object. */
extern JSClass js_StringClass;
@ -686,13 +640,6 @@ extern JSBool
js_DeflateStringToBuffer(JSContext *cx, const jschar *chars,
size_t charsLength, char *bytes, size_t *length);
/*
* Associate bytes with str in the deflated string cache, returning true on
* successful association, false on out of memory.
*/
extern JSBool
js_SetStringBytes(JSContext *cx, JSString *str, char *bytes, size_t length);
/*
* Find or create a deflated string cache entry for str that contains its
* characters chopped from Unicode code points into bytes.
@ -700,10 +647,6 @@ js_SetStringBytes(JSContext *cx, JSString *str, char *bytes, size_t length);
extern const char *
js_GetStringBytes(JSContext *cx, JSString *str);
/* Remove a deflated string cache entry associated with str if any. */
extern void
js_PurgeDeflatedStringCache(JSRuntime *rt, JSString *str);
/* Export a few natives and a helper to other files in SpiderMonkey. */
extern JSBool
js_str_escape(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
@ -750,4 +693,57 @@ js_String(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
JS_END_EXTERN_C
namespace js {
class DeflatedStringCache {
public:
DeflatedStringCache();
bool init();
~DeflatedStringCache();
void sweep(JSContext *cx);
void remove(JSString *str);
bool setBytes(JSContext *cx, JSString *str, char *bytes);
private:
struct StringPtrHasher
{
typedef JSString *Lookup;
static uint32 hash(JSString *str) {
/*
* We hash only GC-allocated Strings. They are aligned on
* sizeof(JSString) boundary so we can improve hashing by stripping
* initial zeros.
*/
const jsuword ALIGN_LOG = tl::FloorLog2<sizeof(JSString)>::result;
JS_STATIC_ASSERT(sizeof(JSString) == (size_t(1) << ALIGN_LOG));
jsuword ptr = reinterpret_cast<jsuword>(str);
jsuword key = ptr >> ALIGN_LOG;
JS_ASSERT((key << ALIGN_LOG) == ptr);
return uint32(key);
}
static bool match(JSString *s1, JSString *s2) {
return s1 == s2;
}
};
typedef HashMap<JSString *, char *, StringPtrHasher, SystemAllocPolicy> Map;
/* cx is NULL when the caller is JS_GetStringBytes(JSString *). */
char *getBytes(JSContext *cx, JSString *str);
friend const char *
::js_GetStringBytes(JSContext *cx, JSString *str);
Map map;
#ifdef JS_THREADSAFE
JSLock *lock;
#endif
};
} /* namespace js */
#endif /* jsstr_h___ */

View File

@ -7495,7 +7495,7 @@ js_AddAttributePart(JSContext *cx, JSBool isName, JSString *str, JSString *str2)
* Reallocating str (because we know it has no other references)
* requires purging any deflated string cached for it.
*/
js_PurgeDeflatedStringCache(cx->runtime, str);
cx->runtime->deflatedStringCache->remove(str);
}
str2->getCharsAndLength(chars2, len2);