Skip to content

Commit d2f7ee7

Browse files
authored
Merge pull request #95 from mmtk/dcn-ffi-extensions
Moving GC Extensions to Julia's FFI
2 parents 7180fb8 + 2107c6b commit d2f7ee7

File tree

7 files changed

+413
-2
lines changed

7 files changed

+413
-2
lines changed

base/Base_compiler.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ const _DOCS_ALIASING_WARNING = """
204204
"""
205205

206206
include("ctypes.jl")
207+
include("gc-explicit-pinning.jl")
207208
include("gcutils.jl")
208209
include("generator.jl")
209210
include("runtime_internals.jl")

base/gc-explicit-pinning.jl

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# This file is a part of Julia. License is MIT: https://julialang.org/license
2+
3+
export increment_pin_count!, decrement_pin_count!,
4+
increment_tpin_count!, decrement_tpin_count!,
5+
get_pin_count, get_tpin_count
6+
7+
"""
8+
increment_pin_count!(obj)
9+
10+
Increment the pin count of `obj` to preserve it beyond a lexical scope.
11+
12+
This ensures that `obj` is not moved or collected by the garbage collector.
13+
It is crucial for safely passing references to foreign code.
14+
15+
Each call increments the pin count by one. The object remains pinned and
16+
alive until the count is decremented to zero via `decrement_pin_count!`.
17+
18+
# Examples
19+
20+
```julia
21+
x = SomeObject()
22+
increment_pin_count!(x) # x is now pinned (count = 1)
23+
24+
increment_pin_count!(x) # pin count is now 2
25+
"""
26+
function increment_pin_count!(obj)
27+
ccall(:jl_increment_pin_count, Cvoid, (Any,), obj)
28+
end
29+
30+
"""
31+
decrement_pin_count!(obj)
32+
33+
Decrement the pin count of `obj`.
34+
35+
When the count drops to zero, the object is no longer pinned and may be
36+
moved (or collected by the garbage collector, if no other references
37+
exist).
38+
39+
This is necessary to release objects that were previously preserved for
40+
foreign code.
41+
42+
# Examples
43+
44+
```julia
45+
x = SomeObject()
46+
increment_pin_count!(x) # x is now pinned (count = 1)
47+
48+
increment_pin_count!(x) # pin count is now 2
49+
50+
decrement_pin_count!(x) # reduces pin count to 1
51+
52+
decrement_pin_count!(x) # count is 0; x may now be collected
53+
"""
54+
function decrement_pin_count!(obj)
55+
ccall(:jl_decrement_pin_count, Cvoid, (Any,), obj)
56+
end
57+
58+
"""
59+
increment_tpin_count!(obj)
60+
61+
Increment the transitive pin count of `obj` to preserve it beyond a
62+
lexical scope.
63+
64+
This ensures that `obj` and any other objects reachable from it are not
65+
moved or collected by the garbage collector. This is crucial for safely
66+
passing references to foreign code.
67+
68+
Each call increments the transitive pin count by one. The object remains
69+
transitively pinned and alive until the count is decremented to zero via
70+
`decrement_tpin_count!`.
71+
72+
# Examples
73+
74+
```julia
75+
x = SomeObject()
76+
increment_tpin_count!(x) # x is now transitively pinned (count = 1)
77+
78+
increment_tpin_count!(x) # transitive pin count is now 2
79+
"""
80+
function increment_tpin_count!(obj)
81+
ccall(:jl_increment_tpin_count, Cvoid, (Any,), obj)
82+
end
83+
84+
"""
85+
decrement_tpin_count!(obj)
86+
87+
Decrement the transitive pin count of `obj`.
88+
89+
When the count drops to zero, `obj` and any objects reachable from it are
90+
no longer pinned and may be moved (if no other pins exist) or collected by
91+
the garbage collector (if no other references exist).
92+
93+
This is necessary to release object graphs that were previously preserved
94+
for foreign code.
95+
96+
# Examples
97+
98+
```julia
99+
x = SomeObject()
100+
increment_tpin_count!(x) # pins x and reachable objects (count = 1)
101+
102+
decrement_tpin_count!(x) # reduces transitive pin count to 0
103+
# objects may now be collected
104+
"""
105+
function decrement_tpin_count!(obj)
106+
ccall(:jl_decrement_tpin_count, Cvoid, (Any,), obj)
107+
end
108+
109+
"""
110+
get_pin_count(obj)
111+
112+
Return the current pin count of `obj`.
113+
114+
This indicates how many times `obj` has been explicitly pinned via
115+
`increment_pin_count!`. A nonzero count means the object is currently
116+
pinned and will not be moved or collected by the garbage collector (GC).
117+
118+
# Examples
119+
120+
```julia
121+
x = SomeObject()
122+
increment_pin_count!(x)
123+
get_pin_count(x) # returns 1
124+
125+
increment_pin_count!(x)
126+
get_pin_count(x) # returns 2
127+
128+
decrement_pin_count!(x)
129+
get_pin_count(x) # returns 1
130+
"""
131+
function get_pin_count(obj)
132+
c = ccall(:jl_get_pin_count, Csize_t, (Any,), obj)
133+
return Int64(c)
134+
end
135+
136+
"""
137+
get_tpin_count(obj)
138+
139+
Return the current transitive pin count of `obj`.
140+
141+
This indicates how many times `obj` has been explicitly transitively pinned
142+
via `increment_tpin_count!`. A nonzero count means `obj` and all objects
143+
reachable from it are currently pinned and will not be moved or collected
144+
by the garbage collector (GC).
145+
146+
# Examples
147+
148+
```julia
149+
x = SomeObject()
150+
increment_tpin_count!(x)
151+
get_tpin_count(x) # returns 1
152+
153+
increment_tpin_count!(x)
154+
get_tpin_count(x) # returns 2
155+
156+
decrement_tpin_count!(x)
157+
get_tpin_count(x) # returns 1
158+
"""
159+
function get_tpin_count(obj)
160+
c = ccall(:jl_get_tpin_count, Csize_t, (Any,), obj)
161+
return Int64(c)
162+
end

