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
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
|
#
#
# 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://www.cs.purdue.edu/homes/hosking/690M/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
#
include cellseqs_v2
const
colBlack = 0b000
colGray = 0b001
colWhite = 0b010
maybeCycle = 0b100 # possibly part of a cycle; this has to be a "sticky" bit
jumpStackFlag = 0b1000
colorMask = 0b011
logOrc = defined(nimArcIds)
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
const
optimizedOrc = false # not defined(nimOldOrc)
# XXX Still incorrect, see tests/arc/tdestroy_in_loopcond
proc nimIncRefCyclic(p: pointer; cyclic: bool) {.compilerRtl, inl.} =
let h = head(p)
inc h.rc, rcIncrement
when optimizedOrc:
if cyclic:
h.rc = h.rc or maybeCycle
proc nimMarkCyclic(p: pointer) {.compilerRtl, inl.} =
when optimizedOrc:
if p != nil:
let h = head(p)
h.rc = h.rc or maybeCycle
proc unsureAsgnRef(dest: ptr pointer, src: pointer) {.inline.} =
# This is only used by the old RTTI mechanism and we know
# that 'dest[]' is nil and needs no destruction. Which is really handy
# as we cannot destroy the object reliably if it's an object of unknown
# compile-time type.
dest[] = src
if src != nil: nimIncRefCyclic(src, true)
const
useJumpStack = false # for thavlak the jump stack doesn't improve the performance at all
type
GcEnv = object
traceStack: CellSeq[ptr pointer]
when useJumpStack:
jumpStack: CellSeq[ptr pointer] # Lins' jump stack in order to speed up traversals
toFree: CellSeq[Cell]
freed, touched, edges, rcSum: int
keepThreshold: bool
proc trace(s: Cell; desc: PNimTypeV2; j: var GcEnv) {.inline.} =
if desc.traceImpl != nil:
var p = s +! sizeof(RefHeader)
cast[TraceProc](desc.traceImpl)(p, addr(j))
include threadids
when logOrc:
proc writeCell(msg: cstring; s: Cell; desc: PNimTypeV2) =
cfprintf(cstderr, "%s %s %ld root index: %ld; RC: %ld; color: %ld; thread: %ld\n",
msg, desc.name, s.refId, s.rootIdx, s.rc shr rcShift, s.color, getThreadId())
proc free(s: Cell; desc: PNimTypeV2) {.inline.} =
when traceCollector:
cprintf("[From ] %p rc %ld color %ld\n", s, s.rc shr rcShift, s.color)
let p = s +! sizeof(RefHeader)
when logOrc: writeCell("free", s, desc)
if desc.destructor != nil:
cast[DestructorProc](desc.destructor)(p)
when false:
cstderr.rawWrite desc.name
cstderr.rawWrite " "
if desc.destructor == nil:
cstderr.rawWrite "lacks dispose"
if desc.traceImpl != nil:
cstderr.rawWrite ", but has trace\n"
else:
cstderr.rawWrite ", and lacks trace\n"
else:
cstderr.rawWrite "has dispose!\n"
nimRawDispose(p, desc.align)
template orcAssert(cond, msg) =
when logOrc:
if not cond:
cfprintf(cstderr, "[Bug!] %s\n", msg)
rawQuit 1
when logOrc:
proc strstr(s, sub: cstring): cstring {.header: "<string.h>", importc.}
proc nimTraceRef(q: pointer; desc: PNimTypeV2; env: pointer) {.compilerRtl, inl.} =
let p = cast[ptr pointer](q)
if p[] != nil:
orcAssert strstr(desc.name, "TType") == nil, "following a TType but it's acyclic!"
var j = cast[ptr GcEnv](env)
j.traceStack.add(p, desc)
proc nimTraceRefDyn(q: pointer; env: pointer) {.compilerRtl, inl.} =
let p = cast[ptr pointer](q)
if p[] != nil:
var j = cast[ptr GcEnv](env)
j.traceStack.add(p, cast[ptr PNimTypeV2](p[])[])
var
roots {.threadvar.}: CellSeq[Cell]
proc unregisterCycle(s: Cell) =
# swap with the last element. O(1)
let idx = s.rootIdx-1
when false:
if idx >= roots.len or idx < 0:
cprintf("[Bug!] %ld\n", idx)
rawQuit 1
roots.d[idx] = roots.d[roots.len-1]
roots.d[idx][0].rootIdx = idx+1
dec roots.len
s.rootIdx = 0
proc scanBlack(s: Cell; desc: PNimTypeV2; 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)
]#
s.setColor colBlack
let until = j.traceStack.len
trace(s, desc, j)
when logOrc: writeCell("root still alive", s, desc)
while j.traceStack.len > until:
let (entry, desc) = j.traceStack.pop()
let t = head entry[]
inc t.rc, rcIncrement
if t.color != colBlack:
t.setColor colBlack
trace(t, desc, j)
when logOrc: writeCell("child still alive", t, desc)
proc markGray(s: Cell; desc: PNimTypeV2; 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
# keep in mind that refcounts are zero based so add 1 here:
inc j.rcSum, (s.rc shr rcShift) + 1
orcAssert(j.traceStack.len == 0, "markGray: trace stack not empty")
trace(s, desc, j)
while j.traceStack.len > 0:
let (entry, desc) = j.traceStack.pop()
let t = head entry[]
dec t.rc, rcIncrement
inc j.edges
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(entry, desc)
if t.color != colGray:
t.setColor colGray
inc j.touched
# we already decremented its refcount so account for that:
inc j.rcSum, (t.rc shr rcShift) + 2
trace(t, desc, j)
proc scan(s: Cell; desc: PNimTypeV2; 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 (entry, desc) = j.jumpStack.pop
let t = head entry[]
# 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)
orcAssert(j.traceStack.len == 0, "scan: trace stack not empty")
s.setColor(colWhite)
trace(s, desc, j)
while j.traceStack.len > 0:
let (entry, desc) = j.traceStack.pop()
let t = head entry[]
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 (entry, desc) = j.jumpStack.pop
let t = head entry[]
# 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 collectColor(s: Cell; desc: PNimTypeV2; col: int; j: var GcEnv) =
#[
was: 'collectWhite'.
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 == col and s.rootIdx == 0:
orcAssert(j.traceStack.len == 0, "collectWhite: trace stack not empty")
s.setColor(colBlack)
j.toFree.add(s, desc)
trace(s, desc, j)
while j.traceStack.len > 0:
let (entry, desc) = j.traceStack.pop()
let t = head entry[]
entry[] = nil # ensure that the destructor does touch moribund objects!
if t.color == col and t.rootIdx == 0:
j.toFree.add(t, desc)
t.setColor(colBlack)
trace(t, desc, j)
proc collectCyclesBacon(j: var GcEnv; lowMark: int) =
# 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)
]#
let last = roots.len - 1
when logOrc:
for i in countdown(last, lowMark):
writeCell("root", roots.d[i][0], roots.d[i][1])
for i in countdown(last, lowMark):
markGray(roots.d[i][0], roots.d[i][1], j)
var colToCollect = colWhite
if j.rcSum == j.edges:
# short-cut: we know everything is garbage:
colToCollect = colGray
# remember the fact that we got so lucky:
j.keepThreshold = true
else:
for i in countdown(last, lowMark):
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.rootIdx = 0
collectColor(s, roots.d[i][1], colToCollect, 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 = when defined(nimFixedOrc): 10_000 else: 128
when defined(nimStressOrc):
const rootsThreshold = 10 # broken with -d:nimStressOrc: 10 and for havlak iterations 1..8
else:
var rootsThreshold = defaultThreshold
proc partialCollect(lowMark: int) =
when false:
if roots.len < 10 + lowMark: return
when logOrc:
cfprintf(cstderr, "[partialCollect] begin\n")
var j: GcEnv
init j.traceStack
collectCyclesBacon(j, lowMark)
when logOrc:
cfprintf(cstderr, "[partialCollect] end; freed %ld touched: %ld work: %ld\n", j.freed, j.touched,
roots.len - lowMark)
roots.len = lowMark
deinit j.traceStack
proc collectCycles() =
## Collect cycles.
when logOrc:
cfprintf(cstderr, "[collectCycles] begin\n")
var j: GcEnv
init j.traceStack
when useJumpStack:
init j.jumpStack
collectCyclesBacon(j, 0)
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, 0)
deinit j.traceStack
deinit roots
when not defined(nimStressOrc):
# 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.keepThreshold and rootsThreshold <= defaultThreshold:
discard
elif j.freed * 2 >= j.touched:
when not defined(nimFixedOrc):
rootsThreshold = max(rootsThreshold div 3 * 2, 16)
else:
rootsThreshold = defaultThreshold
#cfprintf(cstderr, "[collectCycles] freed %ld, touched %ld new threshold %ld\n", j.freed, j.touched, rootsThreshold)
elif rootsThreshold < high(int) div 4:
rootsThreshold = rootsThreshold * 3 div 2
when logOrc:
cfprintf(cstderr, "[collectCycles] end; freed %ld new threshold %ld touched: %ld mem: %ld rcSum: %ld edges: %ld\n", j.freed, rootsThreshold, j.touched,
getOccupiedMem(), j.rcSum, j.edges)
proc registerCycle(s: Cell; desc: PNimTypeV2) =
s.rootIdx = roots.len+1
if roots.d == nil: init(roots)
add(roots, s, desc)
if roots.len >= rootsThreshold:
collectCycles()
when logOrc:
writeCell("[added root]", s, desc)
orcAssert strstr(desc.name, "TType") == nil, "added a TType as a root!"
proc GC_runOrc* =
## Forces a cycle collection pass.
collectCycles()
orcAssert roots.len == 0, "roots not empty!"
proc GC_enableOrc*() =
## Enables the cycle collector subsystem of `--gc:orc`. This is a `--gc:orc`
## specific API. Check with `when defined(gcOrc)` for its existence.
when not defined(nimStressOrc):
rootsThreshold = defaultThreshold
proc GC_disableOrc*() =
## Disables the cycle collector subsystem of `--gc:orc`. This is a `--gc:orc`
## specific API. Check with `when defined(gcOrc)` for its existence.
when not defined(nimStressOrc):
rootsThreshold = high(int)
proc GC_prepareOrc*(): int {.inline.} = roots.len
proc GC_partialCollect*(limit: int) =
partialCollect(limit)
proc GC_fullCollect* =
## Forces a full garbage collection pass. With `--gc:orc` triggers the cycle
## collector. This is an alias for `GC_runOrc`.
collectCycles()
proc GC_enableMarkAndSweep*() =
## For `--gc:orc` an alias for `GC_enableOrc`.
GC_enableOrc()
proc GC_disableMarkAndSweep*() =
## For `--gc:orc` an alias for `GC_disableOrc`.
GC_disableOrc()
const
acyclicFlag = 1 # see also cggtypes.nim, proc genTypeInfoV2Impl
when optimizedOrc:
template markedAsCyclic(s: Cell; desc: PNimTypeV2): bool =
(desc.flags and acyclicFlag) == 0 and (s.rc and maybeCycle) != 0
else:
template markedAsCyclic(s: Cell; desc: PNimTypeV2): bool =
(desc.flags and acyclicFlag) == 0
proc rememberCycle(isDestroyAction: bool; s: Cell; desc: PNimTypeV2) {.noinline.} =
if isDestroyAction:
if s.rootIdx > 0:
unregisterCycle(s)
else:
# do not call 'rememberCycle' again unless this cell
# got an 'incRef' event:
if s.rootIdx == 0 and markedAsCyclic(s, desc):
s.setColor colBlack
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 PNimTypeV2](p)[])
proc nimDecRefIsLastDyn(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:
if result:
if cell.rootIdx > 0:
unregisterCycle(cell)
proc nimDecRefIsLastCyclicStatic(p: pointer; desc: PNimTypeV2): 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)
|