summaryrefslogtreecommitdiffstats
path: root/dom/script/ScriptCompression.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/script/ScriptCompression.cpp')
-rw-r--r--dom/script/ScriptCompression.cpp174
1 files changed, 174 insertions, 0 deletions
diff --git a/dom/script/ScriptCompression.cpp b/dom/script/ScriptCompression.cpp
new file mode 100644
index 0000000000..7aa8f08de2
--- /dev/null
+++ b/dom/script/ScriptCompression.cpp
@@ -0,0 +1,174 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "zlib.h"
+#include "ScriptLoadRequest.h"
+#include "ScriptLoader.h"
+#include "mozilla/PerfStats.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "mozilla/Vector.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_browser.h"
+
+using namespace mozilla;
+
+namespace JS::loader {
+
+#undef LOG
+#define LOG(args) \
+ MOZ_LOG(mozilla::dom::ScriptLoader::gScriptLoaderLog, \
+ mozilla::LogLevel::Debug, args)
+
+/*
+ * ScriptBytecodeDataLayout
+ *
+ * ScriptBytecodeDataLayout provides accessors to maintain the correct data
+ * layout of the ScriptLoadRequest's script bytecode buffer.
+ */
+class ScriptBytecodeDataLayout {
+ public:
+ explicit ScriptBytecodeDataLayout(mozilla::Vector<uint8_t>& aBytecode,
+ size_t aBytecodeOffset)
+ : mBytecode(aBytecode), mBytecodeOffset(aBytecodeOffset) {}
+
+ uint8_t* prelude() const { return mBytecode.begin(); }
+ size_t preludeLength() const { return mBytecodeOffset; }
+
+ uint8_t* bytecode() const { return prelude() + mBytecodeOffset; }
+ size_t bytecodeLength() const { return mBytecode.length() - preludeLength(); }
+
+ mozilla::Vector<uint8_t>& mBytecode;
+ size_t mBytecodeOffset;
+};
+
+/*
+ * ScriptBytecodeCompressedDataLayout
+ *
+ * ScriptBytecodeCompressedDataLayout provides accessors to maintain the correct
+ * data layout of a compressed script bytecode buffer.
+ */
+class ScriptBytecodeCompressedDataLayout {
+ public:
+ using UncompressedLengthType = uint32_t;
+
+ explicit ScriptBytecodeCompressedDataLayout(
+ mozilla::Vector<uint8_t>& aBytecode, size_t aBytecodeOffset)
+ : mBytecode(aBytecode), mBytecodeOffset(aBytecodeOffset) {}
+
+ uint8_t* prelude() const { return mBytecode.begin(); }
+ size_t preludeLength() const { return mBytecodeOffset; }
+
+ uint8_t* uncompressedLength() const { return prelude() + mBytecodeOffset; }
+ size_t uncompressedLengthLength() const {
+ return sizeof(UncompressedLengthType);
+ }
+
+ uint8_t* bytecode() const {
+ return uncompressedLength() + uncompressedLengthLength();
+ }
+ size_t bytecodeLength() const {
+ return mBytecode.length() - uncompressedLengthLength() - preludeLength();
+ }
+
+ mozilla::Vector<uint8_t>& mBytecode;
+ size_t mBytecodeOffset;
+};
+
+bool ScriptBytecodeCompress(Vector<uint8_t>& aBytecodeBuf,
+ size_t aBytecodeOffset,
+ Vector<uint8_t>& aCompressedBytecodeBufOut) {
+ // TODO probably need to move this to a helper thread
+
+ AUTO_PROFILER_MARKER_TEXT("ScriptBytecodeCompress", JS, {}, ""_ns);
+ PerfStats::AutoMetricRecording<PerfStats::Metric::JSBC_Compression>
+ autoRecording;
+
+ ScriptBytecodeDataLayout uncompressedLayout(aBytecodeBuf, aBytecodeOffset);
+ ScriptBytecodeCompressedDataLayout compressedLayout(
+ aCompressedBytecodeBufOut, uncompressedLayout.preludeLength());
+ ScriptBytecodeCompressedDataLayout::UncompressedLengthType
+ uncompressedLength = uncompressedLayout.bytecodeLength();
+ z_stream zstream{.next_in = uncompressedLayout.bytecode(),
+ .avail_in = uncompressedLength};
+ auto compressedLength = deflateBound(&zstream, uncompressedLength);
+ if (!aCompressedBytecodeBufOut.resizeUninitialized(
+ compressedLength + compressedLayout.preludeLength() +
+ compressedLayout.uncompressedLengthLength())) {
+ return false;
+ }
+ memcpy(compressedLayout.prelude(), uncompressedLayout.prelude(),
+ uncompressedLayout.preludeLength());
+ memcpy(compressedLayout.uncompressedLength(), &uncompressedLength,
+ sizeof(uncompressedLength));
+ zstream.next_out = compressedLayout.bytecode();
+ zstream.avail_out = compressedLength;
+
+ const uint32_t compressionLevel =
+ StaticPrefs::browser_cache_jsbc_compression_level();
+ if (deflateInit(&zstream, compressionLevel) != Z_OK) {
+ LOG(
+ ("ScriptLoadRequest: Unable to initialize bytecode cache "
+ "compression."));
+ return false;
+ }
+ auto autoDestroy = MakeScopeExit([&]() { deflateEnd(&zstream); });
+
+ int ret = deflate(&zstream, Z_FINISH);
+ if (ret == Z_MEM_ERROR) {
+ return false;
+ }
+ MOZ_RELEASE_ASSERT(ret == Z_STREAM_END);
+
+ aCompressedBytecodeBufOut.shrinkTo(zstream.next_out -
+ aCompressedBytecodeBufOut.begin());
+ return true;
+}
+
+bool ScriptBytecodeDecompress(Vector<uint8_t>& aCompressedBytecodeBuf,
+ size_t aBytecodeOffset,
+ Vector<uint8_t>& aBytecodeBufOut) {
+ AUTO_PROFILER_MARKER_TEXT("ScriptBytecodeDecompress", JS, {}, ""_ns);
+ PerfStats::AutoMetricRecording<PerfStats::Metric::JSBC_Decompression>
+ autoRecording;
+
+ ScriptBytecodeDataLayout uncompressedLayout(aBytecodeBufOut, aBytecodeOffset);
+ ScriptBytecodeCompressedDataLayout compressedLayout(
+ aCompressedBytecodeBuf, uncompressedLayout.preludeLength());
+ ScriptBytecodeCompressedDataLayout::UncompressedLengthType uncompressedLength;
+ memcpy(&uncompressedLength, compressedLayout.uncompressedLength(),
+ compressedLayout.uncompressedLengthLength());
+ if (!aBytecodeBufOut.resizeUninitialized(uncompressedLayout.preludeLength() +
+ uncompressedLength)) {
+ return false;
+ }
+ memcpy(uncompressedLayout.prelude(), compressedLayout.prelude(),
+ compressedLayout.preludeLength());
+
+ z_stream zstream{nullptr};
+ zstream.next_in = compressedLayout.bytecode();
+ zstream.avail_in = static_cast<uint32_t>(compressedLayout.bytecodeLength());
+ zstream.next_out = uncompressedLayout.bytecode();
+ zstream.avail_out = uncompressedLength;
+ if (inflateInit(&zstream) != Z_OK) {
+ LOG(("ScriptLoadRequest: inflateInit FAILED (%s)", zstream.msg));
+ return false;
+ }
+ auto autoDestroy = MakeScopeExit([&]() { inflateEnd(&zstream); });
+
+ int ret = inflate(&zstream, Z_NO_FLUSH);
+ bool ok = (ret == Z_OK || ret == Z_STREAM_END) && zstream.avail_in == 0;
+ if (!ok) {
+ LOG(("ScriptLoadReques: inflate FAILED (%s)", zstream.msg));
+ return false;
+ }
+
+ return true;
+}
+
+#undef LOG
+
+} // namespace JS::loader