tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 025f8d5456bad832f04329a37f16480f94170fff
parent a82e3b9c431fbfe9a06c776440cc360973498c06
Author: Jan de Mooij <jdemooij@mozilla.com>
Date:   Fri, 19 Dec 2025 08:40:39 +0000

Bug 2004893 part 4 - Use a single Compressor for each SourceCompressionTask. r=jonco

For short script sources, most of our time was spent under `deflateInit2` and `deflateEnd`.
It's faster to use `deflateReset` between script sources.

Differential Revision: https://phabricator.services.mozilla.com/D276487

Diffstat:
Mjs/src/vm/Compression.cpp | 55++++++++++++++++++++++++++++++++++++++++---------------
Mjs/src/vm/Compression.h | 31+++++++++++++++++++++++--------
Mjs/src/vm/HelperThreadState.h | 5+++--
Mjs/src/vm/JSScript.cpp | 29+++++++++++++++++++----------
Mjs/src/vm/JSScript.h | 3++-
5 files changed, 87 insertions(+), 36 deletions(-)

diff --git a/js/src/vm/Compression.cpp b/js/src/vm/Compression.cpp @@ -22,16 +22,9 @@ static void* zlib_alloc(void* cx, uInt items, uInt size) { static void zlib_free(void* cx, void* addr) { js_free(addr); } -Compressor::Compressor(const unsigned char* inp, size_t inplen) - : inp(inp), - inplen(inplen), - initialized(false), - finished(false), - currentChunkSize(0) { - MOZ_ASSERT(inplen > 0, "data to compress can't be empty"); - +Compressor::Compressor() { zs.opaque = nullptr; - zs.next_in = (Bytef*)inp; + zs.next_in = nullptr; zs.avail_in = 0; zs.next_out = nullptr; zs.avail_out = 0; @@ -44,9 +37,6 @@ Compressor::Compressor(const unsigned char* inp, size_t inplen) zs.data_type = 0; zs.adler = 0; zs.reserved = 0; - - // Reserve space for the CompressedDataHeader. - outbytes = sizeof(CompressedDataHeader); } Compressor::~Compressor() { @@ -66,9 +56,8 @@ Compressor::~Compressor() { static const int WindowBits = -15; bool Compressor::init() { - if (inplen >= UINT32_MAX) { - return false; - } + MOZ_ASSERT(!initialized); + // zlib is slow and we'd rather be done compression sooner // even if it means decompression is slower which penalizes // Function.toString() @@ -89,6 +78,42 @@ bool Compressor::init() { return true; } +bool Compressor::setInput(const unsigned char* input, size_t inputLength) { + MOZ_ASSERT(initialized); + + if (inputLength >= UINT32_MAX) { + return false; + } + MOZ_ASSERT(inputLength > 0, "data to compress can't be empty"); + + inp = input; + inplen = inputLength; + + // Reserve space for the CompressedDataHeader. + outbytes = sizeof(CompressedDataHeader); + + zs.next_in = (Bytef*)inp; + zs.avail_in = 0; + zs.next_out = nullptr; + zs.avail_out = 0; + + // If we've already compressed another string, we need to reset our internal + // state. Note: deflateReset is much faster than deflateEnd + deflateInit2. + if (isFirstInput) { + MOZ_ASSERT(currentChunkSize == 0); + MOZ_ASSERT(chunkOffsets.empty()); + MOZ_ASSERT(!finished); + isFirstInput = false; + } else { + currentChunkSize = 0; + chunkOffsets.clear(); + finished = false; + int ret = deflateReset(&zs); + MOZ_RELEASE_ASSERT(ret == Z_OK); + } + return true; +} + void Compressor::setOutput(unsigned char* out, size_t outlen) { MOZ_ASSERT(outlen > outbytes); zs.next_out = out + outbytes; diff --git a/js/src/vm/Compression.h b/js/src/vm/Compression.h @@ -31,15 +31,20 @@ class Compressor { static constexpr size_t MAX_INPUT_SIZE = 2 * 1024; z_stream zs; - const unsigned char* inp; - size_t inplen; - size_t outbytes; - bool initialized; - bool finished; + const unsigned char* inp = nullptr; + size_t inplen = 0; + size_t outbytes = 0; + bool finished = false; + + // True if |zs| has been initialized by |init|. + bool initialized = false; + + // Flag set to |false| after the first call to |setInput|. + bool isFirstInput = true; // The number of uncompressed bytes written for the current chunk. When this // reaches CHUNK_SIZE, we finish the current chunk and start a new chunk. - uint32_t currentChunkSize; + uint32_t currentChunkSize = 0; // At the end of each chunk (and the end of the uncompressed data if it's // not a chunk boundary), we record the offset in the compressed data. @@ -48,9 +53,19 @@ class Compressor { public: enum Status { MOREOUTPUT, DONE, CONTINUE, OOM }; - Compressor(const unsigned char* inp, size_t inplen); + Compressor(); ~Compressor(); - bool init(); + + Compressor(const Compressor&) = delete; + void operator=(const Compressor&) = delete; + + // This should be called once per Compressor, before calling setInput. + [[nodiscard]] bool init(); + + // setInput can be called more than once, to compress multiple strings with + // minimal overhead. This will reset the compressor's state. + [[nodiscard]] bool setInput(const unsigned char* input, size_t inputLength); + void setOutput(unsigned char* out, size_t outlen); /* Compress some of the input. Return true if it should be called again. */ Status compressMore(); diff --git a/js/src/vm/HelperThreadState.h b/js/src/vm/HelperThreadState.h @@ -52,6 +52,7 @@ class JSTracer; namespace js { +class Compressor; struct DelazifyTask; struct FreeDelazifyTask; struct PromiseHelperTask; @@ -600,9 +601,9 @@ class SourceCompressionTaskEntry { // text or UTF-16, for CharT either Utf8Unit or char16_t. Invoked by // work() after doing a type-test of the ScriptSource*. template <typename CharT> - void workEncodingSpecific(); + void workEncodingSpecific(Compressor& comp); - void runTask(); + void runTask(Compressor& comp); void complete(); struct PerformTaskWork; diff --git a/js/src/vm/JSScript.cpp b/js/src/vm/JSScript.cpp @@ -1723,7 +1723,7 @@ template bool ScriptSource::assignSource(FrontendContext* fc, } template <typename Unit> -void SourceCompressionTaskEntry::workEncodingSpecific() { +void SourceCompressionTaskEntry::workEncodingSpecific(Compressor& comp) { MOZ_ASSERT(source_->isUncompressed<Unit>()); // Try to keep the maximum memory usage down by only allocating half the @@ -1736,8 +1736,8 @@ void SourceCompressionTaskEntry::workEncodingSpecific() { } const Unit* chars = source_->uncompressedData<Unit>()->units(); - Compressor comp(reinterpret_cast<const unsigned char*>(chars), inputBytes); - if (!comp.init()) { + if (!comp.setInput(reinterpret_cast<const unsigned char*>(chars), + inputBytes)) { return; } @@ -1802,12 +1802,14 @@ PendingSourceCompressionEntry::PendingSourceCompressionEntry( struct SourceCompressionTaskEntry::PerformTaskWork { SourceCompressionTaskEntry* const task_; + Compressor& comp_; - explicit PerformTaskWork(SourceCompressionTaskEntry* task) : task_(task) {} + PerformTaskWork(SourceCompressionTaskEntry* task, Compressor& comp) + : task_(task), comp_(comp) {} template <typename Unit, SourceRetrievable CanRetrieve> void operator()(const ScriptSource::Uncompressed<Unit, CanRetrieve>&) { - task_->workEncodingSpecific<Unit>(); + task_->workEncodingSpecific<Unit>(comp_); } template <typename T> @@ -1818,25 +1820,32 @@ struct SourceCompressionTaskEntry::PerformTaskWork { } }; -void ScriptSource::performTaskWork(SourceCompressionTaskEntry* task) { +void ScriptSource::performTaskWork(SourceCompressionTaskEntry* task, + Compressor& comp) { MOZ_ASSERT(hasUncompressedSource()); - data.match(SourceCompressionTaskEntry::PerformTaskWork(task)); + data.match(SourceCompressionTaskEntry::PerformTaskWork(task, comp)); } -void SourceCompressionTaskEntry::runTask() { +void SourceCompressionTaskEntry::runTask(Compressor& comp) { if (shouldCancel()) { return; } MOZ_ASSERT(source_->hasUncompressedSource()); - source_->performTaskWork(this); + source_->performTaskWork(this, comp); } void SourceCompressionTask::runTask() { MOZ_ASSERT(!entries_.empty()); + // Note: here and in workEncodingSpecific we abort compression work on OOM + // since source compression is optional. + Compressor comp; + if (!comp.init()) { + return; + } for (auto& entry : entries_) { - entry.runTask(); + entry.runTask(comp); } } diff --git a/js/src/vm/JSScript.h b/js/src/vm/JSScript.h @@ -51,6 +51,7 @@ class SourceText; namespace js { +class Compressor; class FrontendContext; class ScriptSource; @@ -1007,7 +1008,7 @@ class ScriptSource { size_t sourceLength); private: - void performTaskWork(SourceCompressionTaskEntry* task); + void performTaskWork(SourceCompressionTaskEntry* task, Compressor& comp); struct TriggerConvertToCompressedSourceFromTask { ScriptSource* const source_;