ScriptCompression.cpp (6489B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "ScriptLoadRequest.h" 8 #include "ScriptLoader.h" 9 #include "mozilla/PerfStats.h" 10 #include "mozilla/Preferences.h" 11 #include "mozilla/ProfilerMarkers.h" 12 #include "mozilla/ScopeExit.h" 13 #include "mozilla/StaticPrefs_browser.h" 14 #include "mozilla/Vector.h" 15 #include "zlib.h" 16 17 using namespace mozilla; 18 19 namespace JS::loader { 20 21 #undef LOG 22 #define LOG(args) \ 23 MOZ_LOG(mozilla::dom::ScriptLoader::gScriptLoaderLog, \ 24 mozilla::LogLevel::Debug, args) 25 26 /* 27 * ScriptBytecodeDataLayout 28 * 29 * ScriptBytecodeDataLayout provides accessors to maintain the correct data 30 * layout of the ScriptLoadRequest's script bytecode buffer. 31 */ 32 class ScriptBytecodeDataLayout { 33 public: 34 explicit ScriptBytecodeDataLayout(mozilla::Vector<uint8_t>& aBytecode, 35 size_t aBytecodeOffset) 36 : mBytecode(aBytecode), mBytecodeOffset(aBytecodeOffset) {} 37 38 uint8_t* prelude() const { return mBytecode.begin(); } 39 size_t preludeLength() const { return mBytecodeOffset; } 40 41 uint8_t* bytecode() const { return prelude() + mBytecodeOffset; } 42 size_t bytecodeLength() const { return mBytecode.length() - preludeLength(); } 43 44 mozilla::Vector<uint8_t>& mBytecode; 45 size_t mBytecodeOffset; 46 }; 47 48 /* 49 * ScriptBytecodeCompressedDataLayout 50 * 51 * ScriptBytecodeCompressedDataLayout provides accessors to maintain the correct 52 * data layout of a compressed script bytecode buffer. 53 */ 54 class ScriptBytecodeCompressedDataLayout { 55 public: 56 using UncompressedLengthType = uint32_t; 57 58 explicit ScriptBytecodeCompressedDataLayout( 59 mozilla::Vector<uint8_t>& aBytecode, size_t aBytecodeOffset) 60 : mBytecode(aBytecode), mBytecodeOffset(aBytecodeOffset) {} 61 62 uint8_t* prelude() const { return mBytecode.begin(); } 63 size_t preludeLength() const { return mBytecodeOffset; } 64 65 uint8_t* uncompressedLength() const { return prelude() + mBytecodeOffset; } 66 size_t uncompressedLengthLength() const { 67 return sizeof(UncompressedLengthType); 68 } 69 70 uint8_t* bytecode() const { 71 return uncompressedLength() + uncompressedLengthLength(); 72 } 73 size_t bytecodeLength() const { 74 return mBytecode.length() - uncompressedLengthLength() - preludeLength(); 75 } 76 77 mozilla::Vector<uint8_t>& mBytecode; 78 size_t mBytecodeOffset; 79 }; 80 81 bool ScriptBytecodeCompress(Vector<uint8_t>& aBytecodeBuf, 82 size_t aBytecodeOffset, 83 Vector<uint8_t>& aCompressedBytecodeBufOut) { 84 // TODO probably need to move this to a helper thread 85 86 AUTO_PROFILER_MARKER_UNTYPED("ScriptBytecodeCompress", JS, {}); 87 PerfStats::AutoMetricRecording<PerfStats::Metric::JSBC_Compression> 88 autoRecording; 89 90 ScriptBytecodeDataLayout uncompressedLayout(aBytecodeBuf, aBytecodeOffset); 91 ScriptBytecodeCompressedDataLayout compressedLayout( 92 aCompressedBytecodeBufOut, uncompressedLayout.preludeLength()); 93 ScriptBytecodeCompressedDataLayout::UncompressedLengthType 94 uncompressedLength = uncompressedLayout.bytecodeLength(); 95 z_stream zstream{.next_in = uncompressedLayout.bytecode(), 96 .avail_in = uncompressedLength}; 97 98 // Note: deflateInit needs to be called before deflateBound. 99 const uint32_t compressionLevel = 100 StaticPrefs::browser_cache_jsbc_compression_level(); 101 if (deflateInit(&zstream, compressionLevel) != Z_OK) { 102 LOG( 103 ("ScriptLoadRequest: Unable to initialize bytecode cache " 104 "compression.")); 105 return false; 106 } 107 auto autoDestroy = MakeScopeExit([&]() { deflateEnd(&zstream); }); 108 109 auto compressedLength = deflateBound(&zstream, uncompressedLength); 110 if (!aCompressedBytecodeBufOut.resizeUninitialized( 111 compressedLength + compressedLayout.preludeLength() + 112 compressedLayout.uncompressedLengthLength())) { 113 return false; 114 } 115 memcpy(compressedLayout.prelude(), uncompressedLayout.prelude(), 116 uncompressedLayout.preludeLength()); 117 memcpy(compressedLayout.uncompressedLength(), &uncompressedLength, 118 sizeof(uncompressedLength)); 119 zstream.next_out = compressedLayout.bytecode(); 120 zstream.avail_out = compressedLength; 121 122 int ret = deflate(&zstream, Z_FINISH); 123 if (ret == Z_MEM_ERROR) { 124 return false; 125 } 126 MOZ_RELEASE_ASSERT(ret == Z_STREAM_END); 127 128 aCompressedBytecodeBufOut.shrinkTo(zstream.next_out - 129 aCompressedBytecodeBufOut.begin()); 130 return true; 131 } 132 133 bool ScriptBytecodeDecompress(Vector<uint8_t>& aCompressedBytecodeBuf, 134 size_t aBytecodeOffset, 135 Vector<uint8_t>& aBytecodeBufOut) { 136 AUTO_PROFILER_MARKER_UNTYPED("ScriptBytecodeDecompress", JS, {}); 137 PerfStats::AutoMetricRecording<PerfStats::Metric::JSBC_Decompression> 138 autoRecording; 139 140 ScriptBytecodeDataLayout uncompressedLayout(aBytecodeBufOut, aBytecodeOffset); 141 ScriptBytecodeCompressedDataLayout compressedLayout( 142 aCompressedBytecodeBuf, uncompressedLayout.preludeLength()); 143 ScriptBytecodeCompressedDataLayout::UncompressedLengthType uncompressedLength; 144 memcpy(&uncompressedLength, compressedLayout.uncompressedLength(), 145 compressedLayout.uncompressedLengthLength()); 146 if (!aBytecodeBufOut.resizeUninitialized(uncompressedLayout.preludeLength() + 147 uncompressedLength)) { 148 return false; 149 } 150 memcpy(uncompressedLayout.prelude(), compressedLayout.prelude(), 151 compressedLayout.preludeLength()); 152 153 z_stream zstream{nullptr}; 154 zstream.next_in = compressedLayout.bytecode(); 155 zstream.avail_in = static_cast<uint32_t>(compressedLayout.bytecodeLength()); 156 zstream.next_out = uncompressedLayout.bytecode(); 157 zstream.avail_out = uncompressedLength; 158 if (inflateInit(&zstream) != Z_OK) { 159 LOG(("ScriptLoadRequest: inflateInit FAILED (%s)", zstream.msg)); 160 return false; 161 } 162 auto autoDestroy = MakeScopeExit([&]() { inflateEnd(&zstream); }); 163 164 int ret = inflate(&zstream, Z_NO_FLUSH); 165 bool ok = (ret == Z_OK || ret == Z_STREAM_END) && zstream.avail_in == 0; 166 if (!ok) { 167 LOG(("ScriptLoadReques: inflate FAILED (%s)", zstream.msg)); 168 return false; 169 } 170 171 return true; 172 } 173 174 #undef LOG 175 176 } // namespace JS::loader