зеркало из https://github.com/mozilla/gecko-dev.git
Bug 777190 - Don't compress files with huge strings; reenable source compression. r=jorendorff
This commit is contained in:
Родитель
123faba661
Коммит
d07da680be
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче