Bug 1867190 - Add prefs for PHC probablities r=glandium
[gecko.git] / js / public / UbiNodeShortestPaths.h
blobc5bd84d4e47c0d9dfa4cfd171250960510afe5dd
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/. */
7 #ifndef js_UbiNodeShortestPaths_h
8 #define js_UbiNodeShortestPaths_h
10 #include "mozilla/CheckedInt.h"
11 #include "mozilla/Maybe.h"
13 #include <utility>
15 #include "js/AllocPolicy.h"
16 #include "js/GCAPI.h"
17 #include "js/UbiNode.h"
18 #include "js/UbiNodeBreadthFirst.h"
19 #include "js/UniquePtr.h"
21 namespace JS {
22 namespace ubi {
24 /**
25 * A back edge along a path in the heap graph.
27 struct JS_PUBLIC_API BackEdge {
28 private:
29 Node predecessor_;
30 EdgeName name_;
32 public:
33 using Ptr = js::UniquePtr<BackEdge>;
35 BackEdge() : predecessor_(), name_(nullptr) {}
37 [[nodiscard]] bool init(const Node& predecessor, Edge& edge) {
38 MOZ_ASSERT(!predecessor_);
39 MOZ_ASSERT(!name_);
41 predecessor_ = predecessor;
42 name_ = std::move(edge.name);
43 return true;
46 BackEdge(const BackEdge&) = delete;
47 BackEdge& operator=(const BackEdge&) = delete;
49 BackEdge(BackEdge&& rhs)
50 : predecessor_(rhs.predecessor_), name_(std::move(rhs.name_)) {
51 MOZ_ASSERT(&rhs != this);
54 BackEdge& operator=(BackEdge&& rhs) {
55 this->~BackEdge();
56 new (this) BackEdge(std::move(rhs));
57 return *this;
60 Ptr clone() const;
62 const EdgeName& name() const { return name_; }
63 EdgeName& name() { return name_; }
65 const JS::ubi::Node& predecessor() const { return predecessor_; }
68 /**
69 * A path is a series of back edges from which we discovered a target node.
71 using Path = JS::ubi::Vector<BackEdge*>;
73 /**
74 * The `JS::ubi::ShortestPaths` type represents a collection of up to N shortest
75 * retaining paths for each of a target set of nodes, starting from the same
76 * root node.
78 struct JS_PUBLIC_API ShortestPaths {
79 private:
80 // Types, type aliases, and data members.
82 using BackEdgeVector = JS::ubi::Vector<BackEdge::Ptr>;
83 using NodeToBackEdgeVectorMap =
84 js::HashMap<Node, BackEdgeVector, js::DefaultHasher<Node>,
85 js::SystemAllocPolicy>;
87 struct Handler;
88 using Traversal = BreadthFirst<Handler>;
90 /**
91 * A `JS::ubi::BreadthFirst` traversal handler that records back edges for
92 * how we reached each node, allowing us to reconstruct the shortest
93 * retaining paths after the traversal.
95 struct Handler {
96 using NodeData = BackEdge;
98 ShortestPaths& shortestPaths;
99 size_t totalMaxPathsToRecord;
100 size_t totalPathsRecorded;
102 explicit Handler(ShortestPaths& shortestPaths)
103 : shortestPaths(shortestPaths),
104 totalMaxPathsToRecord(shortestPaths.targets_.count() *
105 shortestPaths.maxNumPaths_),
106 totalPathsRecorded(0) {}
108 bool operator()(Traversal& traversal, const JS::ubi::Node& origin,
109 JS::ubi::Edge& edge, BackEdge* back, bool first) {
110 MOZ_ASSERT(back);
111 MOZ_ASSERT(origin == shortestPaths.root_ ||
112 traversal.visited.has(origin));
113 MOZ_ASSERT(totalPathsRecorded < totalMaxPathsToRecord);
115 if (first && !back->init(origin, edge)) {
116 return false;
119 if (!shortestPaths.targets_.has(edge.referent)) {
120 return true;
123 // If `first` is true, then we moved the edge's name into `back` in
124 // the above call to `init`. So clone that back edge to get the
125 // correct edge name. If `first` is not true, then our edge name is
126 // still in `edge`. This accounts for the asymmetry between
127 // `back->clone()` in the first branch, and the `init` call in the
128 // second branch.
130 if (first) {
131 BackEdgeVector paths;
132 if (!paths.reserve(shortestPaths.maxNumPaths_)) {
133 return false;
135 auto cloned = back->clone();
136 if (!cloned) {
137 return false;
139 paths.infallibleAppend(std::move(cloned));
140 if (!shortestPaths.paths_.putNew(edge.referent, std::move(paths))) {
141 return false;
143 totalPathsRecorded++;
144 } else {
145 auto ptr = shortestPaths.paths_.lookup(edge.referent);
146 MOZ_ASSERT(ptr,
147 "This isn't the first time we have seen the target node "
148 "`edge.referent`. "
149 "We should have inserted it into shortestPaths.paths_ the "
150 "first time we "
151 "saw it.");
153 if (ptr->value().length() < shortestPaths.maxNumPaths_) {
154 auto thisBackEdge = js::MakeUnique<BackEdge>();
155 if (!thisBackEdge || !thisBackEdge->init(origin, edge)) {
156 return false;
158 ptr->value().infallibleAppend(std::move(thisBackEdge));
159 totalPathsRecorded++;
163 MOZ_ASSERT(totalPathsRecorded <= totalMaxPathsToRecord);
164 if (totalPathsRecorded == totalMaxPathsToRecord) {
165 traversal.stop();
168 return true;
172 // The maximum number of paths to record for each node.
173 uint32_t maxNumPaths_;
175 // The root node we are starting the search from.
176 Node root_;
178 // The set of nodes we are searching for paths to.
179 NodeSet targets_;
181 // The resulting paths.
182 NodeToBackEdgeVectorMap paths_;
184 // Need to keep alive the traversal's back edges so we can walk them later
185 // when the traversal is over when recreating the shortest paths.
186 Traversal::NodeMap backEdges_;
188 private:
189 // Private methods.
191 ShortestPaths(uint32_t maxNumPaths, const Node& root, NodeSet&& targets)
192 : maxNumPaths_(maxNumPaths),
193 root_(root),
194 targets_(std::move(targets)),
195 paths_(targets_.count()),
196 backEdges_() {
197 MOZ_ASSERT(maxNumPaths_ > 0);
198 MOZ_ASSERT(root_);
201 public:
202 // Public methods.
204 ShortestPaths(ShortestPaths&& rhs)
205 : maxNumPaths_(rhs.maxNumPaths_),
206 root_(rhs.root_),
207 targets_(std::move(rhs.targets_)),
208 paths_(std::move(rhs.paths_)),
209 backEdges_(std::move(rhs.backEdges_)) {
210 MOZ_ASSERT(this != &rhs, "self-move is not allowed");
213 ShortestPaths& operator=(ShortestPaths&& rhs) {
214 this->~ShortestPaths();
215 new (this) ShortestPaths(std::move(rhs));
216 return *this;
219 ShortestPaths(const ShortestPaths&) = delete;
220 ShortestPaths& operator=(const ShortestPaths&) = delete;
223 * Construct a new `JS::ubi::ShortestPaths`, finding up to `maxNumPaths`
224 * shortest retaining paths for each target node in `targets` starting from
225 * `root`.
227 * The resulting `ShortestPaths` instance must not outlive the
228 * `JS::ubi::Node` graph it was constructed from.
230 * - For `JS::ubi::Node` graphs backed by the live heap graph, this means
231 * that the `ShortestPaths`'s lifetime _must_ be contained within the
232 * scope of the provided `AutoCheckCannotGC` reference because a GC will
233 * invalidate the nodes.
235 * - For `JS::ubi::Node` graphs backed by some other offline structure
236 * provided by the embedder, the resulting `ShortestPaths`'s lifetime is
237 * bounded by that offline structure's lifetime.
239 * Returns `mozilla::Nothing()` on OOM failure. It is the caller's
240 * responsibility to handle and report the OOM.
242 static mozilla::Maybe<ShortestPaths> Create(JSContext* cx,
243 AutoCheckCannotGC& noGC,
244 uint32_t maxNumPaths,
245 const Node& root,
246 NodeSet&& targets) {
247 MOZ_ASSERT(targets.count() > 0);
248 MOZ_ASSERT(maxNumPaths > 0);
250 mozilla::CheckedInt<uint32_t> max = maxNumPaths;
251 max *= targets.count();
252 if (!max.isValid()) {
253 return mozilla::Nothing();
256 ShortestPaths paths(maxNumPaths, root, std::move(targets));
258 Handler handler(paths);
259 Traversal traversal(cx, handler, noGC);
260 traversal.wantNames = true;
261 if (!traversal.addStart(root) || !traversal.traverse()) {
262 return mozilla::Nothing();
265 // Take ownership of the back edges we created while traversing the
266 // graph so that we can follow them from `paths_` and don't
267 // use-after-free.
268 paths.backEdges_ = std::move(traversal.visited);
270 return mozilla::Some(std::move(paths));
274 * Get an iterator over each target node we searched for retaining paths
275 * for. The returned iterator must not outlive the `ShortestPaths`
276 * instance.
278 NodeSet::Iterator targetIter() const { return targets_.iter(); }
281 * Invoke the provided functor/lambda/callable once for each retaining path
282 * discovered for `target`. The `func` is passed a single `JS::ubi::Path&`
283 * argument, which contains each edge along the path ordered starting from
284 * the root and ending at the target, and must not outlive the scope of the
285 * call.
287 * Note that it is possible that we did not find any paths from the root to
288 * the given target, in which case `func` will not be invoked.
290 template <class Func>
291 [[nodiscard]] bool forEachPath(const Node& target, Func func) {
292 MOZ_ASSERT(targets_.has(target));
294 auto ptr = paths_.lookup(target);
296 // We didn't find any paths to this target, so nothing to do here.
297 if (!ptr) {
298 return true;
301 MOZ_ASSERT(ptr->value().length() <= maxNumPaths_);
303 Path path;
304 for (const auto& backEdge : ptr->value()) {
305 path.clear();
307 if (!path.append(backEdge.get())) {
308 return false;
311 Node here = backEdge->predecessor();
312 MOZ_ASSERT(here);
314 while (here != root_) {
315 auto p = backEdges_.lookup(here);
316 MOZ_ASSERT(p);
317 if (!path.append(&p->value())) {
318 return false;
320 here = p->value().predecessor();
321 MOZ_ASSERT(here);
324 path.reverse();
326 if (!func(path)) {
327 return false;
331 return true;
335 #ifdef DEBUG
336 // A helper function to dump the first `maxNumPaths` shortest retaining paths to
337 // `node` from the GC roots. Useful when GC things you expect to have been
338 // reclaimed by the collector haven't been!
340 // Usage:
342 // JSObject* foo = ...;
343 // JS::ubi::dumpPaths(rt, JS::ubi::Node(foo));
344 JS_PUBLIC_API void dumpPaths(JSRuntime* rt, Node node,
345 uint32_t maxNumPaths = 10);
346 #endif
348 } // namespace ubi
349 } // namespace JS
351 #endif // js_UbiNodeShortestPaths_h