-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
boehm.cr
318 lines (262 loc) · 8.54 KB
/
boehm.cr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
{% if flag?(:preview_mt) %}
require "crystal/rw_lock"
{% end %}
{% unless flag?(:win32) %}
@[Link("pthread")]
{% end %}
{% if flag?(:freebsd) || flag?(:dragonfly) %}
@[Link("gc-threaded")]
{% else %}
@[Link("gc")]
{% end %}
lib LibGC
alias Int = LibC::Int
alias SizeT = LibC::SizeT
alias Word = LibC::ULong
struct StackBase
mem_base : Void*
# reg_base : Void* should be used also for IA-64 when/if supported
end
alias ThreadHandle = Void*
struct ProfStats
heap_size : Word
free_bytes : Word
unmapped_bytes : Word
bytes_since_gc : Word
bytes_before_gc : Word
non_gc_bytes : Word
gc_no : Word
markers_m1 : Word
bytes_reclaimed_since_gc : Word
reclaimed_bytes_before_gc : Word
end
fun init = GC_init
fun malloc = GC_malloc(size : SizeT) : Void*
fun malloc_atomic = GC_malloc_atomic(size : SizeT) : Void*
fun realloc = GC_realloc(ptr : Void*, size : SizeT) : Void*
fun free = GC_free(ptr : Void*)
fun collect_a_little = GC_collect_a_little : Int
fun collect = GC_gcollect
fun add_roots = GC_add_roots(low : Void*, high : Void*)
fun enable = GC_enable
fun disable = GC_disable
fun is_disabled = GC_is_disabled : Int
fun set_handle_fork = GC_set_handle_fork(value : Int)
fun base = GC_base(displaced_pointer : Void*) : Void*
fun is_heap_ptr = GC_is_heap_ptr(pointer : Void*) : Int
fun general_register_disappearing_link = GC_general_register_disappearing_link(link : Void**, obj : Void*) : Int
alias Finalizer = Void*, Void* ->
fun register_finalizer = GC_register_finalizer(obj : Void*, fn : Finalizer, cd : Void*, ofn : Finalizer*, ocd : Void**)
fun register_finalizer_ignore_self = GC_register_finalizer_ignore_self(obj : Void*, fn : Finalizer, cd : Void*, ofn : Finalizer*, ocd : Void**)
fun invoke_finalizers = GC_invoke_finalizers : Int
fun get_heap_usage_safe = GC_get_heap_usage_safe(heap_size : Word*, free_bytes : Word*, unmapped_bytes : Word*, bytes_since_gc : Word*, total_bytes : Word*)
fun set_max_heap_size = GC_set_max_heap_size(Word)
fun get_prof_stats = GC_get_prof_stats(stats : ProfStats*, size : SizeT)
fun get_start_callback = GC_get_start_callback : Void*
fun set_start_callback = GC_set_start_callback(callback : ->)
fun set_push_other_roots = GC_set_push_other_roots(proc : ->)
fun get_push_other_roots = GC_get_push_other_roots : ->
fun push_all_eager = GC_push_all_eager(bottom : Void*, top : Void*)
{% if flag?(:preview_mt) %}
fun get_my_stackbottom = GC_get_my_stackbottom(sb : StackBase*) : ThreadHandle
fun set_stackbottom = GC_set_stackbottom(th : ThreadHandle, sb : StackBase*) : ThreadHandle
{% else %}
$stackbottom = GC_stackbottom : Void*
{% end %}
fun set_on_collection_event = GC_set_on_collection_event(cb : ->)
$gc_no = GC_gc_no : LibC::ULong
$bytes_found = GC_bytes_found : LibC::Long
# GC_on_collection_event isn't exported. Can't collect totals without it.
# bytes_allocd, heap_size, unmapped_bytes are macros
fun size = GC_size(addr : Void*) : LibC::SizeT
{% unless flag?(:win32) %}
# Boehm GC requires to use GC_pthread_create and GC_pthread_join instead of pthread_create and pthread_join
fun pthread_create = GC_pthread_create(thread : LibC::PthreadT*, attr : LibC::PthreadAttrT*, start : Void* -> Void*, arg : Void*) : LibC::Int
fun pthread_join = GC_pthread_join(thread : LibC::PthreadT, value : Void**) : LibC::Int
fun pthread_detach = GC_pthread_detach(thread : LibC::PthreadT) : LibC::Int
{% end %}
alias WarnProc = LibC::Char*, Word ->
fun set_warn_proc = GC_set_warn_proc(WarnProc)
$warn_proc = GC_current_warn_proc : WarnProc
end
module GC
{% if flag?(:preview_mt) %}
@@lock = Crystal::RWLock.new
{% end %}
# :nodoc:
def self.malloc(size : LibC::SizeT) : Void*
LibGC.malloc(size)
end
# :nodoc:
def self.malloc_atomic(size : LibC::SizeT) : Void*
LibGC.malloc_atomic(size)
end
# :nodoc:
def self.realloc(ptr : Void*, size : LibC::SizeT) : Void*
LibGC.realloc(ptr, size)
end
def self.init
{% unless flag?(:win32) %}
LibGC.set_handle_fork(1)
{% end %}
LibGC.init
LibGC.set_start_callback ->do
GC.lock_write
end
end
def self.collect
LibGC.collect
end
def self.enable
unless LibGC.is_disabled != 0
raise "GC is not disabled"
end
LibGC.enable
end
def self.disable
LibGC.disable
end
def self.free(pointer : Void*)
LibGC.free(pointer)
end
def self.add_finalizer(object : Reference)
add_finalizer_impl(object)
end
def self.add_finalizer(object)
# Nothing
end
private def self.add_finalizer_impl(object : T) forall T
LibGC.register_finalizer_ignore_self(object.as(Void*),
->(obj, data) { obj.as(T).finalize },
nil, nil, nil)
nil
end
def self.add_root(object : Reference)
roots = @@roots ||= [] of Pointer(Void)
roots << Pointer(Void).new(object.object_id)
end
def self.register_disappearing_link(pointer : Void**)
base = LibGC.base(pointer.value)
LibGC.general_register_disappearing_link(pointer, base)
end
def self.is_heap_ptr(pointer : Void*)
LibGC.is_heap_ptr(pointer) != 0
end
def self.stats
LibGC.get_heap_usage_safe(out heap_size, out free_bytes, out unmapped_bytes, out bytes_since_gc, out total_bytes)
# collections = LibGC.gc_no - 1
# bytes_found = LibGC.bytes_found
Stats.new(
# collections: collections,
# bytes_found: bytes_found,
heap_size: heap_size,
free_bytes: free_bytes,
unmapped_bytes: unmapped_bytes,
bytes_since_gc: bytes_since_gc,
total_bytes: total_bytes
)
end
def self.prof_stats
LibGC.get_prof_stats(out stats, sizeof(LibGC::ProfStats))
ProfStats.new(
heap_size: stats.heap_size,
free_bytes: stats.free_bytes,
unmapped_bytes: stats.unmapped_bytes,
bytes_since_gc: stats.bytes_since_gc,
bytes_before_gc: stats.bytes_before_gc,
non_gc_bytes: stats.non_gc_bytes,
gc_no: stats.gc_no,
markers_m1: stats.markers_m1,
bytes_reclaimed_since_gc: stats.bytes_reclaimed_since_gc,
reclaimed_bytes_before_gc: stats.reclaimed_bytes_before_gc)
end
{% unless flag?(:win32) %}
# :nodoc:
def self.pthread_create(thread : LibC::PthreadT*, attr : LibC::PthreadAttrT*, start : Void* -> Void*, arg : Void*)
LibGC.pthread_create(thread, attr, start, arg)
end
# :nodoc:
def self.pthread_join(thread : LibC::PthreadT) : Void*
ret = LibGC.pthread_join(thread, out value)
raise RuntimeError.from_os_error("pthread_join", Errno.new(ret)) unless ret == 0
value
end
# :nodoc:
def self.pthread_detach(thread : LibC::PthreadT)
LibGC.pthread_detach(thread)
end
{% end %}
# :nodoc:
def self.current_thread_stack_bottom
{% if flag?(:preview_mt) %}
th = LibGC.get_my_stackbottom(out sb)
{th, sb.mem_base}
{% else %}
{Pointer(Void).null, LibGC.stackbottom}
{% end %}
end
# :nodoc:
{% if flag?(:preview_mt) %}
def self.set_stackbottom(thread_handle : Void*, stack_bottom : Void*)
sb = LibGC::StackBase.new
sb.mem_base = stack_bottom
LibGC.set_stackbottom(thread_handle, pointerof(sb))
end
{% else %}
def self.set_stackbottom(stack_bottom : Void*)
LibGC.stackbottom = stack_bottom
end
{% end %}
# :nodoc:
def self.lock_read
{% if flag?(:preview_mt) %}
@@lock.read_lock
{% end %}
end
# :nodoc:
def self.unlock_read
{% if flag?(:preview_mt) %}
@@lock.read_unlock
{% end %}
end
# :nodoc:
def self.lock_write
{% if flag?(:preview_mt) %}
@@lock.write_lock
{% end %}
end
# :nodoc:
def self.unlock_write
{% if flag?(:preview_mt) %}
@@lock.write_unlock
{% end %}
end
# :nodoc:
def self.push_stack(stack_top, stack_bottom)
LibGC.push_all_eager(stack_top, stack_bottom)
end
# :nodoc:
def self.before_collect(&block)
@@curr_push_other_roots = block
@@prev_push_other_roots = LibGC.get_push_other_roots
LibGC.set_push_other_roots ->do
@@curr_push_other_roots.try(&.call)
@@prev_push_other_roots.try(&.call)
end
end
# pushes the stack of pending fibers when the GC wants to collect memory:
GC.before_collect do
Fiber.unsafe_each do |fiber|
fiber.push_gc_roots unless fiber.running?
end
{% if flag?(:preview_mt) %}
Thread.unsafe_each do |thread|
if scheduler = thread.@scheduler
fiber = scheduler.@current
GC.set_stackbottom(thread.gc_thread_handler, fiber.@stack_bottom)
end
end
{% end %}
GC.unlock_write
end
end