Skip to content
File

Blob: src/workerd/jsg/memory.h

cpp631 lines
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5// Implements mechanism for incorporating details about the native (c++) objects
6// in a v8 heap snapshot. The design of the API and implementation were heavily
7// influenced by Node.js' implementation of the same feature.
8 
9#pragma once
10 
11#include <v8-profiler.h>
12 
13#include <kj/common.h>
14#include <kj/debug.h>
15#include <kj/exception.h>
16#include <kj/hash.h>
17#include <kj/map.h>
18#include <kj/string.h>
19#include <kj/table.h>
20 
21#include <stack>
22#include <string>
23 
24namespace v8 {
25class BackingStore;
26}
27 
28namespace workerd::jsg {
29 
30// The MemoryTracker is used to integrate with v8's BuildEmbedderGraph API.
31// It constructs the graph of embedder objects to be included in a generated
32// heap snapshot.
33//
34// The API is implemented using a visitor pattern. V8 calls the BuilderEmbedderGraph
35// callback (in setup.h) which in turn begins walking through the known embedder
36// objects collecting the necessary information.
37//
38// To instrument a struct or class so that it can be included in the graph, the
39// type must implement *at least* the following three methods:
40//
41// kj::StringPtr jsgGetMemoryName() const;
42// size_t jsgGetMemorySelfSize() const;
43// void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const;
44//
45// The jsgGetMemoryName() method returns the name that should be used to identify
46// the type in the graph. This will be prefixed with "workerd / " in the actual
47// generated snapshot. For instance, if this method returns "Foo"_kjc, the heap
48// snapshot will contain "workerd / Foo".
49//
50// The jsgGetMemorySelfSize() method returns the *shallow* size of the type.
51// This would typically be implemented using sizeof(Type), and in the vast
52// majority of cases that's all it does. It is provided as a method, however,
53// in order to allow a type the ability to customize the size calculation.
54//
55// The jsgGetMemoryInfo(...) method is the method that is actually called to
56// visit instances of the type to collect details for the graph. Note that this
57// method is NOT expected to be called within the scope of an IoContext. It will
58// be called while within the isolate lock, however.
59//
60// Types may also implement the following additional methods to further customize
61// how they are represented in the graph:
62//
63// v8::Local<v8::Object> jsgGetMemoryInfoWrapperObject();
64// MemoryInfoDetachedState jsgGetMemoryInfoDetachedState() const;
65// bool jsgGetMemoryInfoIsRootNode() const;
66//
67// Note that the `jsgGetMemoryInfoWrapperObject() method is called from within
68// a v8::HandleScope.
69//
70// For extremely simple cases, the JSG_MEMORY_INFO macro can be used to simplify
71// implementing these methods. It is a shortcut that provides basic implementations
72// of the jsgGetMemoryName() and jsgGetMemorySelfSize() methods:
73//
74// JSG_MEMORY_INFO(Foo) {
75// tracker.trackField("bar", bar);
76// }
77//
78// ... is equivalent to:
79//
80// kj::StringPtr jsgGetMemoryName() const { return "Foo"_kjc; }
81// size_t jsgGetMemorySelfSize() const { return sizeof(Foo); }
82// void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const {
83// tracker.trackField("bar", bar);
84// }
85//
86// All jsg::Object instances provide a basic implementation of these methods.
87// Within a jsg::Object, your only responsibility would be to implement the
88// helper visitForMemoryInfo(jsg::MemoryTracker& tracker) const method only
89// if the type has additional fields that need to be tracked. This works a
90// lot like the visitForGc(...) method used for GC tracing:
91//
92// class Foo : public jsg::Object {
93// public:
94// JSG_RESOURCE_TYPE(Foo) {}
95//
96// void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {
97// tracker.trackField("bar", bar);
98// }
99// // ...
100// };
101//
102// The constructed graph should include any fields that materially contribute
103// the retained memory of the type. This graph is primarily used for analysis
104// and investigation of memory issues in an application (e.g. hunting down
105// memory leaks, detecting bugs, optimizing memory usage, etc) so the information
106// should include details that are most useful for those purposes.
107//
108// This code is only ever called when a heap snapshot is being generated so
109// typically it should have very little cost. Heap snapshots are generally
110// fairly expensive to create, however, so care should be taken not to make
111// things too complicated. Ideally, none of the implementation methods in a
112// type should allocate. There is some allocation occurring internally while
113// building the graph, of course, but the methods for visitation (in particular
114// the jsgGetMemoryInfo(...) method) should not perform any allocations if it
115// can be avoided.
116 
117class MemoryTracker;
118class MemoryRetainerNode;
119 
120template <typename T>
121class V8Ref;
122template <typename T>
123class Ref;
124 
125enum class MemoryInfoDetachedState {
126 UNKNOWN,
127 ATTACHED,
128 DETACHED,
129};
130 
131template <typename T>
132concept MemoryRetainer = requires(const T* a) {
133 std::is_member_function_pointer_v<decltype(&T::jsgGetMemoryInfo)>;
134 std::is_member_function_pointer_v<decltype(&T::jsgGetMemoryName)>;
135 std::is_member_function_pointer_v<decltype(&T::jsgGetMemorySelfSize)>;
136};
137 
138template <typename T>
139concept MemoryRetainerObject = requires(T a) {
140 MemoryRetainer<T>;
141 std::is_member_function_pointer_v<decltype(&T::jsgGetMemoryInfoWrapperObject)>;
142};
143 
144template <typename T>
145concept MemoryRetainerDetachedState = requires(T a) {
146 MemoryRetainer<T>;
147 std::is_member_function_pointer_v<decltype(&T::jsgGetMemoryInfoDetachedState)>;
148};
149 
150template <typename T>
151concept MemoryRetainerIsRootNode = requires(T a) {
152 MemoryRetainer<T>;
153 std::is_member_function_pointer_v<decltype(&T::jsgGetMemoryInfoIsRootNode)>;
154};
155 
156template <typename T>
157concept V8Value = requires(T a) { std::is_assignable_v<v8::Value, T>; };
158 
159// sometimes jsgGetMemoryName is virtual sometimes it is not, so ¯\_(ツ)_/¯
160#define JSG_MEMORY_INFO(Name) \
161 _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wsuggest-override\"") \
162 kj::StringPtr \
163 jsgGetMemoryName() const { \
164 return #Name##_kjc; \
165 } \
166 size_t jsgGetMemorySelfSize() const { \
167 return sizeof(Name); \
168 } \
169 void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const _Pragma("GCC diagnostic pop")
170 
171// jsg::MemoryTracker is used to construct the embedder graph for v8 heap
172// snapshot construction.
173class MemoryTracker final {
174 public:
175 inline void trackFieldWithSize(
176 kj::StringPtr edgeName, size_t size, kj::Maybe<kj::StringPtr> nodeName = kj::none);
177 
178 inline void trackInlineFieldWithSize(
179 kj::StringPtr edgeName, size_t size, kj::Maybe<kj::StringPtr> nodeName = kj::none);
180 
181 template <MemoryRetainer T>
182 inline void trackField(kj::StringPtr edgeName,
183 const kj::Own<T>& value,
184 kj::Maybe<kj::StringPtr> nodeName = kj::none);
185 
186 template <MemoryRetainer T, typename D>
187 inline void trackField(kj::StringPtr edgeName,
188 const std::unique_ptr<T, D>& value,
189 kj::Maybe<kj::StringPtr> nodeName = kj::none);
190 
191 template <MemoryRetainer T>
192 inline void trackField(kj::StringPtr edgeName,
193 const std::shared_ptr<T>& value,
194 kj::Maybe<kj::StringPtr> nodeName = kj::none);
195 
196 template <V8Value T>
197 inline void trackField(
198 kj::StringPtr edgeName, const V8Ref<T>& value, kj::Maybe<kj::StringPtr> nodeName = kj::none);
199 
200 template <MemoryRetainer T>
201 inline void trackField(
202 kj::StringPtr edgeName, const Ref<T>& value, kj::Maybe<kj::StringPtr> nodeName = kj::none);
203 
204 template <MemoryRetainer T>
205 inline void trackField(
206 kj::StringPtr edgeName, const T& value, kj::Maybe<kj::StringPtr> nodeName = kj::none);
207 
208 template <typename T>
209 inline void trackField(kj::StringPtr edgeName,
210 const kj::Maybe<T>& value,
211 kj::Maybe<kj::StringPtr> nodeName = kj::none);
212 
213 template <typename T>
214 inline void trackField(kj::StringPtr edgeName,
215 const kj::Maybe<T&>& value,
216 kj::Maybe<kj::StringPtr> nodeName = kj::none);
217 
218 inline void trackField(kj::StringPtr edgeName,
219 const kj::String& value,
220 kj::Maybe<kj::StringPtr> nodeName = kj::none);
221 
222 inline void trackField(kj::StringPtr edgeName,
223 const kj::Exception& value,
224 kj::Maybe<kj::StringPtr> nodeName = kj::none);
225 
226 template <typename T,
227 typename test = std::enable_if_t<std::numeric_limits<T>::is_specialized, bool>,
228 typename dummy = bool>
229 inline void trackField(kj::StringPtr edgeName,
230 const kj::Array<T>& value,
231 kj::Maybe<kj::StringPtr> nodeName = kj::none);
232 
233 template <MemoryRetainer T, typename... Indexes>
234 void trackField(kj::StringPtr edgeName,
235 const kj::Table<T, Indexes...>& value,
236 kj::Maybe<kj::StringPtr> nodeName = kj::none,
237 kj::Maybe<kj::StringPtr> elementName = kj::none,
238 bool subtractFromSelf = true);
239 
240 template <typename Key, typename Value>
241 void trackField(kj::StringPtr edgeName,
242 const kj::HashMap<Key, Value>& value,
243 kj::Maybe<kj::StringPtr> nodeName = kj::none);
244 
245 template <MemoryRetainer T, typename Iterator = T::const_iterator>
246 inline void trackField(kj::StringPtr edgeName,
247 const T& value,
248 kj::Maybe<kj::StringPtr> nodeName = kj::none,
249 kj::Maybe<kj::StringPtr> elementName = kj::none,
250 bool subtractFromSelf = true);
251 
252 template <MemoryRetainer T>
253 inline void trackField(kj::StringPtr edgeName,
254 const kj::ArrayPtr<T>& value,
255 kj::Maybe<kj::StringPtr> nodeName = kj::none,
256 kj::Maybe<kj::StringPtr> elementName = kj::none,
257 bool subtractFromSelf = true);
258 
259 template <MemoryRetainer T>
260 inline void trackField(kj::StringPtr edgeName,
261 const kj::ArrayPtr<T* const>& value,
262 kj::Maybe<kj::StringPtr> nodeName = kj::none,
263 kj::Maybe<kj::StringPtr> elementName = kj::none,
264 bool subtractFromSelf = true);
265 
266 template <MemoryRetainer T>
267 inline void trackField(
268 kj::StringPtr edgeName, const T* value, kj::Maybe<kj::StringPtr> nodeName = kj::none);
269 
270 template <typename T>
271 inline void trackField(kj::StringPtr edgeName,
272 const std::basic_string<T>& value,
273 kj::Maybe<kj::StringPtr> nodeName = kj::none);
274 
275 template <V8Value T>
276 inline void trackField(
277 kj::StringPtr edgeName, const v8::Eternal<T>& value, kj::StringPtr nodeName);
278 
279 template <V8Value T>
280 inline void trackField(kj::StringPtr edgeName,
281 const v8::PersistentBase<T>& value,
282 kj::Maybe<kj::StringPtr> nodeName = kj::none);
283 
284 template <V8Value T>
285 inline void trackField(kj::StringPtr edgeName,
286 const v8::Local<T>& value,
287 kj::Maybe<kj::StringPtr> nodeName = kj::none);
288 
289 inline void trackField(kj::StringPtr edgeName,
290 const v8::BackingStore* value,
291 kj::Maybe<kj::StringPtr> nodeName = kj::none);
292 
293 template <MemoryRetainer T>
294 inline void track(const T* retainer, kj::Maybe<kj::StringPtr> edgeName = kj::none);
295 
296 template <MemoryRetainer T>
297 inline void trackInlineField(const T* retainer, kj::Maybe<kj::StringPtr> edgeName = kj::none);
298 
299 inline v8::Isolate* isolate() {
300 return isolate_;
301 }
302 
303 KJ_DISALLOW_COPY_AND_MOVE(MemoryTracker);
304 
305 private:
306 v8::Isolate* isolate_;
307 v8::EmbedderGraph* graph_;
308 std::stack<MemoryRetainerNode*> nodeStack_;
309 kj::HashMap<const void*, MemoryRetainerNode*> seen_;
310 KJ_DISALLOW_AS_COROUTINE_PARAM;
311 
312 explicit MemoryTracker(v8::Isolate* isolate, v8::EmbedderGraph* graph);
313 
314 KJ_NOINLINE MemoryRetainerNode* addNode(const void* retainer,
315 kj::StringPtr name,
316 size_t size,
317 v8::Local<v8::Object> obj,
318 kj::Maybe<kj::Function<bool()>> checkIsRootNode,
319 MemoryInfoDetachedState detachedness,
320 kj::Maybe<kj::StringPtr> edgeName);
321 
322 template <MemoryRetainer T>
323 inline MemoryRetainerNode* pushNode(
324 const T* retainer, kj::Maybe<kj::StringPtr> edgeName = kj::none);
325 
326 KJ_NOINLINE MemoryRetainerNode* addNode(
327 kj::StringPtr node_name, size_t size, kj::Maybe<kj::StringPtr> edgeName = kj::none);
328 
329 KJ_NOINLINE MemoryRetainerNode* pushNode(
330 kj::StringPtr node_name, size_t size, kj::Maybe<kj::StringPtr> edgeName = kj::none);
331 
332 KJ_NOINLINE void addEdge(MemoryRetainerNode* node, kj::StringPtr edgeName);
333 KJ_NOINLINE void addEdge(v8::EmbedderGraph::Node* node, kj::StringPtr edgeName);
334 void decCurrentNodeSize(size_t size);
335 
336 friend class IsolateBase;
337 friend class MemoryRetainerNode;
338};
339 
340// ======================================================================================
341 
342void MemoryTracker::trackFieldWithSize(
343 kj::StringPtr edgeName, size_t size, kj::Maybe<kj::StringPtr> nodeName) {
344 if (size > 0) addNode(nodeName.orDefault(edgeName), size, edgeName);
345}
346 
347void MemoryTracker::trackInlineFieldWithSize(
348 kj::StringPtr edgeName, size_t size, kj::Maybe<kj::StringPtr> nodeName) {
349 if (size > 0) addNode(nodeName.orDefault(edgeName), size, edgeName);
350}
351 
352void MemoryTracker::trackField(
353 kj::StringPtr edgeName, const kj::String& value, kj::Maybe<kj::StringPtr> nodeName) {
354 trackFieldWithSize(edgeName, value.size(), "kj::String"_kjc);
355}
356 
357void MemoryTracker::trackField(
358 kj::StringPtr edgeName, const kj::Exception& value, kj::Maybe<kj::StringPtr> nodeName) {
359 // Note that the size of the kj::Exception here only includes the
360 // shallow size of the kj::Exception type itself plus the length
361 // of the description string. We ignore the size of the stack and
362 // the context (if any). We could provide more detail but it's
363 // likely unnecessary.
364 trackFieldWithSize(
365 edgeName, sizeof(kj::Exception) + value.getDescription().size(), "kj::Exception"_kjc);
366}
367 
368template <MemoryRetainer T>
369void MemoryTracker::trackField(
370 kj::StringPtr edgeName, const kj::Own<T>& value, kj::Maybe<kj::StringPtr> nodeName) {
371 if (value.get() != nullptr) {
372 trackField(edgeName, value.get(), nodeName);
373 }
374}
375 
376template <MemoryRetainer T, typename D>
377void MemoryTracker::trackField(
378 kj::StringPtr edgeName, const std::unique_ptr<T, D>& value, kj::Maybe<kj::StringPtr> nodeName) {
379 if (value.get() != nullptr) {
380 return trackField(edgeName, value.get(), nodeName);
381 }
382}
383 
384template <MemoryRetainer T>
385void MemoryTracker::trackField(
386 kj::StringPtr edgeName, const std::shared_ptr<T>& value, kj::Maybe<kj::StringPtr> nodeName) {
387 if (value.get() != nullptr) {
388 return trackField(edgeName, value.get(), nodeName);
389 }
390}
391 
392template <typename T>
393void MemoryTracker::trackField(
394 kj::StringPtr edgeName, const kj::Maybe<T>& value, kj::Maybe<kj::StringPtr> nodeName) {
395 KJ_IF_SOME(v, value) {
396 trackField(edgeName, v, nodeName);
397 }
398}
399 
400template <typename T>
401void MemoryTracker::trackField(
402 kj::StringPtr edgeName, const kj::Maybe<T&>& value, kj::Maybe<kj::StringPtr> nodeName) {
403 KJ_IF_SOME(v, value) {
404 trackField(edgeName, v, nodeName);
405 }
406}
407 
408template <typename T>
409void MemoryTracker::trackField(
410 kj::StringPtr edgeName, const std::basic_string<T>& value, kj::Maybe<kj::StringPtr> nodeName) {
411 trackFieldWithSize(edgeName, value.size() * sizeof(T), "std::basic_string"_kjc);
412}
413 
414template <typename T, typename test, typename dummy>
415void MemoryTracker::trackField(
416 kj::StringPtr edgeName, const kj::Array<T>& value, kj::Maybe<kj::StringPtr> nodeName) {
417 trackFieldWithSize(edgeName, value.size() * sizeof(T), "kj::Array<T>"_kjc);
418}
419 
420template <MemoryRetainer T, typename... Indexes>
421void MemoryTracker::trackField(kj::StringPtr edgeName,
422 const kj::Table<T, Indexes...>& value,
423 kj::Maybe<kj::StringPtr> nodeName,
424 kj::Maybe<kj::StringPtr> elementName,
425 bool subtractFromSelf) {
426 if (value.begin() == value.end()) return;
427 if (subtractFromSelf) {
428 decCurrentNodeSize(sizeof(T));
429 }
430 pushNode(nodeName.orDefault(edgeName), sizeof(T), edgeName);
431 for (auto it = value.begin(); it != value.end(); ++it) {
432 trackField(nullptr, *it, elementName);
433 }
434 nodeStack_.pop();
435}
436 
437template <typename Key, typename Value>
438void MemoryTracker::trackField(kj::StringPtr edgeName,
439 const kj::HashMap<Key, Value>& value,
440 kj::Maybe<kj::StringPtr> nodeName) {
441 if (value.size() == 0) return;
442 pushNode(nodeName.orDefault(edgeName), sizeof(kj::HashMap<Key, Value>), edgeName);
443 
444 for (const auto& entry: value) {
445 trackField("key", entry.key);
446 trackField("value", entry.value);
447 }
448 nodeStack_.pop();
449}
450 
451template <MemoryRetainer T, typename Iterator>
452void MemoryTracker::trackField(kj::StringPtr edgeName,
453 const T& value,
454 kj::Maybe<kj::StringPtr> nodeName,
455 kj::Maybe<kj::StringPtr> elementName,
456 bool subtractFromSelf) {
457 if (value.begin() == value.end()) return;
458 if (subtractFromSelf) {
459 decCurrentNodeSize(sizeof(T));
460 }
461 pushNode(nodeName.orDefault(edgeName), sizeof(T), edgeName);
462 for (Iterator it = value.begin(); it != value.end(); ++it) {
463 trackField(nullptr, *it, elementName);
464 }
465 nodeStack_.pop();
466}
467 
468template <MemoryRetainer T>
469void MemoryTracker::trackField(kj::StringPtr edgeName,
470 const kj::ArrayPtr<T>& value,
471 kj::Maybe<kj::StringPtr> nodeName,
472 kj::Maybe<kj::StringPtr> elementName,
473 bool subtractFromSelf) {
474 if (value.begin() == value.end()) return;
475 if (subtractFromSelf) {
476 decCurrentNodeSize(sizeof(T));
477 }
478 pushNode(nodeName.orDefault(edgeName), sizeof(T), edgeName);
479 for (const auto& item: value) {
480 trackField(nullptr, item, elementName);
481 }
482 nodeStack_.pop();
483}
484 
485template <MemoryRetainer T>
486void MemoryTracker::trackField(kj::StringPtr edgeName,
487 const kj::ArrayPtr<T* const>& value,
488 kj::Maybe<kj::StringPtr> nodeName,
489 kj::Maybe<kj::StringPtr> elementName,
490 bool subtractFromSelf) {
491 if (value.begin() == value.end()) return;
492 if (subtractFromSelf) {
493 decCurrentNodeSize(sizeof(T));
494 }
495 pushNode(nodeName.orDefault(edgeName), sizeof(T), edgeName);
496 for (const auto& item: value) {
497 trackField(nullptr, item, elementName);
498 }
499 nodeStack_.pop();
500}
501 
502template <MemoryRetainer T>
503void MemoryTracker::trackField(
504 kj::StringPtr edgeName, const T& value, kj::Maybe<kj::StringPtr> nodeName) {
505 trackField(edgeName, &value, nodeName);
506}
507 
508template <MemoryRetainer T>
509void MemoryTracker::trackField(
510 kj::StringPtr edgeName, const T* value, kj::Maybe<kj::StringPtr> nodeName) {
511 if (value == nullptr) return;
512 KJ_IF_SOME(found, seen_.find(value)) {
513 addEdge(found, edgeName);
514 return;
515 }
516 track(value, edgeName);
517}
518 
519template <V8Value T>
520void MemoryTracker::trackField(
521 kj::StringPtr edgeName, const v8::Eternal<T>& value, kj::StringPtr nodeName) {
522 trackField(edgeName, value.Get(isolate_));
523}
524 
525template <V8Value T>
526void MemoryTracker::trackField(
527 kj::StringPtr edgeName, const v8::PersistentBase<T>& value, kj::Maybe<kj::StringPtr> nodeName) {
528 if (!value.IsEmpty() && !value.IsWeak()) {
529 trackField(edgeName, value.Get(isolate_));
530 }
531}
532 
533template <V8Value T>
534void MemoryTracker::trackField(
535 kj::StringPtr edgeName, const v8::Local<T>& value, kj::Maybe<kj::StringPtr> nodeName) {
536 if (!value.IsEmpty()) {
537 addEdge(graph_->V8Node(value.template As<v8::Value>()), edgeName);
538 }
539}
540 
541template <MemoryRetainer T>
542void MemoryTracker::track(const T* retainer, kj::Maybe<kj::StringPtr> edgeName) {
543 v8::HandleScope handle_scope(isolate_);
544 KJ_IF_SOME(found, seen_.find(retainer)) {
545 addEdge(found, edgeName.orDefault(nullptr));
546 return;
547 }
548 
549 pushNode(retainer, edgeName);
550 retainer->jsgGetMemoryInfo(*this);
551 nodeStack_.pop();
552}
553 
554template <MemoryRetainer T>
555void MemoryTracker::trackInlineField(const T* retainer, kj::Maybe<kj::StringPtr> edgeName) {
556 track(retainer, edgeName);
557}
558 
559template <MemoryRetainer T>
560MemoryRetainerNode* MemoryTracker::pushNode(const T* retainer, kj::Maybe<kj::StringPtr> edgeName) {
561 const kj::StringPtr name = retainer->jsgGetMemoryName();
562 const size_t size = retainer->jsgGetMemorySelfSize();
563 v8::Local<v8::Object> obj;
564 kj::Maybe<kj::Function<bool()>> checkIsRootNode = kj::none;
565 MemoryInfoDetachedState detachedness = MemoryInfoDetachedState::UNKNOWN;
566 v8::HandleScope handleScope(isolate());
567 if constexpr (MemoryRetainerObject<T>) {
568 obj = const_cast<T*>(retainer)->jsgGetMemoryInfoWrapperObject(isolate());
569 }
570 if constexpr (MemoryRetainerIsRootNode<T>) {
571 checkIsRootNode = [retainer]() { return retainer->jsgGetMemoryInfoIsRootNode(); };
572 }
573 if constexpr (MemoryRetainerDetachedState<T>) {
574 detachedness = retainer->jsgGetMemoryInfoDetachedState();
575 }
576 
577 MemoryRetainerNode* n =
578 addNode(retainer, name, size, obj, kj::mv(checkIsRootNode), detachedness, edgeName);
579 nodeStack_.push(n);
580 return n;
581}
582 
583template <typename T>
584inline void visitSubclassForMemoryInfo(const T* obj, MemoryTracker& tracker) {
585 if constexpr (&T::visitForMemoryInfo != &T::jsgSuper::visitForMemoryInfo) {
586 obj->visitForMemoryInfo(tracker);
587 }
588}
589 
590// ======================================================================================
591 
592class HeapSnapshotActivity final: public v8::ActivityControl {
593 public:
594 using Callback = kj::Function<bool(uint32_t done, uint32_t total)>;
595 
596 HeapSnapshotActivity(Callback callback);
597 ~HeapSnapshotActivity() noexcept(true) = default;
598 
599 ControlOption ReportProgressValue(uint32_t done, uint32_t total) override;
600 
601 private:
602 Callback callback;
603};
604 
605class HeapSnapshotWriter final: public v8::OutputStream {
606 public:
607 using Callback = kj::Function<bool(kj::Maybe<kj::ArrayPtr<char>>)>;
608 
609 HeapSnapshotWriter(Callback callback, size_t chunkSize = 65536);
610 ~HeapSnapshotWriter() noexcept(true) = default;
611 
612 void EndOfStream() override;
613 
614 int GetChunkSize() override;
615 
616 v8::OutputStream::WriteResult WriteAsciiChunk(char* data, int size) override;
617 
618 private:
619 Callback callback;
620 size_t chunkSize;
621};
622 
623struct HeapSnapshotDeleter: public kj::Disposer {
624 inline void disposeImpl(void* ptr) const override {
625 auto snapshot = const_cast<v8::HeapSnapshot*>(static_cast<const v8::HeapSnapshot*>(ptr));
626 snapshot->Delete();
627 }
628};
629 
630} // namespace workerd::jsg