src/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ FLAGS += -I$(LOCALBASE)/include
4848
endif
4949

5050
# GC source code. It depends on which GC implementation to use.
51-
GC_SRCS := gc-common gc-stacks gc-alloc-profiler gc-heap-snapshot gc-pinning-log
51+
GC_SRCS := gc-common gc-stacks gc-alloc-profiler gc-heap-snapshot gc-pinning-log gc-explicit-pinning
5252
ifeq (${USE_THIRD_PARTY_GC},mmtk)
5353
GC_SRCS += gc-mmtk
5454
else

src/gc-explicit-pinning.cpp

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// This file is a part of Julia. License is MIT: https://julialang.org/license
2+
3+
#include <map>
4+
#include <mutex>
5+
6+
#include "julia.h"
7+
8+
// Let's just use global maps in this first implementation
9+
// They could cause contention in multi-threaded code, so we might need to optimize them later
10+
11+
// Pinning
12+
std::map<void *, size_t> pin_count_map;
13+
std::mutex pin_count_map_lock;
14+
// Transitive Pinning
15+
std::map<void *, size_t> tpin_count_map;
16+
std::mutex tpin_count_map_lock;
17+
18+
#ifdef __cplusplus
19+
extern "C" {
20+
#endif
21+
22+
// Pinning
23+
JL_DLLEXPORT void jl_increment_pin_count(void *obj) {
24+
pin_count_map_lock.lock();
25+
if (pin_count_map.find(obj) == pin_count_map.end()) {
26+
pin_count_map[obj] = 0;
27+
}
28+
pin_count_map[obj]++;
29+
pin_count_map_lock.unlock();
30+
}
31+
JL_DLLEXPORT void jl_decrement_pin_count(void *obj) {
32+
pin_count_map_lock.lock();
33+
auto it = pin_count_map.find(obj);
34+
if (it != pin_count_map.end()) {
35+
if (it->second == 1) {
36+
pin_count_map.erase(it);
37+
} else {
38+
it->second--;
39+
}
40+
}
41+
pin_count_map_lock.unlock();
42+
}
43+
44+
// Transitive Pinning
45+
JL_DLLEXPORT void jl_increment_tpin_count(void *obj) {
46+
tpin_count_map_lock.lock();
47+
if (tpin_count_map.find(obj) == tpin_count_map.end()) {
48+
tpin_count_map[obj] = 0;
49+
}
50+
tpin_count_map[obj]++;
51+
tpin_count_map_lock.unlock();
52+
}
53+
JL_DLLEXPORT void jl_decrement_tpin_count(void *obj) {
54+
tpin_count_map_lock.lock();
55+
auto it = tpin_count_map.find(obj);
56+
if (it != tpin_count_map.end()) {
57+
if (it->second == 1) {
58+
tpin_count_map.erase(it);
59+
} else {
60+
it->second--;
61+
}
62+
}
63+
tpin_count_map_lock.unlock();
64+
}
65+
66+
// Retrieve Pinning and Transitive Pinning counts for a given object
67+
JL_DLLEXPORT size_t jl_get_pin_count(void *obj) {
68+
pin_count_map_lock.lock();
69+
auto it = pin_count_map.find(obj);
70+
size_t count = (it != pin_count_map.end()) ? it->second : 0;
71+
pin_count_map_lock.unlock();
72+
return count;
73+
}
74+
JL_DLLEXPORT size_t jl_get_tpin_count(void *obj) {
75+
tpin_count_map_lock.lock();
76+
auto it = tpin_count_map.find(obj);
77+
size_t count = (it != tpin_count_map.end()) ? it->second : 0;
78+
tpin_count_map_lock.unlock();
79+
return count;
80+
}
81+
82+
// Returns all pinned and transitively pinned objects
83+
// Argument should have been initialized by the caller
84+
// TODO: add a few assertions to check this?
85+
JL_DLLEXPORT void jl_dump_all_pinned_objects(arraylist_t *objects) {
86+
pin_count_map_lock.lock();
87+
for (const auto &pair : pin_count_map) {
88+
arraylist_push(objects, pair.first);
89+
}
90+
pin_count_map_lock.unlock();
91+
}
92+
JL_DLLEXPORT void jl_dump_all_tpinned_objects(arraylist_t *objects) {
93+
tpin_count_map_lock.lock();
94+
for (const auto &pair : tpin_count_map) {
95+
arraylist_push(objects, pair.first);
96+
}
97+
tpin_count_map_lock.unlock();
98+
}
99+
100+
#ifdef __cplusplus
101+
}
102+
#endif

