Skip to content

Commit 7b758dd

Browse files
add gc roots and gc finalist roots to fix unrooted nodes (#125)
* add gc roots and gc finalist roots to fix unrooted nodes * apply changes from upstream PR
1 parent bf10aeb commit 7b758dd

File tree

4 files changed

+142
-14
lines changed

4 files changed

+142
-14
lines changed

src/gc-heap-snapshot.cpp

Lines changed: 92 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "gc-heap-snapshot.h"
44

55
#include "julia_internal.h"
6+
#include "julia_assert.h"
67
#include "gc.h"
78

89
#include "llvm/ADT/StringMap.h"
@@ -11,9 +12,12 @@
1112
#include <vector>
1213
#include <string>
1314
#include <sstream>
15+
#include <iostream>
16+
#include <set>
1417

1518
using std::vector;
1619
using std::string;
20+
using std::set;
1721
using std::ostringstream;
1822
using std::pair;
1923
using std::make_pair;
@@ -70,7 +74,7 @@ struct Node {
7074
size_t id; // This should be a globally-unique counter, but we use the memory address
7175
size_t self_size;
7276
size_t trace_node_id; // This is ALWAYS 0 in Javascript heap-snapshots.
73-
// whether the from_node is attached or dettached from the main application state
77+
// whether the from_node is attached or detached from the main application state
7478
// https://github.com/nodejs/node/blob/5fd7a72e1c4fbaf37d3723c4c81dce35c149dc84/deps/v8/include/v8-profiler.h#L739-L745
7579
int detachedness; // 0 - unknown, 1 - attached, 2 - detached
7680
vector<Edge> edges;
@@ -115,6 +119,8 @@ struct HeapSnapshot {
115119
DenseMap<void *, size_t> node_ptr_to_index_map;
116120

117121
size_t num_edges = 0; // For metadata, updated as you add each edge. Needed because edges owned by nodes.
122+
size_t _gc_root_idx = 1; // node index of the GC roots node
123+
size_t _gc_finlist_root_idx = 2; // node index of the GC finlist roots node
118124
};
119125

120126
// global heap snapshot, mutated by garbage collector
@@ -127,13 +133,13 @@ void serialize_heap_snapshot(ios_t *stream, HeapSnapshot &snapshot, char all_one
127133
static inline void _record_gc_edge(const char *edge_type,
128134
jl_value_t *a, jl_value_t *b, size_t name_or_index) JL_NOTSAFEPOINT;
129135
void _record_gc_just_edge(const char *edge_type, Node &from_node, size_t to_idx, size_t name_or_idx) JL_NOTSAFEPOINT;
130-
void _add_internal_root(HeapSnapshot *snapshot);
136+
void _add_synthetic_root_entries(HeapSnapshot *snapshot);
131137

132138

133139
JL_DLLEXPORT void jl_gc_take_heap_snapshot(ios_t *stream, char all_one)
134140
{
135141
HeapSnapshot snapshot;
136-
_add_internal_root(&snapshot);
142+
_add_synthetic_root_entries(&snapshot);
137143

138144
jl_mutex_lock(&heapsnapshot_lock);
139145

@@ -155,10 +161,12 @@ JL_DLLEXPORT void jl_gc_take_heap_snapshot(ios_t *stream, char all_one)
155161
serialize_heap_snapshot((ios_t*)stream, snapshot, all_one);
156162
}
157163

158-
// adds a node at id 0 which is the "uber root":
159-
// a synthetic node which points to all the GC roots.
160-
void _add_internal_root(HeapSnapshot *snapshot)
164+
// mimicking https://github.com/nodejs/node/blob/5fd7a72e1c4fbaf37d3723c4c81dce35c149dc84/deps/v8/src/profiler/heap-snapshot-generator.cc#L212
165+
// add synthetic nodes for the uber root, the GC roots, and the GC finalizer list roots
166+
void _add_synthetic_root_entries(HeapSnapshot *snapshot)
161167
{
168+
// adds a node at id 0 which is the "uber root":
169+
// a synthetic node which points to all the GC roots.
162170
Node internal_root{
163171
snapshot->node_types.find_or_create_string_id("synthetic"),
164172
snapshot->names.find_or_create_string_id(""), // name
@@ -169,6 +177,44 @@ void _add_internal_root(HeapSnapshot *snapshot)
169177
vector<Edge>() // outgoing edges
170178
};
171179
snapshot->nodes.push_back(internal_root);
180+
181+
// Add a node for the GC roots
182+
snapshot->_gc_root_idx = snapshot->nodes.size();
183+
Node gc_roots{
184+
snapshot->node_types.find_or_create_string_id("synthetic"),
185+
snapshot->names.find_or_create_string_id("GC roots"), // name
186+
snapshot->_gc_root_idx, // id
187+
0, // size
188+
0, // size_t trace_node_id (unused)
189+
0, // int detachedness; // 0 - unknown, 1 - attached; 2 - detached
190+
vector<Edge>() // outgoing edges
191+
};
192+
snapshot->nodes.push_back(gc_roots);
193+
snapshot->nodes.front().edges.push_back(Edge{
194+
snapshot->edge_types.find_or_create_string_id("internal"),
195+
snapshot->names.find_or_create_string_id("GC roots"), // edge label
196+
snapshot->_gc_root_idx // to
197+
});
198+
snapshot->num_edges += 1;
199+
200+
// add a node for the gc finalizer list roots
201+
snapshot->_gc_finlist_root_idx = snapshot->nodes.size();
202+
Node gc_finlist_roots{
203+
snapshot->node_types.find_or_create_string_id("synthetic"),
204+
snapshot->names.find_or_create_string_id("GC finalizer list roots"), // name
205+
snapshot->_gc_finlist_root_idx, // id
206+
0, // size
207+
0, // size_t trace_node_id (unused)
208+
0, // int detachedness; // 0 - unknown, 1 - attached; 2 - detached
209+
vector<Edge>() // outgoing edges
210+
};
211+
snapshot->nodes.push_back(gc_finlist_roots);
212+
snapshot->nodes.front().edges.push_back(Edge{
213+
snapshot->edge_types.find_or_create_string_id("internal"),
214+
snapshot->names.find_or_create_string_id("GC finlist roots"), // edge label
215+
snapshot->_gc_finlist_root_idx // to
216+
});
217+
snapshot->num_edges += 1;
172218
}
173219

174220
// mimicking https://github.com/nodejs/node/blob/5fd7a72e1c4fbaf37d3723c4c81dce35c149dc84/deps/v8/src/profiler/heap-snapshot-generator.cc#L597-L597
@@ -326,6 +372,26 @@ void _gc_heap_snapshot_record_root(jl_value_t *root, char *name) JL_NOTSAFEPOINT
326372
_record_gc_just_edge("internal", internal_root, to_node_idx, edge_label);
327373
}
328374

375+
void _gc_heap_snapshot_record_gc_roots(jl_value_t *root, char *name) JL_NOTSAFEPOINT
376+
{
377+
record_node_to_gc_snapshot(root);
378+
379+
auto from_node_idx = g_snapshot->_gc_root_idx;
380+
auto to_node_idx = record_node_to_gc_snapshot(root);
381+
auto edge_label = g_snapshot->names.find_or_create_string_id(name);
382+
_record_gc_just_edge("internal", g_snapshot->nodes[from_node_idx], to_node_idx, edge_label);
383+
}
384+
385+
void _gc_heap_snapshot_record_finlist(jl_value_t *obj, size_t index) JL_NOTSAFEPOINT
386+
{
387+
auto from_node_idx = g_snapshot->_gc_finlist_root_idx;
388+
auto to_node_idx = record_node_to_gc_snapshot(obj);
389+
ostringstream ss;
390+
ss << "finlist-" << index;
391+
auto edge_label = g_snapshot->names.find_or_create_string_id(ss.str());
392+
_record_gc_just_edge("internal", g_snapshot->nodes[from_node_idx], to_node_idx, edge_label);
393+
}
394+
329395
// Add a node to the heap snapshot representing a Julia stack frame.
330396
// Each task points at a stack frame, which points at the stack frame of
331397
// the function it's currently calling, forming a linked list.
@@ -490,6 +556,8 @@ void serialize_heap_snapshot(ios_t *stream, HeapSnapshot &snapshot, char all_one
490556

491557
ios_printf(stream, "\"nodes\":[");
492558
bool first_node = true;
559+
// use a set to track the nodes that do not have parents
560+
set<size_t> orphans;
493561
for (const auto &from_node : snapshot.nodes) {
494562
if (first_node) {
495563
first_node = false;
@@ -506,6 +574,14 @@ void serialize_heap_snapshot(ios_t *stream, HeapSnapshot &snapshot, char all_one
506574
from_node.edges.size(),
507575
from_node.trace_node_id,
508576
from_node.detachedness);
577+
if (from_node.id != snapshot._gc_root_idx && from_node.id != snapshot._gc_finlist_root_idx) {
578+
// find the node index from the node object pointer
579+
void * ptr = (void*)from_node.id;
580+
size_t n_id = snapshot.node_ptr_to_index_map[ptr];
581+
orphans.insert(n_id);
582+
} else {
583+
orphans.insert(from_node.id);
584+
}
509585
}
510586
ios_printf(stream, "],\n");
511587

@@ -523,6 +599,12 @@ void serialize_heap_snapshot(ios_t *stream, HeapSnapshot &snapshot, char all_one
523599
edge.type,
524600
edge.name_or_index,
525601
edge.to_node * k_node_number_of_fields);
602+
auto n_id = edge.to_node;
603+
auto it = orphans.find(n_id);
604+
if (it != orphans.end()) {
605+
// remove the node from the orphans if it has at least one incoming edge
606+
orphans.erase(it);
607+
}
526608
}
527609
}
528610
ios_printf(stream, "],\n"); // end "edges"
@@ -532,4 +614,8 @@ void serialize_heap_snapshot(ios_t *stream, HeapSnapshot &snapshot, char all_one
532614
snapshot.names.print_json_array(stream, true);
533615

534616
ios_printf(stream, "}");
617+
618+
// remove the uber node from the orphans
619+
orphans.erase(0);
620+
assert(orphans.size() == 0 && "all nodes except the uber node should have at least one incoming edge");
535621
}

src/gc-heap-snapshot.h

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ void _gc_heap_snapshot_record_internal_array_edge(jl_value_t *from, jl_value_t *
2828
// Used for objects manually allocated in C (outside julia GC), to still tell the heap snapshot about the
2929
// size of the object, even though we're never going to mark that object.
3030
void _gc_heap_snapshot_record_hidden_edge(jl_value_t *from, void* to, size_t bytes, uint16_t alloc_type) JL_NOTSAFEPOINT;
31-
31+
// Used for objects that are reachable from the GC roots
32+
void _gc_heap_snapshot_record_gc_roots(jl_value_t *root, char *name) JL_NOTSAFEPOINT;
33+
// Used for objects that are reachable from the finalizer list
34+
void _gc_heap_snapshot_record_finlist(jl_value_t *finlist, size_t index) JL_NOTSAFEPOINT;
3235

3336
extern int gc_heap_snapshot_enabled;
3437
extern int prev_sweep_full;
@@ -60,6 +63,12 @@ static inline void gc_heap_snapshot_record_root(jl_value_t *root, char *name) JL
6063
_gc_heap_snapshot_record_root(root, name);
6164
}
6265
}
66+
static inline void gc_heap_snapshot_record_array_edge_index(jl_value_t *from, jl_value_t *to, size_t index) JL_NOTSAFEPOINT
67+
{
68+
if (__unlikely(gc_heap_snapshot_enabled && prev_sweep_full && from != NULL && to != NULL)) {
69+
_gc_heap_snapshot_record_array_edge(from, to, index);
70+
}
71+
}
6372
static inline void gc_heap_snapshot_record_array_edge(jl_value_t *from, jl_value_t **to) JL_NOTSAFEPOINT
6473
{
6574
if (__unlikely(gc_heap_snapshot_enabled && prev_sweep_full)) {
@@ -94,6 +103,20 @@ static inline void gc_heap_snapshot_record_hidden_edge(jl_value_t *from, void* t
94103
}
95104
}
96105

106+
static inline void gc_heap_snapshot_record_gc_roots(jl_value_t *root, char *name) JL_NOTSAFEPOINT
107+
{
108+
if (__unlikely(gc_heap_snapshot_enabled && prev_sweep_full && root != NULL)) {
109+
_gc_heap_snapshot_record_gc_roots(root, name);
110+
}
111+
}
112+
113+
static inline void gc_heap_snapshot_record_finlist(jl_value_t *finlist, size_t index) JL_NOTSAFEPOINT
114+
{
115+
if (__unlikely(gc_heap_snapshot_enabled && prev_sweep_full && finlist != NULL)) {
116+
_gc_heap_snapshot_record_finlist(finlist, index);
117+
}
118+
}
119+
97120
// ---------------------------------------------------------------------
98121
// Functions to call from Julia to take heap snapshot
99122
// ---------------------------------------------------------------------

src/gc.c

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2377,9 +2377,10 @@ STATIC_INLINE void gc_mark_chunk(jl_ptls_t ptls, jl_gc_markqueue_t *mq, jl_gc_ch
23772377
break;
23782378
}
23792379
case GC_finlist_chunk: {
2380+
jl_value_t *fl_parent = c->parent;
23802381
jl_value_t **fl_begin = c->begin;
23812382
jl_value_t **fl_end = c->end;
2382-
gc_mark_finlist_(mq, fl_begin, fl_end);
2383+
gc_mark_finlist_(mq, fl_parent, fl_begin, fl_end);
23832384
break;
23842385
}
23852386
default: {
@@ -2516,7 +2517,7 @@ STATIC_INLINE void gc_mark_module_binding(jl_ptls_t ptls, jl_module_t *mb_parent
25162517
}
25172518
}
25182519

2519-
void gc_mark_finlist_(jl_gc_markqueue_t *mq, jl_value_t **fl_begin, jl_value_t **fl_end)
2520+
void gc_mark_finlist_(jl_gc_markqueue_t *mq, jl_value_t *fl_parent, jl_value_t **fl_begin, jl_value_t **fl_end)
25202521
{
25212522
jl_value_t *new_obj;
25222523
// Decide whether need to chunk finlist
@@ -2526,8 +2527,10 @@ void gc_mark_finlist_(jl_gc_markqueue_t *mq, jl_value_t **fl_begin, jl_value_t *
25262527
gc_chunkqueue_push(mq, &c);
25272528
fl_end = fl_begin + GC_CHUNK_BATCH_SIZE;
25282529
}
2530+
size_t i = 0;
25292531
for (; fl_begin < fl_end; fl_begin++) {
2530-
new_obj = *fl_begin;
2532+
jl_value_t **slot = fl_begin;
2533+
new_obj = *slot;
25312534
if (__unlikely(!new_obj))
25322535
continue;
25332536
if (gc_ptr_tag(new_obj, 1)) {
@@ -2538,6 +2541,13 @@ void gc_mark_finlist_(jl_gc_markqueue_t *mq, jl_value_t **fl_begin, jl_value_t *
25382541
if (gc_ptr_tag(new_obj, 2))
25392542
continue;
25402543
gc_try_claim_and_push(mq, new_obj, NULL);
2544+
if (fl_parent != NULL) {
2545+
gc_heap_snapshot_record_array_edge(fl_parent, slot);
2546+
} else {
2547+
// This is a list of objects following the same format as a finlist
2548+
// if `fl_parent` is NULL
2549+
gc_heap_snapshot_record_finlist(new_obj, ++i);
2550+
}
25412551
}
25422552
}
25432553

@@ -2549,7 +2559,7 @@ void gc_mark_finlist(jl_gc_markqueue_t *mq, arraylist_t *list, size_t start)
25492559
return;
25502560
jl_value_t **fl_begin = (jl_value_t **)list->items + start;
25512561
jl_value_t **fl_end = (jl_value_t **)list->items + len;
2552-
gc_mark_finlist_(mq, fl_begin, fl_end);
2562+
gc_mark_finlist_(mq, NULL, fl_begin, fl_end);
25532563
}
25542564

25552565
JL_DLLEXPORT int jl_gc_mark_queue_obj(jl_ptls_t ptls, jl_value_t *obj)
@@ -3212,27 +3222,36 @@ static void gc_mark_roots(jl_gc_markqueue_t *mq)
32123222
{
32133223
// modules
32143224
gc_try_claim_and_push(mq, jl_main_module, NULL);
3215-
gc_heap_snapshot_record_root((jl_value_t*)jl_main_module, "main_module");
3225+
gc_heap_snapshot_record_gc_roots((jl_value_t*)jl_main_module, "main_module");
32163226
// invisible builtin values
32173227
gc_try_claim_and_push(mq, jl_an_empty_vec_any, NULL);
3228+
gc_heap_snapshot_record_gc_roots((jl_value_t*)jl_an_empty_vec_any, "an_empty_vec_any");
32183229
gc_try_claim_and_push(mq, jl_module_init_order, NULL);
3230+
gc_heap_snapshot_record_gc_roots((jl_value_t*)jl_module_init_order, "module_init_order");
32193231
for (size_t i = 0; i < jl_current_modules.size; i += 2) {
32203232
if (jl_current_modules.table[i + 1] != HT_NOTFOUND) {
32213233
gc_try_claim_and_push(mq, jl_current_modules.table[i], NULL);
3222-
gc_heap_snapshot_record_root((jl_value_t*)jl_current_modules.table[i], "top level module");
3234+
gc_heap_snapshot_record_gc_roots((jl_value_t*)jl_current_modules.table[i], "top level module");
32233235
}
32243236
}
32253237
gc_try_claim_and_push(mq, jl_anytuple_type_type, NULL);
3238+
gc_heap_snapshot_record_gc_roots((jl_value_t*)jl_anytuple_type_type, "anytuple_type_type");
32263239
for (size_t i = 0; i < N_CALL_CACHE; i++) {
32273240
jl_typemap_entry_t *v = jl_atomic_load_relaxed(&call_cache[i]);
32283241
gc_try_claim_and_push(mq, v, NULL);
3242+
gc_heap_snapshot_record_array_edge_index((jl_value_t*)jl_anytuple_type_type, (jl_value_t*)v, i);
32293243
}
32303244
gc_try_claim_and_push(mq, jl_all_methods, NULL);
3245+
gc_heap_snapshot_record_gc_roots((jl_value_t*)jl_all_methods, "all_methods");
32313246
gc_try_claim_and_push(mq, _jl_debug_method_invalidation, NULL);
3247+
gc_heap_snapshot_record_gc_roots((jl_value_t*)_jl_debug_method_invalidation, "debug_method_invalidation");
32323248
// constants
32333249
gc_try_claim_and_push(mq, jl_emptytuple_type, NULL);
3250+
gc_heap_snapshot_record_gc_roots((jl_value_t*)jl_emptytuple_type, "emptytuple_type");
32343251
gc_try_claim_and_push(mq, cmpswap_names, NULL);
3252+
gc_heap_snapshot_record_gc_roots((jl_value_t*)cmpswap_names, "cmpswap_names");
32353253
gc_try_claim_and_push(mq, jl_global_roots_table, NULL);
3254+
gc_heap_snapshot_record_gc_roots((jl_value_t*)jl_global_roots_table, "global_roots_table");
32363255
}
32373256

32383257
// find unmarked objects that need to be finalized from the finalizer list "list".

src/gc.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ extern uv_cond_t gc_threads_cond;
458458
extern _Atomic(int) gc_n_threads_marking;
459459
extern _Atomic(int) gc_n_threads_sweeping;
460460
void gc_mark_queue_all_roots(jl_ptls_t ptls, jl_gc_markqueue_t *mq);
461-
void gc_mark_finlist_(jl_gc_markqueue_t *mq, jl_value_t **fl_begin, jl_value_t **fl_end) JL_NOTSAFEPOINT;
461+
void gc_mark_finlist_(jl_gc_markqueue_t *mq, jl_value_t *fl_parent, jl_value_t **fl_begin, jl_value_t **fl_end) JL_NOTSAFEPOINT;
462462
void gc_mark_finlist(jl_gc_markqueue_t *mq, arraylist_t *list, size_t start) JL_NOTSAFEPOINT;
463463
void gc_mark_loop_serial_(jl_ptls_t ptls, jl_gc_markqueue_t *mq);
464464
void gc_mark_loop_serial(jl_ptls_t ptls);

0 commit comments

Comments
 (0)