Skip to content

Commit cfbff10

Browse files
JianFangAtRaiKristofferC
authored andcommitted
heap snapshot: add gc roots and gc finalist roots to fix unrooted nodes (#52618)
(cherry picked from commit fe0db7d)
1 parent 8a04df0 commit cfbff10

File tree

4 files changed

+156
-35
lines changed

4 files changed

+156
-35
lines changed

src/gc-heap-snapshot.cpp

Lines changed: 101 additions & 23 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;
@@ -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.
@@ -392,27 +458,19 @@ void _gc_heap_snapshot_record_object_edge(jl_value_t *from, jl_value_t *to, void
392458
g_snapshot->names.find_or_create_string_id(path));
393459
}
394460

395-
void _gc_heap_snapshot_record_module_to_binding(jl_module_t *module, jl_binding_t *binding) JL_NOTSAFEPOINT
461+
void _gc_heap_snapshot_record_module_to_binding(jl_module_t *module, jl_value_t *bindings, jl_value_t *bindingkeyset) JL_NOTSAFEPOINT
396462
{
397-
jl_globalref_t *globalref = binding->globalref;
398-
jl_sym_t *name = globalref->name;
399463
auto from_node_idx = record_node_to_gc_snapshot((jl_value_t*)module);
400-
auto to_node_idx = record_pointer_to_gc_snapshot(binding, sizeof(jl_binding_t), jl_symbol_name(name));
401-
402-
jl_value_t *value = jl_atomic_load_relaxed(&binding->value);
403-
auto value_idx = value ? record_node_to_gc_snapshot(value) : 0;
404-
jl_value_t *ty = jl_atomic_load_relaxed(&binding->ty);
405-
auto ty_idx = ty ? record_node_to_gc_snapshot(ty) : 0;
406-
auto globalref_idx = record_node_to_gc_snapshot((jl_value_t*)globalref);
407-
464+
auto to_bindings_idx = record_node_to_gc_snapshot(bindings);
465+
auto to_bindingkeyset_idx = record_node_to_gc_snapshot(bindingkeyset);
408466
auto &from_node = g_snapshot->nodes[from_node_idx];
409-
auto &to_node = g_snapshot->nodes[to_node_idx];
410-
411-
_record_gc_just_edge("property", from_node, to_node_idx, g_snapshot->names.find_or_create_string_id("<native>"));
412-
if (value_idx) _record_gc_just_edge("internal", to_node, value_idx, g_snapshot->names.find_or_create_string_id("value"));
413-
if (ty_idx) _record_gc_just_edge("internal", to_node, ty_idx, g_snapshot->names.find_or_create_string_id("ty"));
414-
if (globalref_idx) _record_gc_just_edge("internal", to_node, globalref_idx, g_snapshot->names.find_or_create_string_id("globalref"));
415-
}
467+
if (to_bindings_idx > 0) {
468+
_record_gc_just_edge("internal", from_node, to_bindings_idx, g_snapshot->names.find_or_create_string_id("bindings"));
469+
}
470+
if (to_bindingkeyset_idx > 0) {
471+
_record_gc_just_edge("internal", from_node, to_bindingkeyset_idx, g_snapshot->names.find_or_create_string_id("bindingkeyset"));
472+
}
473+
}
416474

