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
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
|
#
#
# Nim's Runtime Library
# (c) Copyright 2020 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
# Cycle collector based on
# https://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon01Concurrent.pdf
# And ideas from Lins' in 2008 by the notion of "critical links", see
# "Cyclic reference counting" by Rafael Dueire Lins
# R.D. Lins / Information Processing Letters 109 (2008) 71–78
#
type PT = Cell
include cellseqs_v2
const
colBlack = 0b000
colGray = 0b001
colWhite = 0b010
colPurple = 0b011
isCycleCandidate = 0b100 # cell is marked as a cycle candidate
jumpStackFlag = 0b1000
colorMask = 0b011
type
TraceProc = proc (p, env: pointer) {.nimcall, benign.}
DisposeProc = proc (p: pointer) {.nimcall, benign.}
template color(c): untyped = c.rc and colorMask
template setColor(c, col) =
when col == colBlack:
c.rc = c.rc and not colorMask
else:
c.rc = c.rc and not colorMask or col
proc nimIncRefCyclic(p: pointer) {.compilerRtl, inl.} =
let h = head(p)
inc h.rc, rcIncrement
h.setColor colPurple # mark as potential cycle!
const
useJumpStack = false # for thavlak the jump stack doesn't improve the performance at all
type
GcEnv = object
traceStack: CellSeq
when useJumpStack:
jumpStack: CellSeq # Lins' jump stack in order to speed up traversals
toFree: CellSeq
freed, touched: int
proc trace(s: Cell; desc: PNimType; j: var GcEnv) {.inline.} =
if desc.traceImpl != nil:
var p = s +! sizeof(RefHeader)
cast[TraceProc](desc.traceImpl)(p, addr(j))
when true:
template debug(str: cstring; s: Cell) = discard
else:
proc debug(str: cstring; s: Cell) =
let p = s +! sizeof(RefHeader)
cprintf("[%s] name %s RC %ld\n", str, p, s.rc shr rcShift)
proc free(s: Cell; desc: PNimType) {.inline.} =
when traceCollector:
cprintf("[From ] %p rc %ld color %ld\n", s, s.rc shr rcShift, s.color)
let p = s +! sizeof(RefHeader)
debug("free", s)
if desc.disposeImpl != nil:
cast[DisposeProc](desc.disposeImpl)(p)
nimRawDispose(p)
proc nimTraceRef(q: pointer; desc: PNimType; env: pointer) {.compilerRtl.} =
let p = cast[ptr pointer](q)
if p[] != nil:
var j = cast[ptr GcEnv](env)
j.traceStack.add(head p[], desc)
proc nimTraceRefDyn(q: pointer; env: pointer) {.compilerRtl.} =
let p = cast[ptr pointer](q)
if p[] != nil:
var j = cast[ptr GcEnv](env)
j.traceStack.add(head p[], cast[ptr PNimType](p[])[])
var
roots {.threadvar.}: CellSeq
proc unregisterCycle(s: Cell) =
# swap with the last element. O(1)
let idx = s.rootIdx
when false:
if idx >= roots.len or idx < 0:
cprintf("[Bug!] %ld\n", idx)
quit 1
roots.d[idx] = roots.d[roots.len-1]
roots.d[idx][0].rootIdx = idx
dec roots.len
proc scanBlack(s: Cell; desc: PNimType; j: var GcEnv) =
#[
proc scanBlack(s: Cell) =
setColor(s, colBlack)
for t in sons(s):
t.rc = t.rc + rcIncrement
if t.color != colBlack:
scanBlack(t)
]#
debug "scanBlack", s
s.setColor colBlack
trace(s, desc, j)
while j.traceStack.len > 0:
let (t, desc) = j.traceStack.pop()
inc t.rc, rcIncrement
debug "incRef", t
if t.color != colBlack:
t.setColor colBlack
trace(t, desc, j)
proc markGray(s: Cell; desc: PNimType; j: var GcEnv) =
#[
proc markGray(s: Cell) =
if s.color != colGray:
setColor(s, colGray)
for t in sons(s):
t.rc = t.rc - rcIncrement
if t.color != colGray:
markGray(t)
]#
if s.color != colGray:
s.setColor colGray
inc j.touched
trace(s, desc, j)
while j.traceStack.len > 0:
let (t, desc) = j.traceStack.pop()
dec t.rc, rcIncrement
when useJumpStack:
if (t.rc shr rcShift) >= 0 and (t.rc and jumpStackFlag) == 0:
t.rc = t.rc or jumpStackFlag
when traceCollector:
cprintf("[Now in jumpstack] %p %ld color %ld in jumpstack %ld\n", t, t.rc shr rcShift, t.color, t.rc and jumpStackFlag)
j.jumpStack.add(t, desc)
if t.color != colGray:
t.setColor colGray
inc j.touched
trace(t, desc, j)
proc scan(s: Cell; desc: PNimType; j: var GcEnv) =
#[
proc scan(s: Cell) =
if s.color == colGray:
if s.rc > 0:
scanBlack(s)
else:
s.setColor(colWhite)
for t in sons(s): scan(t)
]#
if s.color == colGray:
if (s.rc shr rcShift) >= 0:
scanBlack(s, desc, j)
# XXX this should be done according to Lins' paper but currently breaks
#when useJumpStack:
# s.setColor colPurple
else:
when useJumpStack:
# first we have to repair all the nodes we have seen
# that are still alive; we also need to mark what they
# refer to as alive:
while j.jumpStack.len > 0:
let (t, desc) = j.jumpStack.pop
# not in jump stack anymore!
t.rc = t.rc and not jumpStackFlag
if t.color == colGray and (t.rc shr rcShift) >= 0:
scanBlack(t, desc, j)
# XXX this should be done according to Lins' paper but currently breaks
#t.setColor colPurple
when traceCollector:
cprintf("[jump stack] %p %ld\n", t, t.rc shr rcShift)
s.setColor(colWhite)
trace(s, desc, j)
while j.traceStack.len > 0:
let (t, desc) = j.traceStack.pop()
if t.color == colGray:
if (t.rc shr rcShift) >= 0:
scanBlack(t, desc, j)
else:
when useJumpStack:
# first we have to repair all the nodes we have seen
# that are still alive; we also need to mark what they
# refer to as alive:
while j.jumpStack.len > 0:
let (t, desc) = j.jumpStack.pop
# not in jump stack anymore!
t.rc = t.rc and not jumpStackFlag
if t.color == colGray and (t.rc shr rcShift) >= 0:
scanBlack(t, desc, j)
# XXX this should be done according to Lins' paper but currently breaks
#t.setColor colPurple
when traceCollector:
cprintf("[jump stack] %p %ld\n", t, t.rc shr rcShift)
t.setColor(colWhite)
trace(t, desc, j)
when false:
proc writeCell(msg: cstring; s: Cell) =
cfprintf(cstderr, "%s %p root index: %ld; RC: %ld; color: %ld\n",
msg, s, s.rootIdx, s.rc shr rcShift, s.color)
proc collectWhite(s: Cell; desc: PNimType; j: var GcEnv) =
#[
proc collectWhite(s: Cell) =
if s.color == colWhite and not buffered(s):
s.setColor(colBlack)
for t in sons(s):
collectWhite(t)
free(s) # watch out, a bug here!
]#
if s.color == colWhite and (s.rc and isCycleCandidate) == 0:
s.setColor(colBlack)
j.toFree.add(s, desc)
trace(s, desc, j)
while j.traceStack.len > 0:
let (t, desc) = j.traceStack.pop()
if t.color == colWhite and (t.rc and isCycleCandidate) == 0:
j.toFree.add(t, desc)
t.setColor(colBlack)
trace(t, desc, j)
proc collectCyclesBacon(j: var GcEnv) =
# pretty direct translation from
# https://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon01Concurrent.pdf
# Fig. 2. Synchronous Cycle Collection
#[
for s in roots:
markGray(s)
for s in roots:
scan(s)
for s in roots:
remove s from roots
s.buffered = false
collectWhite(s)
]#
for i in 0 ..< roots.len:
markGray(roots.d[i][0], roots.d[i][1], j)
for i in 0 ..< roots.len:
scan(roots.d[i][0], roots.d[i][1], j)
init j.toFree
for i in 0 ..< roots.len:
let s = roots.d[i][0]
s.rc = s.rc and not isCycleCandidate
collectWhite(s, roots.d[i][1], j)
for i in 0 ..< j.toFree.len:
free(j.toFree.d[i][0], j.toFree.d[i][1])
inc j.freed, j.toFree.len
deinit j.toFree
#roots.len = 0
const
defaultThreshold = 10_000
var
rootsThreshold = defaultThreshold
proc collectCycles() =
## Collect cycles.
when false:
cfprintf(cstderr, "[collectCycles] begin\n")
var j: GcEnv
init j.traceStack
when useJumpStack:
init j.jumpStack
collectCyclesBacon(j)
while j.jumpStack.len > 0:
let (t, desc) = j.jumpStack.pop
# not in jump stack anymore!
t.rc = t.rc and not jumpStackFlag
deinit j.jumpStack
else:
collectCyclesBacon(j)
deinit j.traceStack
deinit roots
# compute the threshold based on the previous history
# of the cycle collector's effectiveness:
# we're effective when we collected 50% or more of the nodes
# we touched. If we're effective, we can reset the threshold:
if j.freed * 2 >= j.touched:
rootsThreshold = defaultThreshold
elif rootsThreshold < high(int) div 4:
rootsThreshold = rootsThreshold * 3 div 2
when false:
cfprintf(cstderr, "[collectCycles] freed %ld new threshold %ld\n", j.freed, rootsThreshold)
proc registerCycle(s: Cell; desc: PNimType) =
if roots.len >= rootsThreshold:
collectCycles()
if roots.d == nil: init(roots)
s.rootIdx = roots.len
add(roots, s, desc)
#writeCell("[added root]", s)
proc GC_fullCollect* =
## Forces a full garbage collection pass. With ``--gc:orc`` triggers the cycle
## collector.
collectCycles()
proc GC_enableMarkAndSweep() =
rootsThreshold = defaultThreshold
proc GC_disableMarkAndSweep() =
rootsThreshold = high(int)
proc rememberCycle(isDestroyAction: bool; s: Cell; desc: PNimType) {.noinline.} =
if isDestroyAction:
if (s.rc and isCycleCandidate) != 0:
s.rc = s.rc and not isCycleCandidate
unregisterCycle(s)
else:
# do not call 'rememberCycle' again unless this cell
# got an 'incRef' event:
#s.setColor colGreen # XXX This is wrong!
if (s.rc and isCycleCandidate) == 0:
s.rc = s.rc or isCycleCandidate
registerCycle(s, desc)
proc nimDecRefIsLastCyclicDyn(p: pointer): bool {.compilerRtl, inl.} =
if p != nil:
var cell = head(p)
if (cell.rc and not rcMask) == 0:
result = true
#cprintf("[DESTROY] %p\n", p)
else:
dec cell.rc, rcIncrement
if cell.color == colPurple:
rememberCycle(result, cell, cast[ptr PNimType](p)[])
proc nimDecRefIsLastCyclicStatic(p: pointer; desc: PNimType): bool {.compilerRtl, inl.} =
if p != nil:
var cell = head(p)
if (cell.rc and not rcMask) == 0:
result = true
#cprintf("[DESTROY] %p %s\n", p, desc.name)
else:
dec cell.rc, rcIncrement
if cell.color == colPurple:
rememberCycle(result, cell, desc)
|