src/gc-mmtk.c

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -776,9 +776,12 @@ void trace_partial_globally_rooted(RootsWorkClosure* closure, RootsWorkBuffer* b
776776
JL_DLLEXPORT void jl_gc_scan_vm_specific_roots(RootsWorkClosure* closure)
777777
{
778778

779-
// Create a new buf
779+
// Create a new buf for roots and nodes that have been pinned through the FFI functions
780780
RootsWorkBuffer buf = (closure->report_nodes_func)((void**)0, 0, 0, closure->data, true);
781781
size_t len = 0;
782+
// Create a new buf for nodes that have been transitively pinned through the FFI functions
783+
RootsWorkBuffer tpin_buf = (closure->report_tpinned_nodes_func)((void**)0, 0, 0, closure->data, true);
784+
size_t tpin_len = 0;
782785

783786
// globally rooted
784787
trace_full_globally_rooted(closure, &buf, &len);
@@ -825,6 +828,24 @@ JL_DLLEXPORT void jl_gc_scan_vm_specific_roots(RootsWorkClosure* closure)
825828
}
826829
}
827830

831+
// Trace pinned and transitively pinned objects
832+
arraylist_t all_pinned_objects;
833+
arraylist_t all_tpinned_objects;
834+
arraylist_new(&all_pinned_objects, 0);
835+
arraylist_new(&all_tpinned_objects, 0);
836+
jl_dump_all_pinned_objects(&all_pinned_objects);
837+
jl_dump_all_tpinned_objects(&all_tpinned_objects);
838+
for (size_t i = 0; i < all_pinned_objects.len; i++) {
839+
void *obj = all_pinned_objects.items[i];
840+
add_node_to_roots_buffer(closure, &buf, &len, obj);
841+
}
842+
for (size_t i = 0; i < all_tpinned_objects.len; i++) {
843+
void *obj = all_tpinned_objects.items[i];
844+
add_node_to_roots_buffer(closure, &tpin_buf, &tpin_len, obj);
845+
}
846+
arraylist_free(&all_pinned_objects);
847+
arraylist_free(&all_tpinned_objects);
848+
828849
// // add module
829850
// add_node_to_roots_buffer(closure, &buf, &len, jl_main_module);
830851

@@ -850,6 +871,8 @@ JL_DLLEXPORT void jl_gc_scan_vm_specific_roots(RootsWorkClosure* closure)
850871

851872
// Push the result of the work.
852873
(closure->report_nodes_func)(buf.ptr, len, buf.cap, closure->data, false);
874+
// Push the result of the transitively pinned work.
875+
(closure->report_tpinned_nodes_func)(tpin_buf.ptr, tpin_len, tpin_buf.cap, closure->data, false);
853876
}
854877

855878
JL_DLLEXPORT void jl_gc_scan_julia_exc_obj(void* obj_raw, void* closure, ProcessSlotFn process_slot) {

src/julia.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1395,6 +1395,8 @@ typedef bool (*check_alive_fn_type)(void *);
13951395
JL_DLLEXPORT void jl_set_check_alive_type(check_alive_fn_type fn);
13961396
JL_DLLEXPORT void jl_log_pinning_event(void *pinned_object, const char *filename, int lineno);
13971397
JL_DLLEXPORT void jl_print_pinning_log(void);
1398+
JL_DLLEXPORT void jl_dump_all_pinned_objects(arraylist_t *objects);
1399+
JL_DLLEXPORT void jl_dump_all_tpinned_objects(arraylist_t *objects);
13981400

13991401
#define ENABLE_PINNING_LOGGING
14001402
#ifdef ENABLE_PINNING_LOGGING

0 commit comments

Comments
 (0)