417475
void _gc_heap_snapshot_record_internal_array_edge(jl_value_t *from, jl_value_t *to) JL_NOTSAFEPOINT
418476
{
@@ -491,6 +549,8 @@ void serialize_heap_snapshot(ios_t *stream, HeapSnapshot &snapshot, char all_one
491549

492550
ios_printf(stream, "\"nodes\":[");
493551
bool first_node = true;
552+
// use a set to track the nodes that do not have parents
553+
set<size_t> orphans;
494554
for (const auto &from_node : snapshot.nodes) {
495555
if (first_node) {
496556
first_node = false;
@@ -507,6 +567,14 @@ void serialize_heap_snapshot(ios_t *stream, HeapSnapshot &snapshot, char all_one
507567
from_node.edges.size(),
508568
from_node.trace_node_id,
509569
from_node.detachedness);
570+
if (from_node.id != snapshot._gc_root_idx && from_node.id != snapshot._gc_finlist_root_idx) {
571+
// find the node index from the node object pointer
572+
void * ptr = (void*)from_node.id;
573+
size_t n_id = snapshot.node_ptr_to_index_map[ptr];
574+
orphans.insert(n_id);
575+
} else {
576+
orphans.insert(from_node.id);
577+
}
510578
}
511579
ios_printf(stream, "],\n");
512580

@@ -524,6 +592,12 @@ void serialize_heap_snapshot(ios_t *stream, HeapSnapshot &snapshot, char all_one
524592
edge.type,
525593
edge.name_or_index,
526594
edge.to_node * k_node_number_of_fields);
595+
auto n_id = edge.to_node;
596+
auto it = orphans.find(n_id);
597+
if (it != orphans.end()) {
598+
// remove the node from the orphans if it has at least one incoming edge
599+
orphans.erase(it);
600+
}
527601
}
528602
}
529603
ios_printf(stream, "],\n"); // end "edges"
@@ -533,4 +607,8 @@ void serialize_heap_snapshot(ios_t *stream, HeapSnapshot &snapshot, char all_one
533607
snapshot.names.print_json_array(stream, true);
534608

535609
ios_printf(stream, "}");
610+
611+
// remove the uber node from the orphans
612+
orphans.erase(0);
613+
assert(orphans.size() == 0 && "all nodes except the uber node should have at least one incoming edge");
536614
}

src/gc-heap-snapshot.h

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,18 @@ void _gc_heap_snapshot_record_task_to_frame_edge(jl_task_t *from, void *to) JL_N
2020
void _gc_heap_snapshot_record_frame_to_frame_edge(jl_gcframe_t *from, jl_gcframe_t *to) JL_NOTSAFEPOINT;
2121
void _gc_heap_snapshot_record_array_edge(jl_value_t *from, jl_value_t *to, size_t index) JL_NOTSAFEPOINT;
2222
void _gc_heap_snapshot_record_object_edge(jl_value_t *from, jl_value_t *to, void* slot) JL_NOTSAFEPOINT;
23-
void _gc_heap_snapshot_record_module_to_binding(jl_module_t* module, jl_binding_t* binding) JL_NOTSAFEPOINT;
23+
void _gc_heap_snapshot_record_module_to_binding(jl_module_t* module, jl_value_t *bindings, jl_value_t *bindingkeyset) JL_NOTSAFEPOINT;
2424
// Used for objects managed by GC, but which aren't exposed in the julia object, so have no
2525
// field or index. i.e. they're not reachable from julia code, but we _will_ hit them in
2626
// the GC mark phase (so we can check their type tag to get the size).
2727
void _gc_heap_snapshot_record_internal_array_edge(jl_value_t *from, jl_value_t *to) JL_NOTSAFEPOINT;
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)) {
@@ -73,10 +82,10 @@ static inline void gc_heap_snapshot_record_object_edge(jl_value_t *from, jl_valu
7382
}
7483
}
7584

