Bug 1774796 - Add largest-first delazification strategy. r=arai

This patch adds a new off-thread delazification strategy, which adds a bit of
overhead to queue function with a priority order, which put the largest function
as the first functions to be delazified.

The intent of this new strategy is to limit the usage of main-thread
delazification to the smallest function, in case where the main-thread would win
the delazification race.

Differential Revision: https://phabricator.services.mozilla.com/D149651
This commit is contained in:
Nicolas B. Pierron 2022-06-20 13:33:49 +00:00
Родитель 8c721d23bb
Коммит 7d50cd5850
6 изменённых файлов: 147 добавлений и 12 удалений

Просмотреть файл

@ -1701,6 +1701,10 @@ nsresult ScriptLoader::AttemptAsyncScriptCompile(ScriptLoadRequest* aRequest,
TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(),
"delazification_concurrent_depth_first");
break;
case JS::DelazificationOption::ConcurrentLargeFirst:
TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(),
"delazification_concurrent_large_first");
break;
case JS::DelazificationOption::ParseEverythingEagerly:
TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(),
"delazification_parse_everything_eagerly");

Просмотреть файл

@ -88,6 +88,11 @@ enum class AsmJSOption : uint8_t {
*/ \
_(ConcurrentDepthFirst) \
\
/* \
* Delazifiy functions strating with the largest function first. \
*/ \
_(ConcurrentLargeFirst) \
\
/* \
* Parse everything eagerly, from the first parse. \
* \
@ -266,12 +271,14 @@ class JS_PUBLIC_API TransitiveCompileOptions {
bool forceStrictMode() const { return forceStrictMode_; }
bool consumeDelazificationCache() const {
return eagerDelazificationIsOneOf<
DelazificationOption::ConcurrentDepthFirst>();
DelazificationOption::ConcurrentDepthFirst,
DelazificationOption::ConcurrentLargeFirst>();
}
bool populateDelazificationCache() const {
return eagerDelazificationIsOneOf<
DelazificationOption::CheckConcurrentWithOnDemand,
DelazificationOption::ConcurrentDepthFirst>();
DelazificationOption::ConcurrentDepthFirst,
DelazificationOption::ConcurrentLargeFirst>();
}
bool waitForDelazificationCache() const {
return eagerDelazificationIsOneOf<

Просмотреть файл

@ -16,6 +16,12 @@ const optionsLazyCache = {
eagerDelazificationStrategy: "ConcurrentDepthFirst",
};
const optionsLazyCache2 = {
fileName: "compileToStencil-DATA.js",
lineNumber: 1,
eagerDelazificationStrategy: "ConcurrentLargeFirst",
};
let result = 0;
function testMainThread(script_str) {
@ -47,6 +53,18 @@ function testMainThreadCacheAll(script_str) {
assertEq(result, 1);
}
function testMainThreadCacheAll2(script_str) {
if (isLcovEnabled() || helperThreadCount() === 0) {
// Code-coverage implies forceFullParse = true, and as such it cannot be
// used while testing to incrementally delazify.
// Similarly, concurrent delazification requires off-threads processing.
return;
}
const stencil = compileToStencil(script_str, optionsLazyCache2);
result = evalStencil(stencil, optionsLazyCache2);
assertEq(result, 1);
}
function testOffThread(script_str) {
const job = offThreadCompileToStencil(script_str, optionsFull);
const stencil = finishOffThreadCompileToStencil(job);
@ -109,6 +127,7 @@ for (let s = 0; s < 3000; s++) {
testMainThread(code);
testMainThreadDelazifyAll(code);
testMainThreadCacheAll(code);
testMainThreadCacheAll2(code);
if (helperThreadCount() > 0) {
testOffThread(code);
}

Просмотреть файл

@ -43,6 +43,10 @@ struct FreeDelazifyTask;
struct PromiseHelperTask;
class PromiseObject;
namespace frontend {
struct ScriptStencilRef;
}
namespace jit {
class IonCompileTask;
class IonFreeTask;
@ -609,6 +613,13 @@ struct DelazifyStrategy {
// after this call.
virtual void clear() = 0;
// Insert an index in the container of the delazification strategy. A strategy
// can choose to ignore the insertion of an index in its queue of function to
// delazify. Return false only in case of errors while inserting, and true
// otherwise.
[[nodiscard]] virtual bool insert(ScriptIndex index,
frontend::ScriptStencilRef& ref) = 0;
// Add the inner functions of a delazified function. This function should only
// be called with a function which has some bytecode associated with it, and
// register functions which parent are already delazified.
@ -616,9 +627,9 @@ struct DelazifyStrategy {
// This function is called with the script index of:
// - top-level script, when starting the off-thread delazification.
// - functions added by `add` and delazified by `DelazifyTask`.
[[nodiscard]] virtual bool add(JSContext* cx,
const frontend::CompilationStencil& stencil,
ScriptIndex index) = 0;
[[nodiscard]] bool add(JSContext* cx,
const frontend::CompilationStencil& stencil,
ScriptIndex index);
};
// Delazify all functions using a Depth First traversal of the function-tree
@ -638,9 +649,23 @@ struct DepthFirstDelazification final : public DelazifyStrategy {
bool done() const override { return stack.empty(); }
ScriptIndex next() override { return stack.popCopy(); }
void clear() override { return stack.clear(); }
[[nodiscard]] bool add(JSContext* cx,
const frontend::CompilationStencil& stencil,
ScriptIndex index) override;
bool insert(ScriptIndex index, frontend::ScriptStencilRef&) override {
return stack.append(index);
}
};
// Delazify all functions using a traversal which select the largest function
// first. The intent being that if the main thread races with the helper thread,
// then the main thread should only have to parse small functions instead of the
// large ones which would be prioritized by this delazification strategy.
struct LargeFirstDelazification final : public DelazifyStrategy {
using SourceSize = uint32_t;
Vector<std::pair<SourceSize, ScriptIndex>, 0, SystemAllocPolicy> heap;
bool done() const override { return heap.empty(); }
ScriptIndex next() override;
void clear() override { return heap.clear(); }
bool insert(ScriptIndex, frontend::ScriptStencilRef&) override;
};
// Eagerly delazify functions, and send the result back to the runtime which

Просмотреть файл

@ -873,9 +873,9 @@ bool js::StartOffThreadDelazification(
return true;
}
bool DepthFirstDelazification::add(JSContext* cx,
const frontend::CompilationStencil& stencil,
ScriptIndex index) {
bool DelazifyStrategy::add(JSContext* cx,
const frontend::CompilationStencil& stencil,
ScriptIndex index) {
using namespace js::frontend;
ScriptStencilRef scriptRef{stencil, index};
@ -909,7 +909,8 @@ bool DepthFirstDelazification::add(JSContext* cx,
continue;
}
if (!stack.append(innerScriptIndex)) {
// Maybe insert the new script index in the queue of functions to delazify.
if (!insert(innerScriptIndex, innerScriptRef)) {
ReportOutOfMemory(cx);
return false;
}
@ -918,6 +919,75 @@ bool DepthFirstDelazification::add(JSContext* cx,
return true;
}
DelazifyStrategy::ScriptIndex LargeFirstDelazification::next() {
std::swap(heap.back(), heap[0]);
ScriptIndex result = heap.popCopy().second;
// NOTE: These are a heap indexes offseted by 1, such that we can manipulate
// the tree of heap-sorted values which bubble up the largest values towards
// the root of the tree.
size_t len = heap.length();
size_t i = 1;
while (true) {
// NOTE: We write (n + 1) - 1, instead of n, to explicit that the
// manipualted indexes are all offseted by 1.
size_t n = 2 * i;
size_t largest;
if (n + 1 <= len && heap[(n + 1) - 1].first > heap[n - 1].first) {
largest = n + 1;
} else if (n <= len) {
// The condition is n <= len in case n + 1 is out of the heap vector, but
// not n, in which case we still want to check if the last element of the
// heap vector should be swapped. Otherwise heap[n - 1] represents a
// larger function than heap[(n + 1) - 1].
largest = n;
} else {
// n is out-side the heap vector, thus our element is already in a leaf
// position and would not be moved any more.
break;
}
if (heap[i - 1].first < heap[largest - 1].first) {
// We found a function which has a larger body as a child of the current
// element. we swap it with the current element, such that the largest
// element is closer to the root of the tree.
std::swap(heap[i - 1], heap[largest - 1]);
i = largest;
} else {
// The largest function found as a child of the current node is smaller
// than the current node's function size. The heap tree is now organized
// as expected.
break;
}
}
return result;
}
bool LargeFirstDelazification::insert(ScriptIndex index,
frontend::ScriptStencilRef& ref) {
const frontend::ScriptStencilExtra& extra = ref.scriptExtra();
SourceSize size = extra.extent.sourceEnd - extra.extent.sourceStart;
if (!heap.append(std::pair(size, index))) {
return false;
}
// NOTE: These are a heap indexes offseted by 1, such that we can manipulate
// the tree of heap-sorted values which bubble up the largest values towards
// the root of the tree.
size_t i = heap.length();
while (i > 1) {
if (heap[i - 1].first <= heap[(i / 2) - 1].first) {
return true;
}
std::swap(heap[i - 1], heap[(i / 2) - 1]);
i /= 2;
}
return true;
}
UniquePtr<DelazifyTask> DelazifyTask::Create(
JSContext* cx, JSRuntime* runtime, const JS::ContextOptions& contextOptions,
const JS::ReadOnlyCompileOptions& options,
@ -989,6 +1059,11 @@ bool DelazifyTask::init(
// inner functions before the siblings functions.
strategy = cx->make_unique<DepthFirstDelazification>();
break;
case JS::DelazificationOption::ConcurrentLargeFirst:
// ConcurrentLargeFirst visit all functions to be delazified, visiting the
// largest function first.
strategy = cx->make_unique<LargeFirstDelazification>();
break;
case JS::DelazificationOption::ParseEverythingEagerly:
// ParseEverythingEagerly parse all functions eagerly, thus leaving no
// functions to be parsed on demand.

Просмотреть файл

@ -3231,6 +3231,11 @@
# 2: Depth-first. Delazify all functions off-thread in the order of appearance
# in the source.
#
# 3: Large-first. Delazify all functions off-thread starting with the largest
# functions first, and the smallest as the last one to be delazified, where
# the size of function is measured in bytes between the start to the end of
# the function.
#
# 255: Parse everything eagerly, from the first parse. All functions are parsed
# at the same time as the top-level of a file.
- name: dom.script_loader.delazification.strategy