summaryrefslogtreecommitdiffstats
path: root/js/src/vm/ConcurrentDelazification.h
blob: 9dfbc9a9a1dae23602b9d046a910ff99117c1301 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/* -*- 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/. */

#ifndef vm_ConcurrentDelazification_h
#define vm_ConcurrentDelazification_h

#include "mozilla/MemoryReporting.h"  // mozilla::MallocSizeOf

#include <stddef.h>  // size_t
#include <utility>   // std::pair

#include "frontend/CompilationStencil.h"  // frontend::{CompilationStencil, ScriptStencilRef, CompilationStencilMerger}
#include "frontend/ScriptIndex.h"  // frontend::ScriptIndex
#include "js/AllocPolicy.h"        // SystemAllocPolicy
#include "js/CompileOptions.h"  // JS::PrefableCompileOptions, JS::ReadOnlyCompileOptions
#include "js/UniquePtr.h"  // UniquePtr
#include "js/Vector.h"     // Vector

namespace js {

class FrontendContext;

// Base class for implementing the various strategies to iterate over the
// functions to be delazified, or to decide when to stop doing any
// delazification.
//
// When created, the `add` function should be called with the top-level
// ScriptIndex.
struct DelazifyStrategy {
  using ScriptIndex = frontend::ScriptIndex;
  virtual ~DelazifyStrategy() = default;

  // Returns true if no more functions should be delazified. Note, this does not
  // imply that every function got delazified.
  virtual bool done() const = 0;

  // Return a function identifier which represent the next function to be
  // delazified. If no more function should be delazified, then return 0.
  virtual ScriptIndex next() = 0;

  // Empty the list of functions to be processed next. done() should return true
  // 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.
  //
  // 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 `DelazificationContext`.
  [[nodiscard]] bool add(FrontendContext* fc,
                         const frontend::CompilationStencil& stencil,
                         ScriptIndex index);
};

// Delazify all functions using a Depth First traversal of the function-tree
// ordered, where each functions is visited in source-order.
//
// When `add` is called with the top-level ScriptIndex. This will push all inner
// functions to a stack such that they are popped in source order. Each
// function, once delazified, would be used to schedule their inner functions
// the same way.
//
// Hypothesis: This strategy parses all functions in source order, with the
// expectation that calls will follow the same order, and that helper thread
// would always be ahead of the execution.
struct DepthFirstDelazification final : public DelazifyStrategy {
  Vector<ScriptIndex, 0, SystemAllocPolicy> stack;

  bool done() const override { return stack.empty(); }
  ScriptIndex next() override { return stack.popCopy(); }
  void clear() override { return stack.clear(); }
  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;
};

class DelazificationContext {
  const JS::PrefableCompileOptions initialPrefableOptions_;

  // Queue of functions to be processed while delazifying.
  UniquePtr<DelazifyStrategy> strategy_;

  // Every delazified function is merged back to provide context for delazifying
  // even more functions.
  frontend::CompilationStencilMerger merger_;

  // Record any errors happening while parsing or generating bytecode.
  FrontendContext fc_;

  size_t stackQuota_;

  bool isInterrupted_ = false;

 public:
  explicit DelazificationContext(
      const JS::PrefableCompileOptions& initialPrefableOptions,
      size_t stackQuota)
      : initialPrefableOptions_(initialPrefableOptions),
        stackQuota_(stackQuota) {}

  bool init(const JS::ReadOnlyCompileOptions& options,
            const frontend::CompilationStencil& stencil);
  bool delazify();

  // This function is called by `delazify` function to know whether the
  // delazification should be interrupted.
  //
  // The `delazify` function holds on a thread until all functions iterated
  // over by the strategy. However, as a `delazify` function iterates over
  // multiple functions, it can easily be interrupted at function boundaries.
  //
  // TODO: (Bug 1773683) Plug this with the mozilla::Task::RequestInterrupt
  // function which is wrapping HelperThreads tasks within Mozilla.
  bool isInterrupted() const { return isInterrupted_; }
  void interrupt() { isInterrupted_ = true; }

  bool done() const;

  size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
};

} /* namespace js */

#endif /* vm_ConcurrentDelazification_h */