Bug 777190 - Don't compress files with huge strings; reenable source compression. r=jorendorff

This commit is contained in:
Benjamin Peterson 2012-07-30 19:23:46 -07:00
Родитель 123faba661
Коммит d07da680be
7 изменённых файлов: 170 добавлений и 80 удалений

Просмотреть файл

@ -80,7 +80,7 @@ frontend::CompileScript(JSContext *cx, HandleObject scopeChain, StackFrame *call
if (!CheckLength(cx, length))
return NULL;
AutoAttachToRuntime attacher(cx->runtime);
SourceCompressionToken sct(cx->runtime);
SourceCompressionToken sct(cx);
ScriptSource *ss = NULL;
if (!cx->hasRunOption(JSOPTION_ONLY_CNG_SOURCE) || options.compileAndGo) {
ss = ScriptSource::createFromSource(cx, chars, length, false, &sct);
@ -92,6 +92,7 @@ frontend::CompileScript(JSContext *cx, HandleObject scopeChain, StackFrame *call
Parser parser(cx, options, chars, length, /* foldConstants = */ true);
if (!parser.init())
return NULL;
parser.sct = &sct;
SharedContext sc(cx, scopeChain, /* fun = */ NULL, /* funbox = */ NULL, StrictModeFromContext(cx));
@ -245,7 +246,7 @@ frontend::CompileFunctionBody(JSContext *cx, HandleFunction fun, CompileOptions
if (!CheckLength(cx, length))
return false;
AutoAttachToRuntime attacher(cx->runtime);
SourceCompressionToken sct(cx->runtime);
SourceCompressionToken sct(cx);
ScriptSource *ss = ScriptSource::createFromSource(cx, chars, length, true, &sct);
if (!ss)
return NULL;
@ -255,6 +256,7 @@ frontend::CompileFunctionBody(JSContext *cx, HandleFunction fun, CompileOptions
Parser parser(cx, options, chars, length, /* foldConstants = */ true);
if (!parser.init())
return false;
parser.sct = &sct;
JS_ASSERT(fun);
SharedContext funsc(cx, /* scopeChain = */ NULL, fun, /* funbox = */ NULL,

Просмотреть файл

@ -114,6 +114,7 @@ Parser::Parser(JSContext *cx, const CompileOptions &options,
allocator(cx),
traceListHead(NULL),
tc(NULL),
sct(NULL),
keepAtoms(cx->runtime),
foldConstants(foldConstants),
compileAndGo(options.compileAndGo)
@ -6514,6 +6515,14 @@ Parser::atomNode(ParseNodeKind kind, JSOp op)
node->setOp(op);
const Token &tok = tokenStream.currentToken();
node->pn_atom = tok.atom();
// Large strings are fast to parse but slow to compress. Stop compression on
// them, so we don't wait for a long time for compression to finish at the
// end of compilation.
const size_t HUGE_STRING = 50000;
if (sct && kind == PNK_STRING && node->pn_atom->length() >= HUGE_STRING)
sct->abort();
return node;
}

Просмотреть файл

@ -42,6 +42,8 @@ struct Parser : private AutoGCRooter
TreeContext *tc; /* innermost tree context (stack-allocated) */
SourceCompressionToken *sct; /* compression token for aborting */
/* Root atoms and objects allocated for the parsed tree. */
AutoKeepAtoms keepAtoms;

Просмотреть файл

@ -1026,15 +1026,45 @@ SourceCompressorThread::threadLoop()
case IDLE:
PR_WaitCondVar(wakeup, PR_INTERVAL_NO_TIMEOUT);
break;
case COMPRESSING:
case COMPRESSING: {
JS_ASSERT(tok);
JS_ASSERT(!tok->ss->ready());
tok->ss->considerCompressing(rt, tok->chars);
ScriptSource *ss = tok->ss;
JS_ASSERT(!ss->ready());
const size_t COMPRESS_THRESHOLD = 512;
size_t compressedLength = 0;
#ifdef USE_ZLIB
size_t nbytes = sizeof(jschar) * ss->length();
if (nbytes >= COMPRESS_THRESHOLD) {
Compressor comp(reinterpret_cast<const unsigned char *>(tok->chars),
nbytes, ss->data.compressed);
if (comp.init()) {
while (!stop && comp.compressMore())
;
compressedLength = comp.finish();
if (stop || compressedLength == nbytes)
compressedLength = 0;
}
}
#endif
ss->compressedLength_ = compressedLength;
if (compressedLength == 0) {
PodCopy(ss->data.source, tok->chars, ss->length());
} else {
// Shrink the buffer to the size of the compressed data. The
// memory allocation functions on JSContext and JSRuntime are
// not threadsafe, so use js_realloc directly. We'll fix up the
// memory accounting of the runtime in waitOnCompression().
void *newmem = js_realloc(ss->data.compressed, compressedLength);
JS_ASSERT(newmem); // Reducing memory size shouldn't fail.
ss->data.compressed = static_cast<unsigned char *>(newmem);
}
// We hold the lock, so no one should have changed this.
JS_ASSERT(state == COMPRESSING);
state = IDLE;
PR_NotifyCondVar(done);
break;
}
}
}
}
@ -1049,6 +1079,7 @@ SourceCompressorThread::compress(SourceCompressionToken *sct)
waitOnCompression(tok);
JS_ASSERT(state == IDLE);
JS_ASSERT(!tok);
stop = false;
PR_Lock(lock);
tok = sct;
state = COMPRESSING;
@ -1065,11 +1096,31 @@ SourceCompressorThread::waitOnCompression(SourceCompressionToken *userTok)
if (state == COMPRESSING)
PR_WaitCondVar(done, PR_INTERVAL_NO_TIMEOUT);
JS_ASSERT(state == IDLE);
JS_ASSERT(tok->ss->ready());
tok->ss = NULL;
tok->chars = NULL;
SourceCompressionToken *saveTok = tok;
tok = NULL;
PR_Unlock(lock);
JS_ASSERT(!saveTok->ss->ready());
#ifdef DEBUG
saveTok->ss->ready_ = true;
#endif
// Update memory accounting if needed.
if (saveTok->ss->compressed()) {
ptrdiff_t delta = saveTok->ss->compressedLength_ - sizeof(jschar) * saveTok->ss->length();
JS_ASSERT(delta < 0);
saveTok->cx->runtime->updateMallocCounter(NULL, delta);
}
saveTok->ss = NULL;
saveTok->chars = NULL;
}
void
SourceCompressorThread::abort(SourceCompressionToken *userTok)
{
JS_ASSERT(userTok == tok);
stop = true;
}
#endif /* JS_THREADSAFE */
@ -1192,12 +1243,12 @@ ScriptSource::createFromSource(JSContext *cx, const jschar *src, uint32_t length
bool argumentsNotIncluded, SourceCompressionToken *tok,
bool ownSource)
{
ScriptSource *ss = static_cast<ScriptSource *>(cx->malloc_(sizeof(*ss)));
ScriptSource *ss = static_cast<ScriptSource *>(cx->runtime->malloc_(sizeof(*ss)));
if (!ss)
return NULL;
if (!ownSource) {
const size_t nbytes = length * sizeof(jschar);
ss->data.compressed = static_cast<unsigned char *>(cx->malloc_(nbytes));
ss->data.compressed = static_cast<unsigned char *>(cx->runtime->malloc_(nbytes));
if (!ss->data.compressed) {
cx->free_(ss);
return NULL;
@ -1215,54 +1266,38 @@ ScriptSource::createFromSource(JSContext *cx, const jschar *src, uint32_t length
JS_ASSERT_IF(ownSource, !tok);
#ifdef JS_THREADSAFE
if (tok && 0) {
if (tok && !ownSource) {
tok->ss = ss;
tok->chars = src;
cx->runtime->sourceCompressorThread.compress(tok);
} else
#endif
ss->considerCompressing(cx->runtime, src, ownSource);
{
if (ownSource)
ss->data.source = const_cast<jschar *>(src);
else
PodCopy(ss->data.source, src, ss->length_);
#ifdef DEBUG
ss->ready_ = true;
#endif
}
return ss;
}
void
ScriptSource::considerCompressing(JSRuntime *rt, const jschar *src, bool ownSource)
{
JS_ASSERT(!ready());
#if USE_ZLIB
const size_t nbytes = length_ * sizeof(jschar);
const size_t COMPRESS_THRESHOLD = 512;
size_t compressedLength;
#endif
if (ownSource) {
data.source = const_cast<jschar *>(src);
#if USE_ZLIB
} else if (nbytes >= COMPRESS_THRESHOLD && 0 &&
TryCompressString(reinterpret_cast<const unsigned char *>(src), nbytes,
data.compressed, &compressedLength))
{
JS_ASSERT(compressedLength < nbytes);
compressedLength_ = compressedLength;
void *mem = rt->realloc_(data.compressed, compressedLength_);
data.compressed = static_cast<unsigned char *>(mem);
JS_ASSERT(data.compressed);
#endif
} else {
PodCopy(data.source, src, length_);
}
#ifdef DEBUG
ready_ = true;
#endif
}
void
SourceCompressionToken::ensureReady()
{
#ifdef JS_THREADSAFE
rt->sourceCompressorThread.waitOnCompression(this);
cx->runtime->sourceCompressorThread.waitOnCompression(this);
#endif
}
void
SourceCompressionToken::abort()
{
#ifdef JS_THREADSAFE
cx->runtime->sourceCompressorThread.abort(this);
#endif
}

Просмотреть файл

@ -1024,7 +1024,6 @@ struct ScriptSource
private:
bool compressed() { return compressedLength_ != 0; }
void considerCompressing(JSRuntime *rt, const jschar *src, bool ownSource = false);
};
#ifdef JS_THREADSAFE
@ -1061,6 +1060,8 @@ class SourceCompressorThread
PRCondVar *wakeup;
// The main thread can block on this to wait for compression to finish.
PRCondVar *done;
// Flag which can be set by the main thread to ask compression to abort.
volatile bool stop;
void threadLoop();
static void compressorThread(void *arg);
@ -1078,6 +1079,7 @@ class SourceCompressorThread
bool init();
void compress(SourceCompressionToken *tok);
void waitOnCompression(SourceCompressionToken *userTok);
void abort(SourceCompressionToken *userTok);
};
#endif
@ -1086,19 +1088,21 @@ struct SourceCompressionToken
friend struct ScriptSource;
friend class SourceCompressorThread;
private:
JSRuntime *rt;
JSContext *cx;
ScriptSource *ss;
const jschar *chars;
public:
SourceCompressionToken(JSRuntime *rt)
: rt(rt), ss(NULL), chars(NULL) {}
SourceCompressionToken(JSContext *cx)
: cx(cx), ss(NULL), chars(NULL) {}
~SourceCompressionToken()
{
JS_ASSERT_IF(!ss, !chars);
if (ss)
ensureReady();
}
void ensureReady();
void abort();
};
extern void

Просмотреть файл

@ -1,4 +1,4 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -42,35 +42,53 @@ zlib_free(void *cx, void *addr)
Foreground::free_(addr);
}
bool
js::TryCompressString(const unsigned char *inp, size_t inplen, unsigned char *out, size_t *outlen)
Compressor::init()
{
JS_ASSERT(inplen);
if (inplen >= UINT32_MAX)
return false;
z_stream zs;
zs.opaque = NULL;
zs.zalloc = zlib_alloc;
zs.zfree = zlib_free;
zs.next_in = (Bytef *)inp;
zs.avail_in = inplen;
zs.next_out = out;
zs.avail_out = inplen;
int ret = deflateInit(&zs, Z_BEST_SPEED);
int ret = deflateInit(&zs, Z_DEFAULT_COMPRESSION);
if (ret != Z_OK) {
JS_ASSERT(ret == Z_MEM_ERROR);
return false;
}
ret = deflate(&zs, Z_FINISH);
DebugOnly<int> ret2 = deflateEnd(&zs);
JS_ASSERT(ret2 == Z_OK);
if (ret != Z_STREAM_END)
return false;
*outlen = inplen - zs.avail_out;
return true;
}
bool
Compressor::compressMore()
{
uInt left = inplen - (zs.next_in - inp);
bool done = left <= CHUNKSIZE;
if (done)
zs.avail_in = left;
else if (zs.avail_in == 0)
zs.avail_in = CHUNKSIZE;
int ret = deflate(&zs, done ? Z_FINISH : Z_NO_FLUSH);
if (ret == Z_BUF_ERROR) {
JS_ASSERT(zs.avail_out == 0);
return false;
}
JS_ASSERT_IF(!done, ret == Z_OK);
JS_ASSERT_IF(done, ret == Z_STREAM_END);
return !done;
}
size_t
Compressor::finish()
{
size_t outlen = inplen - zs.avail_out;
int ret = deflateEnd(&zs);
if (ret != Z_OK) {
// If we finished early, we can get a Z_DATA_ERROR.
JS_ASSERT(ret == Z_DATA_ERROR);
JS_ASSERT(uInt(zs.next_in - inp) < inplen || !zs.avail_out);
}
return outlen;
}
bool
js::DecompressString(const unsigned char *inp, size_t inplen, unsigned char *out, size_t outlen)
{
@ -86,8 +104,8 @@ js::DecompressString(const unsigned char *inp, size_t inplen, unsigned char *out
zs.avail_out = outlen;
int ret = inflateInit(&zs);
if (ret != Z_OK) {
JS_ASSERT(ret == Z_MEM_ERROR);
return false;
JS_ASSERT(ret == Z_MEM_ERROR);
return false;
}
ret = inflate(&zs, Z_FINISH);
JS_ASSERT(ret == Z_STREAM_END);
@ -151,10 +169,10 @@ ValToBin(unsigned logscale, uint32_t val)
if (val <= 1)
return val;
bin = (logscale == 10)
? (unsigned) ceil(log10((double) val))
: (logscale == 2)
? (unsigned) JS_CEILING_LOG2W(val)
: val;
? (unsigned) ceil(log10((double) val))
: (logscale == 2)
? (unsigned) JS_CEILING_LOG2W(val)
: val;
return JS_MIN(bin, 10);
}

Просмотреть файл

@ -15,6 +15,8 @@
#include "js/Utility.h"
#include "zlib.h"
/* Forward declarations. */
struct JSContext;
@ -335,14 +337,32 @@ ClearAllBitArrayElements(size_t *array, size_t length)
array[i] = 0;
}
#if USE_ZLIB
/*
* Attempt to compress some bytes. Return true if compression produced a
* string smaller than the input. The caller is responsible for allocating
* |out| to a string the same length as the input.
*/
bool TryCompressString(const unsigned char *inp, size_t inplen,
unsigned char *out, size_t *outlen);
#ifdef USE_ZLIB
class Compressor
{
// Number of bytes we should hand to zlib each compressMore() call.
static const size_t CHUNKSIZE = 2048;
z_stream zs;
const unsigned char *inp;
size_t inplen;
public:
Compressor(const unsigned char *inp, size_t inplen, unsigned char *out)
: inp(inp),
inplen(inplen)
{
JS_ASSERT(inplen > 0);
zs.opaque = NULL;
zs.next_in = (Bytef *)inp;
zs.avail_in = 0;
zs.next_out = out;
zs.avail_out = inplen;
}
bool init();
// Compress some of the input. Return true if it should be called again.
bool compressMore();
// Finalize compression. Return the length of the compressed input.
size_t finish();
};
/*
* Decompress a string. The caller must know the length of the output and