Bug 804857 - Start with a small compression buffer. r=njn

This commit is contained in:
Benjamin Peterson 2012-10-24 16:15:48 -07:00
parent 8444e1bdb4
commit 64f17f75f7
4 changed files with 116 additions and 54 deletions

View File

@ -1,3 +1,5 @@
// This test creates poorly compressible input, which tests certain paths in
// source code compression.
var x = "";
for (var i=0; i<400; ++i) {
x += String.fromCharCode(i * 289);

View File

@ -932,42 +932,80 @@ SourceCompressorThread::internalCompress()
ScriptSource *ss = tok->ss;
JS_ASSERT(!ss->ready());
const size_t COMPRESS_THRESHOLD = 512;
size_t compressedLength = 0;
size_t nbytes = sizeof(jschar) * ss->length_;
// Memory allocation functions on JSRuntime and JSContext are not
// threadsafe. We have to use the js_* variants.
ss->data.compressed = static_cast<unsigned char *>(js_malloc(nbytes));
if (!ss->data.compressed)
return false;
#ifdef USE_ZLIB
const size_t COMPRESS_THRESHOLD = 512;
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;
// Try to keep the maximum memory usage down by only allocating half the
// size of the string, first.
size_t firstSize = nbytes / 2;
ss->data.compressed = static_cast<unsigned char *>(js_malloc(firstSize));
if (!ss->data.compressed)
return false;
Compressor comp(reinterpret_cast<const unsigned char *>(tok->chars), nbytes);
if (!comp.init())
return false;
comp.setOutput(ss->data.compressed, firstSize);
bool cont = !stop;
while (cont) {
switch (comp.compressMore()) {
case Compressor::CONTINUE:
break;
case Compressor::MOREOUTPUT: {
if (comp.outWritten() == nbytes) {
cont = false;
break;
}
// The compressed output is greater than half the size of the
// original string. Reallocate to the full size.
void *newmem = js_realloc(ss->data.compressed, nbytes);
if (!newmem) {
js_free(ss->data.compressed);
ss->data.compressed = NULL;
return false;
}
ss->data.compressed = static_cast<unsigned char *>(newmem);
comp.setOutput(ss->data.compressed, nbytes);
break;
}
case Compressor::DONE:
cont = false;
break;
case Compressor::OOM:
return false;
}
cont = cont && !stop;
}
compressedLength = comp.outWritten();
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.
void *newmem = js_realloc(ss->data.compressed, compressedLength);
if (!newmem) {
js_free(ss->data.compressed);
ss->data.compressed = NULL;
// Note ss->data.source might be NULL.
jschar *buf = static_cast<jschar *>(js_realloc(ss->data.source, nbytes));
if (!buf) {
if (ss->data.source) {
js_free(ss->data.source);
ss->data.source = NULL;
}
return false;
}
ss->data.source = buf;
PodCopy(ss->data.source, tok->chars, ss->length());
} else {
// Shrink the buffer to the size of the compressed data. Shouldn't fail.
void *newmem = js_realloc(ss->data.compressed, compressedLength);
JS_ASSERT(newmem);
ss->data.compressed = static_cast<unsigned char *>(newmem);
}
ss->compressedLength_ = compressedLength;
return true;
}
@ -1172,7 +1210,7 @@ ScriptSource::setSourceCopy(JSContext *cx, StableCharPtr src, uint32_t length,
} else
#endif
{
data.source = cx->pod_malloc<jschar>(length);
data.source = cx->runtime->pod_malloc<jschar>(length);
if (!data.source)
return false;
PodCopy(data.source, src.get(), length_);

View File

@ -42,13 +42,37 @@ zlib_free(void *cx, void *addr)
js_free(addr);
}
Compressor::Compressor(const unsigned char *inp, size_t inplen)
: inp(inp),
inplen(inplen),
outbytes(0)
{
JS_ASSERT(inplen > 0);
zs.opaque = NULL;
zs.next_in = (Bytef *)inp;
zs.avail_in = 0;
zs.next_out = NULL;
zs.avail_out = 0;
zs.zalloc = zlib_alloc;
zs.zfree = zlib_free;
}
Compressor::~Compressor()
{
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);
}
}
bool
Compressor::init()
{
if (inplen >= UINT32_MAX)
return false;
zs.zalloc = zlib_alloc;
zs.zfree = zlib_free;
int ret = deflateInit(&zs, Z_DEFAULT_COMPRESSION);
if (ret != Z_OK) {
JS_ASSERT(ret == Z_MEM_ERROR);
@ -57,40 +81,38 @@ Compressor::init()
return true;
}
bool
void
Compressor::setOutput(unsigned char *out, size_t outlen)
{
JS_ASSERT(outlen > outbytes);
zs.next_out = out + outbytes;
zs.avail_out = outlen - outbytes;
}
Compressor::Status
Compressor::compressMore()
{
JS_ASSERT(zs.next_out);
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;
Bytef *oldout = zs.next_out;
int ret = deflate(&zs, done ? Z_FINISH : Z_NO_FLUSH);
outbytes += zs.next_out - oldout;
if (ret == Z_MEM_ERROR) {
zs.avail_out = 0;
return false;
return OOM;
}
if (ret == Z_BUF_ERROR || (done && ret == Z_OK)) {
JS_ASSERT(zs.avail_out == 0);
return false;
return MOREOUTPUT;
}
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;
return done ? DONE : CONTINUE;
}
bool

View File

@ -369,23 +369,23 @@ class Compressor
z_stream zs;
const unsigned char *inp;
size_t inplen;
size_t outbytes;
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;
}
enum Status {
MOREOUTPUT,
DONE,
CONTINUE,
OOM
};
Compressor(const unsigned char *inp, size_t inplen);
~Compressor();
bool init();
void setOutput(unsigned char *out, size_t outlen);
size_t outWritten() const { return outbytes; }
/* 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();
Status compressMore();
};
/*