Loading js/src/jsapi-tests/moz.build +1 −0 Original line number Diff line number Diff line Loading @@ -94,6 +94,7 @@ UNIFIED_SOURCES += [ 'testSavedStacks.cpp', 'testScriptInfo.cpp', 'testScriptObject.cpp', 'testScriptSourceCompression.cpp', 'testSetProperty.cpp', 'testSetPropertyIgnoringNamedGetter.cpp', 'testSharedImmutableStringsCache.cpp', Loading js/src/jsapi-tests/testScriptSourceCompression.cpp 0 → 100644 +481 −0 Original line number Diff line number Diff line /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: */ /* 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 "mozilla/ArrayUtils.h" // mozilla::ArrayLength #include "mozilla/Assertions.h" // MOZ_RELEASE_ASSERT #include <algorithm> // std::all_of, std::copy_n, std::equal, std::move #include <memory> // std::uninitialized_fill_n #include <stddef.h> // size_t #include <stdint.h> // uint32_t #include "jsapi.h" // JS_FlattenString, JS_GC, JS_Get{Latin1,TwoByte}FlatStringChars, JS_GetStringLength, JS_ValueToFunction #include "js/CompilationAndEvaluation.h" // JS::Evaluate #include "js/CompileOptions.h" // JS::CompileOptions #include "js/Conversions.h" // JS::ToString #include "js/GCAPI.h" // JS_GC #include "js/MemoryFunctions.h" // JS_malloc #include "js/RootingAPI.h" // JS::MutableHandle, JS::Rooted #include "js/SourceText.h" // JS::SourceOwnership, JS::SourceText #include "js/UniquePtr.h" // js::UniquePtr #include "js/Utility.h" // JS::FreePolicy #include "js/Value.h" // JS::NullValue, JS::ObjectValue, JS::Value #include "jsapi-tests/tests.h" #include "vm/Compression.h" // js::Compressor::CHUNK_SIZE #include "vm/JSFunction.h" // JSFunction::getOrCreateScript #include "vm/JSScript.h" // JSScript, js::ScriptSource::MinimumCompressibleLength using mozilla::ArrayLength; struct JSContext; class JSString; template<typename CharT> using Source = js::UniquePtr<CharT[], JS::FreePolicy>; constexpr size_t ChunkSize = js::Compressor::CHUNK_SIZE; constexpr size_t MinimumCompressibleLength = js::ScriptSource::MinimumCompressibleLength; // Don't use ' ' to spread stuff across lines. constexpr char FillerWhitespace = '\n'; template<typename CharT> static Source<CharT> MakeSourceAllWhitespace(JSContext* cx, size_t len) { static_assert(ChunkSize % sizeof(CharT) == 0, "chunk size presumed to be a multiple of char size"); Source<CharT> source(reinterpret_cast<CharT*>(JS_malloc(cx, len * sizeof(CharT)))); if (source) { std::uninitialized_fill_n(source.get(), len, FillerWhitespace); } return source; } static bool Evaluate(JSContext* cx, const JS::CompileOptions& options, const char16_t* src, size_t srclen) { JS::SourceText<char16_t> sourceText; if (!sourceText.init(cx, src, srclen, JS::SourceOwnership::Borrowed)) { return false; } JS::Rooted<JS::Value> dummy(cx); return JS::Evaluate(cx, options, sourceText, &dummy); } template<typename CharT> static JSFunction* EvaluateChars(JSContext* cx, Source<CharT> chars, size_t len, char functionName, const char* func) { JS::CompileOptions options(cx); options.setFileAndLine(func, 1); if (!Evaluate(cx, options, chars.get(), len)) { return nullptr; } JS::Rooted<JS::Value> rval(cx); const char16_t name[] = { char16_t(functionName) }; JS::SourceText<char16_t> srcbuf; if (!srcbuf.init(cx, name, ArrayLength(name), JS::SourceOwnership::Borrowed)) { return nullptr; } if (!JS::Evaluate(cx, options, srcbuf, &rval)) { return nullptr; } MOZ_RELEASE_ASSERT(rval.isObject()); return JS_ValueToFunction(cx, rval); } static void CompressSourceSync(JS::Handle<JSFunction*> fun, JSContext* cx) { JS::Rooted<JSScript*> script(cx, JSFunction::getOrCreateScript(cx, fun)); MOZ_RELEASE_ASSERT(script); MOZ_RELEASE_ASSERT(script->scriptSource()->hasSourceText()); // Hoodoo voodoo that presently compresses |fun|'s source. // XXX Be a mensch: replace this with targeted actions to do this! JS_GC(cx); JS_GC(cx); MOZ_RELEASE_ASSERT(script->scriptSource()->hasCompressedSource()); } static constexpr char FunctionStart[] = "function @() {"; constexpr size_t FunctionStartLength = ArrayLength(FunctionStart) - 1; constexpr size_t FunctionNameOffset = 9; static_assert(FunctionStart[FunctionNameOffset] == '@', "offset must correctly point at the function name location"); static constexpr char FunctionEnd[] = "return 42; }"; constexpr size_t FunctionEndLength = ArrayLength(FunctionEnd) - 1; template<typename CharT> static void WriteFunctionOfSizeAtOffset(Source<CharT>& source, size_t usableSourceLen, char functionName, size_t functionLength, size_t offset) { MOZ_RELEASE_ASSERT(functionLength >= MinimumCompressibleLength, "function must be a certain size to be compressed"); MOZ_RELEASE_ASSERT(offset <= usableSourceLen, "offset must not exceed usable source"); MOZ_RELEASE_ASSERT(functionLength <= usableSourceLen, "function must fit in usable source"); MOZ_RELEASE_ASSERT(offset <= usableSourceLen - functionLength, "function must not extend past usable source"); // Fill in the function start. std::copy_n(FunctionStart, FunctionStartLength, &source[offset]); source[offset + FunctionNameOffset] = functionName; // Fill in the function end. std::copy_n(FunctionEnd, FunctionEndLength, &source[offset + functionLength - FunctionEndLength]); } static JSString* DecompressSource(JSContext* cx, JS::Handle<JSFunction*> fun) { JS::Rooted<JS::Value> fval(cx, JS::ObjectValue(*JS_GetFunctionObject(fun))); return JS::ToString(cx, fval); } static bool IsExpectedFunctionString(JS::Handle<JSString*> str, char functionName, JSContext* cx) { JSFlatString* fstr = JS_FlattenString(cx, str); MOZ_RELEASE_ASSERT(fstr); size_t len = JS_GetStringLength(str); if (len < FunctionStartLength || len < FunctionEndLength) { return false; } JS::AutoAssertNoGC nogc(cx); auto CheckContents = [functionName, len](const auto* chars) { // Check the function in parts: // // * "function " // * "A" // * "() {" // * "\n...\n" // * "return 42; }" return std::equal(chars, chars + FunctionNameOffset, FunctionStart) && chars[FunctionNameOffset] == functionName && std::equal(chars + FunctionNameOffset + 1, chars + FunctionStartLength, FunctionStart + FunctionNameOffset + 1) && std::all_of(chars + FunctionStartLength, chars + len - FunctionEndLength, [](auto c) { return c == FillerWhitespace; }) && std::equal(chars + len - FunctionEndLength, chars + len, FunctionEnd); }; bool hasExpectedContents; if (JS_StringHasLatin1Chars(str)) { const JS::Latin1Char* chars = JS_GetLatin1FlatStringChars(nogc, fstr); hasExpectedContents = CheckContents(chars); } else { const char16_t* chars = JS_GetTwoByteFlatStringChars(nogc, fstr); hasExpectedContents = CheckContents(chars); } return hasExpectedContents; } BEGIN_TEST(testScriptSourceCompression_inOneChunk) { CHECK(run<char16_t>()); return true; } template<typename CharT> bool run() { constexpr size_t len = MinimumCompressibleLength + 55; auto source = MakeSourceAllWhitespace<CharT>(cx, len); CHECK(source); // Write out a 'b' or 'c' function that is long enough to be compressed, // that starts after source start and ends before source end. constexpr char FunctionName = 'a' + sizeof(CharT); WriteFunctionOfSizeAtOffset(source, len, FunctionName, MinimumCompressibleLength, len - MinimumCompressibleLength); JS::Rooted<JSFunction*> fun(cx); fun = EvaluateChars(cx, std::move(source), len, FunctionName, __FUNCTION__); CHECK(fun); CompressSourceSync(fun, cx); JS::Rooted<JSString*> str(cx, DecompressSource(cx, fun)); CHECK(str); CHECK(IsExpectedFunctionString(str, FunctionName, cx)); return true; } END_TEST(testScriptSourceCompression_inOneChunk) BEGIN_TEST(testScriptSourceCompression_endsAtBoundaryInOneChunk) { CHECK(run<char16_t>()); return true; } template<typename CharT> bool run() { constexpr size_t len = ChunkSize / sizeof(CharT); auto source = MakeSourceAllWhitespace<CharT>(cx, len); CHECK(source); // Write out a 'd' or 'e' function that is long enough to be compressed, // that (for no particular reason) starts after source start and ends // before usable source end. constexpr char FunctionName = 'c' + sizeof(CharT); WriteFunctionOfSizeAtOffset(source, len, FunctionName, MinimumCompressibleLength, len - MinimumCompressibleLength); JS::Rooted<JSFunction*> fun(cx); fun = EvaluateChars(cx, std::move(source), len, FunctionName, __FUNCTION__); CHECK(fun); CompressSourceSync(fun, cx); JS::Rooted<JSString*> str(cx, DecompressSource(cx, fun)); CHECK(str); CHECK(IsExpectedFunctionString(str, FunctionName, cx)); return true; } END_TEST(testScriptSourceCompression_endsAtBoundaryInOneChunk) BEGIN_TEST(testScriptSourceCompression_isExactChunk) { CHECK(run<char16_t>()); return true; } template<typename CharT> bool run() { constexpr size_t len = ChunkSize / sizeof(CharT); auto source = MakeSourceAllWhitespace<CharT>(cx, len); CHECK(source); // Write out a 'f' or 'g' function that occupies the entire source (and // entire chunk, too). constexpr char FunctionName = 'e' + sizeof(CharT); WriteFunctionOfSizeAtOffset(source, len, FunctionName, len, 0); JS::Rooted<JSFunction*> fun(cx); fun = EvaluateChars(cx, std::move(source), len, FunctionName, __FUNCTION__); CHECK(fun); CompressSourceSync(fun, cx); JS::Rooted<JSString*> str(cx, DecompressSource(cx, fun)); CHECK(str); CHECK(IsExpectedFunctionString(str, FunctionName, cx)); return true; } END_TEST(testScriptSourceCompression_isExactChunk) BEGIN_TEST(testScriptSourceCompression_crossesChunkBoundary) { CHECK(run<char16_t>()); return true; } template<typename CharT> bool run() { constexpr size_t len = ChunkSize / sizeof(CharT) + 293; auto source = MakeSourceAllWhitespace<CharT>(cx, len); CHECK(source); // This function crosses a chunk boundary but does not end at one. constexpr size_t FunctionSize = 177 + ChunkSize / sizeof(CharT); // Write out a 'h' or 'i' function. constexpr char FunctionName = 'g' + sizeof(CharT); WriteFunctionOfSizeAtOffset(source, len, FunctionName, FunctionSize, 37); JS::Rooted<JSFunction*> fun(cx); fun = EvaluateChars(cx, std::move(source), len, FunctionName, __FUNCTION__); CHECK(fun); CompressSourceSync(fun, cx); JS::Rooted<JSString*> str(cx, DecompressSource(cx, fun)); CHECK(str); CHECK(IsExpectedFunctionString(str, FunctionName, cx)); return true; } END_TEST(testScriptSourceCompression_crossesChunkBoundary) BEGIN_TEST(testScriptSourceCompression_crossesChunkBoundary_endsAtBoundary) { CHECK(run<char16_t>()); return true; } template<typename CharT> bool run() { // Exactly two chunks. constexpr size_t len = (2 * ChunkSize) / sizeof(CharT); auto source = MakeSourceAllWhitespace<CharT>(cx, len); CHECK(source); // This function crosses a chunk boundary, and it ends exactly at the end // of both the second chunk and the full source. constexpr size_t FunctionSize = 1 + ChunkSize / sizeof(CharT); // Write out a 'j' or 'k' function. constexpr char FunctionName = 'i' + sizeof(CharT); WriteFunctionOfSizeAtOffset(source, len, FunctionName, FunctionSize, len - FunctionSize); JS::Rooted<JSFunction*> fun(cx); fun = EvaluateChars(cx, std::move(source), len, FunctionName, __FUNCTION__); CHECK(fun); CompressSourceSync(fun, cx); JS::Rooted<JSString*> str(cx, DecompressSource(cx, fun)); CHECK(str); CHECK(IsExpectedFunctionString(str, FunctionName, cx)); return true; } END_TEST(testScriptSourceCompression_crossesChunkBoundary_endsAtBoundary) BEGIN_TEST(testScriptSourceCompression_containsWholeChunk) { CHECK(run<char16_t>()); return true; } template<typename CharT> bool run() { constexpr size_t len = (2 * ChunkSize) / sizeof(CharT) + 17; auto source = MakeSourceAllWhitespace<CharT>(cx, len); CHECK(source); // This function crosses two chunk boundaries and begins/ends in the middle // of chunk boundaries. constexpr size_t FunctionSize = 2 + ChunkSize / sizeof(CharT); // Write out a 'l' or 'm' function. constexpr char FunctionName = 'k' + sizeof(CharT); WriteFunctionOfSizeAtOffset(source, len, FunctionName, FunctionSize, ChunkSize / sizeof(CharT) - 1); JS::Rooted<JSFunction*> fun(cx); fun = EvaluateChars(cx, std::move(source), len, FunctionName, __FUNCTION__); CHECK(fun); CompressSourceSync(fun, cx); JS::Rooted<JSString*> str(cx, DecompressSource(cx, fun)); CHECK(str); CHECK(IsExpectedFunctionString(str, FunctionName, cx)); return true; } END_TEST(testScriptSourceCompression_containsWholeChunk) BEGIN_TEST(testScriptSourceCompression_containsWholeChunk_endsAtBoundary) { CHECK(run<char16_t>()); return true; } template<typename CharT> bool run() { // Exactly three chunks. constexpr size_t len = (3 * ChunkSize) / sizeof(CharT); auto source = MakeSourceAllWhitespace<CharT>(cx, len); CHECK(source); // This function crosses two chunk boundaries and ends at a chunk boundary. constexpr size_t FunctionSize = 1 + (2 * ChunkSize) / sizeof(CharT); // Write out a 'n' or 'o' function. constexpr char FunctionName = 'm' + sizeof(CharT); WriteFunctionOfSizeAtOffset(source, len, FunctionName, FunctionSize, ChunkSize / sizeof(CharT) - 1); JS::Rooted<JSFunction*> fun(cx); fun = EvaluateChars(cx, std::move(source), len, FunctionName, __FUNCTION__); CHECK(fun); CompressSourceSync(fun, cx); JS::Rooted<JSString*> str(cx, DecompressSource(cx, fun)); CHECK(str); CHECK(IsExpectedFunctionString(str, FunctionName, cx)); return true; } END_TEST(testScriptSourceCompression_containsWholeChunk_endsAtBoundary) BEGIN_TEST(testScriptSourceCompression_spansMultipleMiddleChunks) { CHECK(run<char16_t>()); return true; } template<typename CharT> bool run() { // Four chunks. constexpr size_t len = (4 * ChunkSize) / sizeof(CharT); auto source = MakeSourceAllWhitespace<CharT>(cx, len); CHECK(source); // This function spans the two middle chunks and further extends one // character to each side. constexpr size_t FunctionSize = 2 + (2 * ChunkSize) / sizeof(CharT); // Write out a 'p' or 'q' function. constexpr char FunctionName = 'o' + sizeof(CharT); WriteFunctionOfSizeAtOffset(source, len, FunctionName, FunctionSize, ChunkSize / sizeof(CharT) - 1); JS::Rooted<JSFunction*> fun(cx); fun = EvaluateChars(cx, std::move(source), len, FunctionName, __FUNCTION__); CHECK(fun); CompressSourceSync(fun, cx); JS::Rooted<JSString*> str(cx, DecompressSource(cx, fun)); CHECK(str); CHECK(IsExpectedFunctionString(str, FunctionName, cx)); return true; } END_TEST(testScriptSourceCompression_spansMultipleMiddleChunks) js/src/vm/JSScript.cpp +1 −2 Original line number Diff line number Diff line Loading @@ -1991,8 +1991,7 @@ ScriptSource::tryCompressOffThread(JSContext* cx) HelperThreadState().cpuCount > 1 && HelperThreadState().threadCount >= 2 && CanUseExtraThreads(); const size_t TINY_SCRIPT = 256; if (TINY_SCRIPT > length() || !canCompressOffThread) { if (length() < ScriptSource::MinimumCompressibleLength || !canCompressOffThread) { return true; } Loading js/src/vm/JSScript.h +6 −0 Original line number Diff line number Diff line Loading @@ -681,6 +681,12 @@ class ScriptSource const JS::ReadOnlyCompileOptions& options, const mozilla::Maybe<uint32_t>& parameterListEnd = mozilla::Nothing()); /** * The minimum script length (in code units) necessary for a script to be * eligible to be compressed. */ static constexpr size_t MinimumCompressibleLength = 256; template<typename Unit> MOZ_MUST_USE bool setSourceCopy(JSContext* cx, JS::SourceText<Unit>& srcBuf); Loading Loading
js/src/jsapi-tests/moz.build +1 −0 Original line number Diff line number Diff line Loading @@ -94,6 +94,7 @@ UNIFIED_SOURCES += [ 'testSavedStacks.cpp', 'testScriptInfo.cpp', 'testScriptObject.cpp', 'testScriptSourceCompression.cpp', 'testSetProperty.cpp', 'testSetPropertyIgnoringNamedGetter.cpp', 'testSharedImmutableStringsCache.cpp', Loading
js/src/jsapi-tests/testScriptSourceCompression.cpp 0 → 100644 +481 −0 Original line number Diff line number Diff line /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: */ /* 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 "mozilla/ArrayUtils.h" // mozilla::ArrayLength #include "mozilla/Assertions.h" // MOZ_RELEASE_ASSERT #include <algorithm> // std::all_of, std::copy_n, std::equal, std::move #include <memory> // std::uninitialized_fill_n #include <stddef.h> // size_t #include <stdint.h> // uint32_t #include "jsapi.h" // JS_FlattenString, JS_GC, JS_Get{Latin1,TwoByte}FlatStringChars, JS_GetStringLength, JS_ValueToFunction #include "js/CompilationAndEvaluation.h" // JS::Evaluate #include "js/CompileOptions.h" // JS::CompileOptions #include "js/Conversions.h" // JS::ToString #include "js/GCAPI.h" // JS_GC #include "js/MemoryFunctions.h" // JS_malloc #include "js/RootingAPI.h" // JS::MutableHandle, JS::Rooted #include "js/SourceText.h" // JS::SourceOwnership, JS::SourceText #include "js/UniquePtr.h" // js::UniquePtr #include "js/Utility.h" // JS::FreePolicy #include "js/Value.h" // JS::NullValue, JS::ObjectValue, JS::Value #include "jsapi-tests/tests.h" #include "vm/Compression.h" // js::Compressor::CHUNK_SIZE #include "vm/JSFunction.h" // JSFunction::getOrCreateScript #include "vm/JSScript.h" // JSScript, js::ScriptSource::MinimumCompressibleLength using mozilla::ArrayLength; struct JSContext; class JSString; template<typename CharT> using Source = js::UniquePtr<CharT[], JS::FreePolicy>; constexpr size_t ChunkSize = js::Compressor::CHUNK_SIZE; constexpr size_t MinimumCompressibleLength = js::ScriptSource::MinimumCompressibleLength; // Don't use ' ' to spread stuff across lines. constexpr char FillerWhitespace = '\n'; template<typename CharT> static Source<CharT> MakeSourceAllWhitespace(JSContext* cx, size_t len) { static_assert(ChunkSize % sizeof(CharT) == 0, "chunk size presumed to be a multiple of char size"); Source<CharT> source(reinterpret_cast<CharT*>(JS_malloc(cx, len * sizeof(CharT)))); if (source) { std::uninitialized_fill_n(source.get(), len, FillerWhitespace); } return source; } static bool Evaluate(JSContext* cx, const JS::CompileOptions& options, const char16_t* src, size_t srclen) { JS::SourceText<char16_t> sourceText; if (!sourceText.init(cx, src, srclen, JS::SourceOwnership::Borrowed)) { return false; } JS::Rooted<JS::Value> dummy(cx); return JS::Evaluate(cx, options, sourceText, &dummy); } template<typename CharT> static JSFunction* EvaluateChars(JSContext* cx, Source<CharT> chars, size_t len, char functionName, const char* func) { JS::CompileOptions options(cx); options.setFileAndLine(func, 1); if (!Evaluate(cx, options, chars.get(), len)) { return nullptr; } JS::Rooted<JS::Value> rval(cx); const char16_t name[] = { char16_t(functionName) }; JS::SourceText<char16_t> srcbuf; if (!srcbuf.init(cx, name, ArrayLength(name), JS::SourceOwnership::Borrowed)) { return nullptr; } if (!JS::Evaluate(cx, options, srcbuf, &rval)) { return nullptr; } MOZ_RELEASE_ASSERT(rval.isObject()); return JS_ValueToFunction(cx, rval); } static void CompressSourceSync(JS::Handle<JSFunction*> fun, JSContext* cx) { JS::Rooted<JSScript*> script(cx, JSFunction::getOrCreateScript(cx, fun)); MOZ_RELEASE_ASSERT(script); MOZ_RELEASE_ASSERT(script->scriptSource()->hasSourceText()); // Hoodoo voodoo that presently compresses |fun|'s source. // XXX Be a mensch: replace this with targeted actions to do this! JS_GC(cx); JS_GC(cx); MOZ_RELEASE_ASSERT(script->scriptSource()->hasCompressedSource()); } static constexpr char FunctionStart[] = "function @() {"; constexpr size_t FunctionStartLength = ArrayLength(FunctionStart) - 1; constexpr size_t FunctionNameOffset = 9; static_assert(FunctionStart[FunctionNameOffset] == '@', "offset must correctly point at the function name location"); static constexpr char FunctionEnd[] = "return 42; }"; constexpr size_t FunctionEndLength = ArrayLength(FunctionEnd) - 1; template<typename CharT> static void WriteFunctionOfSizeAtOffset(Source<CharT>& source, size_t usableSourceLen, char functionName, size_t functionLength, size_t offset) { MOZ_RELEASE_ASSERT(functionLength >= MinimumCompressibleLength, "function must be a certain size to be compressed"); MOZ_RELEASE_ASSERT(offset <= usableSourceLen, "offset must not exceed usable source"); MOZ_RELEASE_ASSERT(functionLength <= usableSourceLen, "function must fit in usable source"); MOZ_RELEASE_ASSERT(offset <= usableSourceLen - functionLength, "function must not extend past usable source"); // Fill in the function start. std::copy_n(FunctionStart, FunctionStartLength, &source[offset]); source[offset + FunctionNameOffset] = functionName; // Fill in the function end. std::copy_n(FunctionEnd, FunctionEndLength, &source[offset + functionLength - FunctionEndLength]); } static JSString* DecompressSource(JSContext* cx, JS::Handle<JSFunction*> fun) { JS::Rooted<JS::Value> fval(cx, JS::ObjectValue(*JS_GetFunctionObject(fun))); return JS::ToString(cx, fval); } static bool IsExpectedFunctionString(JS::Handle<JSString*> str, char functionName, JSContext* cx) { JSFlatString* fstr = JS_FlattenString(cx, str); MOZ_RELEASE_ASSERT(fstr); size_t len = JS_GetStringLength(str); if (len < FunctionStartLength || len < FunctionEndLength) { return false; } JS::AutoAssertNoGC nogc(cx); auto CheckContents = [functionName, len](const auto* chars) { // Check the function in parts: // // * "function " // * "A" // * "() {" // * "\n...\n" // * "return 42; }" return std::equal(chars, chars + FunctionNameOffset, FunctionStart) && chars[FunctionNameOffset] == functionName && std::equal(chars + FunctionNameOffset + 1, chars + FunctionStartLength, FunctionStart + FunctionNameOffset + 1) && std::all_of(chars + FunctionStartLength, chars + len - FunctionEndLength, [](auto c) { return c == FillerWhitespace; }) && std::equal(chars + len - FunctionEndLength, chars + len, FunctionEnd); }; bool hasExpectedContents; if (JS_StringHasLatin1Chars(str)) { const JS::Latin1Char* chars = JS_GetLatin1FlatStringChars(nogc, fstr); hasExpectedContents = CheckContents(chars); } else { const char16_t* chars = JS_GetTwoByteFlatStringChars(nogc, fstr); hasExpectedContents = CheckContents(chars); } return hasExpectedContents; } BEGIN_TEST(testScriptSourceCompression_inOneChunk) { CHECK(run<char16_t>()); return true; } template<typename CharT> bool run() { constexpr size_t len = MinimumCompressibleLength + 55; auto source = MakeSourceAllWhitespace<CharT>(cx, len); CHECK(source); // Write out a 'b' or 'c' function that is long enough to be compressed, // that starts after source start and ends before source end. constexpr char FunctionName = 'a' + sizeof(CharT); WriteFunctionOfSizeAtOffset(source, len, FunctionName, MinimumCompressibleLength, len - MinimumCompressibleLength); JS::Rooted<JSFunction*> fun(cx); fun = EvaluateChars(cx, std::move(source), len, FunctionName, __FUNCTION__); CHECK(fun); CompressSourceSync(fun, cx); JS::Rooted<JSString*> str(cx, DecompressSource(cx, fun)); CHECK(str); CHECK(IsExpectedFunctionString(str, FunctionName, cx)); return true; } END_TEST(testScriptSourceCompression_inOneChunk) BEGIN_TEST(testScriptSourceCompression_endsAtBoundaryInOneChunk) { CHECK(run<char16_t>()); return true; } template<typename CharT> bool run() { constexpr size_t len = ChunkSize / sizeof(CharT); auto source = MakeSourceAllWhitespace<CharT>(cx, len); CHECK(source); // Write out a 'd' or 'e' function that is long enough to be compressed, // that (for no particular reason) starts after source start and ends // before usable source end. constexpr char FunctionName = 'c' + sizeof(CharT); WriteFunctionOfSizeAtOffset(source, len, FunctionName, MinimumCompressibleLength, len - MinimumCompressibleLength); JS::Rooted<JSFunction*> fun(cx); fun = EvaluateChars(cx, std::move(source), len, FunctionName, __FUNCTION__); CHECK(fun); CompressSourceSync(fun, cx); JS::Rooted<JSString*> str(cx, DecompressSource(cx, fun)); CHECK(str); CHECK(IsExpectedFunctionString(str, FunctionName, cx)); return true; } END_TEST(testScriptSourceCompression_endsAtBoundaryInOneChunk) BEGIN_TEST(testScriptSourceCompression_isExactChunk) { CHECK(run<char16_t>()); return true; } template<typename CharT> bool run() { constexpr size_t len = ChunkSize / sizeof(CharT); auto source = MakeSourceAllWhitespace<CharT>(cx, len); CHECK(source); // Write out a 'f' or 'g' function that occupies the entire source (and // entire chunk, too). constexpr char FunctionName = 'e' + sizeof(CharT); WriteFunctionOfSizeAtOffset(source, len, FunctionName, len, 0); JS::Rooted<JSFunction*> fun(cx); fun = EvaluateChars(cx, std::move(source), len, FunctionName, __FUNCTION__); CHECK(fun); CompressSourceSync(fun, cx); JS::Rooted<JSString*> str(cx, DecompressSource(cx, fun)); CHECK(str); CHECK(IsExpectedFunctionString(str, FunctionName, cx)); return true; } END_TEST(testScriptSourceCompression_isExactChunk) BEGIN_TEST(testScriptSourceCompression_crossesChunkBoundary) { CHECK(run<char16_t>()); return true; } template<typename CharT> bool run() { constexpr size_t len = ChunkSize / sizeof(CharT) + 293; auto source = MakeSourceAllWhitespace<CharT>(cx, len); CHECK(source); // This function crosses a chunk boundary but does not end at one. constexpr size_t FunctionSize = 177 + ChunkSize / sizeof(CharT); // Write out a 'h' or 'i' function. constexpr char FunctionName = 'g' + sizeof(CharT); WriteFunctionOfSizeAtOffset(source, len, FunctionName, FunctionSize, 37); JS::Rooted<JSFunction*> fun(cx); fun = EvaluateChars(cx, std::move(source), len, FunctionName, __FUNCTION__); CHECK(fun); CompressSourceSync(fun, cx); JS::Rooted<JSString*> str(cx, DecompressSource(cx, fun)); CHECK(str); CHECK(IsExpectedFunctionString(str, FunctionName, cx)); return true; } END_TEST(testScriptSourceCompression_crossesChunkBoundary) BEGIN_TEST(testScriptSourceCompression_crossesChunkBoundary_endsAtBoundary) { CHECK(run<char16_t>()); return true; } template<typename CharT> bool run() { // Exactly two chunks. constexpr size_t len = (2 * ChunkSize) / sizeof(CharT); auto source = MakeSourceAllWhitespace<CharT>(cx, len); CHECK(source); // This function crosses a chunk boundary, and it ends exactly at the end // of both the second chunk and the full source. constexpr size_t FunctionSize = 1 + ChunkSize / sizeof(CharT); // Write out a 'j' or 'k' function. constexpr char FunctionName = 'i' + sizeof(CharT); WriteFunctionOfSizeAtOffset(source, len, FunctionName, FunctionSize, len - FunctionSize); JS::Rooted<JSFunction*> fun(cx); fun = EvaluateChars(cx, std::move(source), len, FunctionName, __FUNCTION__); CHECK(fun); CompressSourceSync(fun, cx); JS::Rooted<JSString*> str(cx, DecompressSource(cx, fun)); CHECK(str); CHECK(IsExpectedFunctionString(str, FunctionName, cx)); return true; } END_TEST(testScriptSourceCompression_crossesChunkBoundary_endsAtBoundary) BEGIN_TEST(testScriptSourceCompression_containsWholeChunk) { CHECK(run<char16_t>()); return true; } template<typename CharT> bool run() { constexpr size_t len = (2 * ChunkSize) / sizeof(CharT) + 17; auto source = MakeSourceAllWhitespace<CharT>(cx, len); CHECK(source); // This function crosses two chunk boundaries and begins/ends in the middle // of chunk boundaries. constexpr size_t FunctionSize = 2 + ChunkSize / sizeof(CharT); // Write out a 'l' or 'm' function. constexpr char FunctionName = 'k' + sizeof(CharT); WriteFunctionOfSizeAtOffset(source, len, FunctionName, FunctionSize, ChunkSize / sizeof(CharT) - 1); JS::Rooted<JSFunction*> fun(cx); fun = EvaluateChars(cx, std::move(source), len, FunctionName, __FUNCTION__); CHECK(fun); CompressSourceSync(fun, cx); JS::Rooted<JSString*> str(cx, DecompressSource(cx, fun)); CHECK(str); CHECK(IsExpectedFunctionString(str, FunctionName, cx)); return true; } END_TEST(testScriptSourceCompression_containsWholeChunk) BEGIN_TEST(testScriptSourceCompression_containsWholeChunk_endsAtBoundary) { CHECK(run<char16_t>()); return true; } template<typename CharT> bool run() { // Exactly three chunks. constexpr size_t len = (3 * ChunkSize) / sizeof(CharT); auto source = MakeSourceAllWhitespace<CharT>(cx, len); CHECK(source); // This function crosses two chunk boundaries and ends at a chunk boundary. constexpr size_t FunctionSize = 1 + (2 * ChunkSize) / sizeof(CharT); // Write out a 'n' or 'o' function. constexpr char FunctionName = 'm' + sizeof(CharT); WriteFunctionOfSizeAtOffset(source, len, FunctionName, FunctionSize, ChunkSize / sizeof(CharT) - 1); JS::Rooted<JSFunction*> fun(cx); fun = EvaluateChars(cx, std::move(source), len, FunctionName, __FUNCTION__); CHECK(fun); CompressSourceSync(fun, cx); JS::Rooted<JSString*> str(cx, DecompressSource(cx, fun)); CHECK(str); CHECK(IsExpectedFunctionString(str, FunctionName, cx)); return true; } END_TEST(testScriptSourceCompression_containsWholeChunk_endsAtBoundary) BEGIN_TEST(testScriptSourceCompression_spansMultipleMiddleChunks) { CHECK(run<char16_t>()); return true; } template<typename CharT> bool run() { // Four chunks. constexpr size_t len = (4 * ChunkSize) / sizeof(CharT); auto source = MakeSourceAllWhitespace<CharT>(cx, len); CHECK(source); // This function spans the two middle chunks and further extends one // character to each side. constexpr size_t FunctionSize = 2 + (2 * ChunkSize) / sizeof(CharT); // Write out a 'p' or 'q' function. constexpr char FunctionName = 'o' + sizeof(CharT); WriteFunctionOfSizeAtOffset(source, len, FunctionName, FunctionSize, ChunkSize / sizeof(CharT) - 1); JS::Rooted<JSFunction*> fun(cx); fun = EvaluateChars(cx, std::move(source), len, FunctionName, __FUNCTION__); CHECK(fun); CompressSourceSync(fun, cx); JS::Rooted<JSString*> str(cx, DecompressSource(cx, fun)); CHECK(str); CHECK(IsExpectedFunctionString(str, FunctionName, cx)); return true; } END_TEST(testScriptSourceCompression_spansMultipleMiddleChunks)
js/src/vm/JSScript.cpp +1 −2 Original line number Diff line number Diff line Loading @@ -1991,8 +1991,7 @@ ScriptSource::tryCompressOffThread(JSContext* cx) HelperThreadState().cpuCount > 1 && HelperThreadState().threadCount >= 2 && CanUseExtraThreads(); const size_t TINY_SCRIPT = 256; if (TINY_SCRIPT > length() || !canCompressOffThread) { if (length() < ScriptSource::MinimumCompressibleLength || !canCompressOffThread) { return true; } Loading
js/src/vm/JSScript.h +6 −0 Original line number Diff line number Diff line Loading @@ -681,6 +681,12 @@ class ScriptSource const JS::ReadOnlyCompileOptions& options, const mozilla::Maybe<uint32_t>& parameterListEnd = mozilla::Nothing()); /** * The minimum script length (in code units) necessary for a script to be * eligible to be compressed. */ static constexpr size_t MinimumCompressibleLength = 256; template<typename Unit> MOZ_MUST_USE bool setSourceCopy(JSContext* cx, JS::SourceText<Unit>& srcBuf); Loading