76-
static inline void gc_heap_snapshot_record_module_to_binding(jl_module_t* module, jl_binding_t* binding) JL_NOTSAFEPOINT
85+
static inline void gc_heap_snapshot_record_module_to_binding(jl_module_t* module, jl_value_t *bindings, jl_value_t *bindingkeyset) JL_NOTSAFEPOINT
7786
{
78-
if (__unlikely(gc_heap_snapshot_enabled && prev_sweep_full)) {
79-
_gc_heap_snapshot_record_module_to_binding(module, binding);
87+
if (__unlikely(gc_heap_snapshot_enabled && prev_sweep_full) && bindings != NULL && bindingkeyset != NULL) {
88+
_gc_heap_snapshot_record_module_to_binding(module, bindings, bindingkeyset);
8089
}
8190
}
8291

@@ -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: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2190,9 +2190,10 @@ STATIC_INLINE void gc_mark_chunk(jl_ptls_t ptls, jl_gc_markqueue_t *mq, jl_gc_ch
21902190
break;
21912191
}
21922192
case GC_finlist_chunk: {
2193+
jl_value_t *fl_parent = c->parent;
21932194
jl_value_t **fl_begin = c->begin;
21942195
jl_value_t **fl_end = c->end;
2195-
gc_mark_finlist_(mq, fl_begin, fl_end);
2196+
gc_mark_finlist_(mq, fl_parent, fl_begin, fl_end);
21962197
break;
21972198
}
21982199
default: {
@@ -2290,6 +2291,7 @@ STATIC_INLINE void gc_mark_module_binding(jl_ptls_t ptls, jl_module_t *mb_parent
22902291
jl_value_t *bindingkeyset = (jl_value_t *)jl_atomic_load_relaxed(&mb_parent->bindingkeyset);
22912292
gc_assert_parent_validity((jl_value_t *)mb_parent, bindingkeyset);
22922293
gc_try_claim_and_push(mq, bindingkeyset, &nptr);
2294+
gc_heap_snapshot_record_module_to_binding(mb_parent, bindings, bindingkeyset);
22932295
gc_assert_parent_validity((jl_value_t *)mb_parent, (jl_value_t *)mb_parent->parent);
22942296
gc_try_claim_and_push(mq, (jl_value_t *)mb_parent->parent, &nptr);
22952297
size_t nusings = mb_parent->usings.len;
@@ -2308,7 +2310,7 @@ STATIC_INLINE void gc_mark_module_binding(jl_ptls_t ptls, jl_module_t *mb_parent
23082310
}
23092311
}
23102312

2311-
void gc_mark_finlist_(jl_gc_markqueue_t *mq, jl_value_t **fl_begin, jl_value_t **fl_end)
2313+
void gc_mark_finlist_(jl_gc_markqueue_t *mq, jl_value_t *fl_parent, jl_value_t **fl_begin, jl_value_t **fl_end)
23122314
{
23132315
jl_value_t *new_obj;
23142316
// Decide whether need to chunk finlist
@@ -2318,8 +2320,10 @@ void gc_mark_finlist_(jl_gc_markqueue_t *mq, jl_value_t **fl_begin, jl_value_t *
23182320
gc_chunkqueue_push(mq, &c);
23192321
fl_end = fl_begin + GC_CHUNK_BATCH_SIZE;
23202322
}
2323+
size_t i = 0;
23212324
for (; fl_begin < fl_end; fl_begin++) {
2322-
new_obj = *fl_begin;
2325+
jl_value_t **slot = fl_begin;
2326+
new_obj = *slot;
23232327
if (__unlikely(new_obj == NULL))
23242328
continue;
23252329
if (gc_ptr_tag(new_obj, 1)) {
@@ -2330,6 +2334,13 @@ void gc_mark_finlist_(jl_gc_markqueue_t *mq, jl_value_t **fl_begin, jl_value_t *
23302334
if (gc_ptr_tag(new_obj, 2))
23312335
continue;
23322336
gc_try_claim_and_push(mq, new_obj, NULL);
2337+
if (fl_parent != NULL) {
2338+
gc_heap_snapshot_record_array_edge(fl_parent, slot);
2339+
} else {
2340+
// This is a list of objects following the same format as a finlist
2341+
// if `fl_parent` is NULL
2342+
gc_heap_snapshot_record_finlist(new_obj, ++i);
2343+
}
23332344
}
23342345
}
23352346

@@ -2341,7 +2352,7 @@ void gc_mark_finlist(jl_gc_markqueue_t *mq, arraylist_t *list, size_t start)
23412352
return;
23422353
jl_value_t **fl_begin = (jl_value_t **)list->items + start;
23432354
jl_value_t **fl_end = (jl_value_t **)list->items + len;
2344-
gc_mark_finlist_(mq, fl_begin, fl_end);
2355+
gc_mark_finlist_(mq, NULL, fl_begin, fl_end);
23452356
}
23462357

23472358
JL_DLLEXPORT int jl_gc_mark_queue_obj(jl_ptls_t ptls, jl_value_t *obj)
@@ -3003,27 +3014,36 @@ static void gc_mark_roots(jl_gc_markqueue_t *mq)
30033014
{
30043015
// modules
30053016
gc_try_claim_and_push(mq, jl_main_module, NULL);
3006-
gc_heap_snapshot_record_root((jl_value_t*)jl_main_module, "main_module");
3017+
gc_heap_snapshot_record_gc_roots((jl_value_t*)jl_main_module, "main_module");
30073018
// invisible builtin values
30083019
gc_try_claim_and_push(mq, jl_an_empty_vec_any, NULL);
3020+
gc_heap_snapshot_record_gc_roots((jl_value_t*)jl_an_empty_vec_any, "an_empty_vec_any");
30093021
gc_try_claim_and_push(mq, jl_module_init_order, NULL);
3022+
gc_heap_snapshot_record_gc_roots((jl_value_t*)jl_module_init_order, "module_init_order");
30103023
for (size_t i = 0; i < jl_current_modules.size; i += 2) {
30113024
if (jl_current_modules.table[i + 1] != HT_NOTFOUND) {
30123025
gc_try_claim_and_push(mq, jl_current_modules.table[i], NULL);
3013-
gc_heap_snapshot_record_root((jl_value_t*)jl_current_modules.table[i], "top level module");
3026+
gc_heap_snapshot_record_gc_roots((jl_value_t*)jl_current_modules.table[i], "top level module");
30143027
}
30153028
}
30163029
gc_try_claim_and_push(mq, jl_anytuple_type_type, NULL);
3030+
gc_heap_snapshot_record_gc_roots((jl_value_t*)jl_anytuple_type_type, "anytuple_type_type");
30173031
for (size_t i = 0; i < N_CALL_CACHE; i++) {
30183032
jl_typemap_entry_t *v = jl_atomic_load_relaxed(&call_cache[i]);
30193033
gc_try_claim_and_push(mq, v, NULL);
3034+
gc_heap_snapshot_record_array_edge_index((jl_value_t*)jl_anytuple_type_type, (jl_value_t*)v, i);
30203035
}
30213036
gc_try_claim_and_push(mq, jl_all_methods, NULL);
3037+
gc_heap_snapshot_record_gc_roots((jl_value_t*)jl_all_methods, "all_methods");
30223038
gc_try_claim_and_push(mq, _jl_debug_method_invalidation, NULL);
3039+
gc_heap_snapshot_record_gc_roots((jl_value_t*)_jl_debug_method_invalidation, "debug_method_invalidation");
30233040
// constants
30243041
gc_try_claim_and_push(mq, jl_emptytuple_type, NULL);
3042+
gc_heap_snapshot_record_gc_roots((jl_value_t*)jl_emptytuple_type, "emptytuple_type");
30253043
gc_try_claim_and_push(mq, cmpswap_names, NULL);
3044+
gc_heap_snapshot_record_gc_roots((jl_value_t*)cmpswap_names, "cmpswap_names");
30263045
gc_try_claim_and_push(mq, jl_global_roots_table, NULL);
3046+
gc_heap_snapshot_record_gc_roots((jl_value_t*)jl_global_roots_table, "global_roots_table");
30273047
}
30283048

30293049
// 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
@@ -427,7 +427,7 @@ extern uv_cond_t gc_threads_cond;
427427
extern uv_sem_t gc_sweep_assists_needed;
428428
extern _Atomic(int) gc_n_threads_marking;
429429
void gc_mark_queue_all_roots(jl_ptls_t ptls, jl_gc_markqueue_t *mq);
430-
void gc_mark_finlist_(jl_gc_markqueue_t *mq, jl_value_t **fl_begin, jl_value_t **fl_end) JL_NOTSAFEPOINT;
430+
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;
431431
void gc_mark_finlist(jl_gc_markqueue_t *mq, arraylist_t *list, size_t start) JL_NOTSAFEPOINT;
432432
void gc_mark_loop_serial_(jl_ptls_t ptls, jl_gc_markqueue_t *mq);
433433
void gc_mark_loop_serial(jl_ptls_t ptls);

0 commit comments

Comments
 (0)