orc.nim 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2020 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. # Cycle collector based on
  10. # https://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon01Concurrent.pdf
  11. # And ideas from Lins' in 2008 by the notion of "critical links", see
  12. # "Cyclic reference counting" by Rafael Dueire Lins
  13. # R.D. Lins / Information Processing Letters 109 (2008) 71–78
  14. #
  15. type PT = Cell
  16. include cellseqs_v2
  17. const
  18. colBlack = 0b000
  19. colGray = 0b001
  20. colWhite = 0b010
  21. maybeCycle = 0b100 # possibly part of a cycle; this has to be a "sticky" bit
  22. jumpStackFlag = 0b1000
  23. colorMask = 0b011
  24. logOrc = defined(nimArcIds)
  25. type
  26. TraceProc = proc (p, env: pointer) {.nimcall, benign.}
  27. DisposeProc = proc (p: pointer) {.nimcall, benign.}
  28. template color(c): untyped = c.rc and colorMask
  29. template setColor(c, col) =
  30. when col == colBlack:
  31. c.rc = c.rc and not colorMask
  32. else:
  33. c.rc = c.rc and not colorMask or col
  34. const
  35. optimizedOrc = not defined(nimOldOrc)
  36. proc nimIncRefCyclic(p: pointer; cyclic: bool) {.compilerRtl, inl.} =
  37. let h = head(p)
  38. inc h.rc, rcIncrement
  39. when optimizedOrc:
  40. if cyclic:
  41. h.rc = h.rc or maybeCycle
  42. proc nimMarkCyclic(p: pointer) {.compilerRtl, inl.} =
  43. when optimizedOrc:
  44. if p != nil:
  45. let h = head(p)
  46. h.rc = h.rc or maybeCycle
  47. proc unsureAsgnRef(dest: ptr pointer, src: pointer) {.inline.} =
  48. # This is only used by the old RTTI mechanism and we know
  49. # that 'dest[]' is nil and needs no destruction. Which is really handy
  50. # as we cannot destroy the object reliably if it's an object of unknown
  51. # compile-time type.
  52. dest[] = src
  53. if src != nil: nimIncRefCyclic(src, true)
  54. const
  55. useJumpStack = false # for thavlak the jump stack doesn't improve the performance at all
  56. type
  57. GcEnv = object
  58. traceStack: CellSeq
  59. when useJumpStack:
  60. jumpStack: CellSeq # Lins' jump stack in order to speed up traversals
  61. toFree: CellSeq
  62. freed, touched: int
  63. proc trace(s: Cell; desc: PNimTypeV2; j: var GcEnv) {.inline.} =
  64. if desc.traceImpl != nil:
  65. var p = s +! sizeof(RefHeader)
  66. cast[TraceProc](desc.traceImpl)(p, addr(j))
  67. when logOrc:
  68. proc writeCell(msg: cstring; s: Cell; desc: PNimTypeV2) =
  69. cfprintf(cstderr, "%s %s %ld root index: %ld; RC: %ld; color: %ld\n",
  70. msg, desc.name, s.refId, s.rootIdx, s.rc shr rcShift, s.color)
  71. proc free(s: Cell; desc: PNimTypeV2) {.inline.} =
  72. when traceCollector:
  73. cprintf("[From ] %p rc %ld color %ld\n", s, s.rc shr rcShift, s.color)
  74. let p = s +! sizeof(RefHeader)
  75. when logOrc: writeCell("free", s, desc)
  76. if desc.disposeImpl != nil:
  77. cast[DisposeProc](desc.disposeImpl)(p)
  78. when false:
  79. cstderr.rawWrite desc.name
  80. cstderr.rawWrite " "
  81. if desc.disposeImpl == nil:
  82. cstderr.rawWrite "lacks dispose"
  83. if desc.traceImpl != nil:
  84. cstderr.rawWrite ", but has trace\n"
  85. else:
  86. cstderr.rawWrite ", and lacks trace\n"
  87. else:
  88. cstderr.rawWrite "has dispose!\n"
  89. nimRawDispose(p, desc.align)
  90. proc nimTraceRef(q: pointer; desc: PNimTypeV2; env: pointer) {.compilerRtl, inline.} =
  91. let p = cast[ptr pointer](q)
  92. if p[] != nil:
  93. var j = cast[ptr GcEnv](env)
  94. j.traceStack.add(head p[], desc)
  95. proc nimTraceRefDyn(q: pointer; env: pointer) {.compilerRtl, inline.} =
  96. let p = cast[ptr pointer](q)
  97. if p[] != nil:
  98. var j = cast[ptr GcEnv](env)
  99. j.traceStack.add(head p[], cast[ptr PNimTypeV2](p[])[])
  100. template orcAssert(cond, msg) =
  101. when logOrc:
  102. if not cond:
  103. cfprintf(cstderr, "[Bug!] %s\n", msg)
  104. quit 1
  105. var
  106. roots {.threadvar.}: CellSeq
  107. proc unregisterCycle(s: Cell) =
  108. # swap with the last element. O(1)
  109. let idx = s.rootIdx-1
  110. when false:
  111. if idx >= roots.len or idx < 0:
  112. cprintf("[Bug!] %ld\n", idx)
  113. quit 1
  114. roots.d[idx] = roots.d[roots.len-1]
  115. roots.d[idx][0].rootIdx = idx+1
  116. dec roots.len
  117. s.rootIdx = 0
  118. proc scanBlack(s: Cell; desc: PNimTypeV2; j: var GcEnv) =
  119. #[
  120. proc scanBlack(s: Cell) =
  121. setColor(s, colBlack)
  122. for t in sons(s):
  123. t.rc = t.rc + rcIncrement
  124. if t.color != colBlack:
  125. scanBlack(t)
  126. ]#
  127. s.setColor colBlack
  128. let until = j.traceStack.len
  129. trace(s, desc, j)
  130. when logOrc: writeCell("root still alive", s, desc)
  131. while j.traceStack.len > until:
  132. let (t, desc) = j.traceStack.pop()
  133. inc t.rc, rcIncrement
  134. if t.color != colBlack:
  135. t.setColor colBlack
  136. trace(t, desc, j)
  137. when logOrc: writeCell("child still alive", t, desc)
  138. proc markGray(s: Cell; desc: PNimTypeV2; j: var GcEnv) =
  139. #[
  140. proc markGray(s: Cell) =
  141. if s.color != colGray:
  142. setColor(s, colGray)
  143. for t in sons(s):
  144. t.rc = t.rc - rcIncrement
  145. if t.color != colGray:
  146. markGray(t)
  147. ]#
  148. if s.color != colGray:
  149. s.setColor colGray
  150. inc j.touched
  151. orcAssert(j.traceStack.len == 0, "markGray: trace stack not empty")
  152. trace(s, desc, j)
  153. while j.traceStack.len > 0:
  154. let (t, desc) = j.traceStack.pop()
  155. dec t.rc, rcIncrement
  156. when useJumpStack:
  157. if (t.rc shr rcShift) >= 0 and (t.rc and jumpStackFlag) == 0:
  158. t.rc = t.rc or jumpStackFlag
  159. when traceCollector:
  160. cprintf("[Now in jumpstack] %p %ld color %ld in jumpstack %ld\n", t, t.rc shr rcShift, t.color, t.rc and jumpStackFlag)
  161. j.jumpStack.add(t, desc)
  162. if t.color != colGray:
  163. t.setColor colGray
  164. inc j.touched
  165. trace(t, desc, j)
  166. proc scan(s: Cell; desc: PNimTypeV2; j: var GcEnv) =
  167. #[
  168. proc scan(s: Cell) =
  169. if s.color == colGray:
  170. if s.rc > 0:
  171. scanBlack(s)
  172. else:
  173. s.setColor(colWhite)
  174. for t in sons(s): scan(t)
  175. ]#
  176. if s.color == colGray:
  177. if (s.rc shr rcShift) >= 0:
  178. scanBlack(s, desc, j)
  179. # XXX this should be done according to Lins' paper but currently breaks
  180. #when useJumpStack:
  181. # s.setColor colPurple
  182. else:
  183. when useJumpStack:
  184. # first we have to repair all the nodes we have seen
  185. # that are still alive; we also need to mark what they
  186. # refer to as alive:
  187. while j.jumpStack.len > 0:
  188. let (t, desc) = j.jumpStack.pop
  189. # not in jump stack anymore!
  190. t.rc = t.rc and not jumpStackFlag
  191. if t.color == colGray and (t.rc shr rcShift) >= 0:
  192. scanBlack(t, desc, j)
  193. # XXX this should be done according to Lins' paper but currently breaks
  194. #t.setColor colPurple
  195. when traceCollector:
  196. cprintf("[jump stack] %p %ld\n", t, t.rc shr rcShift)
  197. orcAssert(j.traceStack.len == 0, "scan: trace stack not empty")
  198. s.setColor(colWhite)
  199. trace(s, desc, j)
  200. while j.traceStack.len > 0:
  201. let (t, desc) = j.traceStack.pop()
  202. if t.color == colGray:
  203. if (t.rc shr rcShift) >= 0:
  204. scanBlack(t, desc, j)
  205. else:
  206. when useJumpStack:
  207. # first we have to repair all the nodes we have seen
  208. # that are still alive; we also need to mark what they
  209. # refer to as alive:
  210. while j.jumpStack.len > 0:
  211. let (t, desc) = j.jumpStack.pop
  212. # not in jump stack anymore!
  213. t.rc = t.rc and not jumpStackFlag
  214. if t.color == colGray and (t.rc shr rcShift) >= 0:
  215. scanBlack(t, desc, j)
  216. # XXX this should be done according to Lins' paper but currently breaks
  217. #t.setColor colPurple
  218. when traceCollector:
  219. cprintf("[jump stack] %p %ld\n", t, t.rc shr rcShift)
  220. t.setColor(colWhite)
  221. trace(t, desc, j)
  222. when false:
  223. proc writeCell(msg: cstring; s: Cell) =
  224. cfprintf(cstderr, "%s %p root index: %ld; RC: %ld; color: %ld\n",
  225. msg, s, s.rootIdx, s.rc shr rcShift, s.color)
  226. proc collectWhite(s: Cell; desc: PNimTypeV2; j: var GcEnv) =
  227. #[
  228. proc collectWhite(s: Cell) =
  229. if s.color == colWhite and not buffered(s):
  230. s.setColor(colBlack)
  231. for t in sons(s):
  232. collectWhite(t)
  233. free(s) # watch out, a bug here!
  234. ]#
  235. if s.color == colWhite and s.rootIdx == 0:
  236. orcAssert(j.traceStack.len == 0, "collectWhite: trace stack not empty")
  237. s.setColor(colBlack)
  238. j.toFree.add(s, desc)
  239. trace(s, desc, j)
  240. while j.traceStack.len > 0:
  241. let (t, desc) = j.traceStack.pop()
  242. if t.color == colWhite and t.rootIdx == 0:
  243. j.toFree.add(t, desc)
  244. t.setColor(colBlack)
  245. trace(t, desc, j)
  246. proc collectCyclesBacon(j: var GcEnv) =
  247. # pretty direct translation from
  248. # https://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon01Concurrent.pdf
  249. # Fig. 2. Synchronous Cycle Collection
  250. #[
  251. for s in roots:
  252. markGray(s)
  253. for s in roots:
  254. scan(s)
  255. for s in roots:
  256. remove s from roots
  257. s.buffered = false
  258. collectWhite(s)
  259. ]#
  260. when logOrc:
  261. for i in 0 ..< roots.len:
  262. writeCell("root", roots.d[i][0], roots.d[i][1])
  263. for i in 0 ..< roots.len:
  264. markGray(roots.d[i][0], roots.d[i][1], j)
  265. for i in 0 ..< roots.len:
  266. scan(roots.d[i][0], roots.d[i][1], j)
  267. init j.toFree
  268. for i in 0 ..< roots.len:
  269. let s = roots.d[i][0]
  270. s.rootIdx = 0
  271. collectWhite(s, roots.d[i][1], j)
  272. for i in 0 ..< j.toFree.len:
  273. free(j.toFree.d[i][0], j.toFree.d[i][1])
  274. inc j.freed, j.toFree.len
  275. deinit j.toFree
  276. #roots.len = 0
  277. const
  278. defaultThreshold = when defined(nimAdaptiveOrc): 128 else: 10_000
  279. when defined(nimStressOrc):
  280. const rootsThreshold = 10 # broken with -d:nimStressOrc: 10 and for havlak iterations 1..8
  281. else:
  282. var rootsThreshold = defaultThreshold
  283. proc collectCycles() =
  284. ## Collect cycles.
  285. when logOrc:
  286. cfprintf(cstderr, "[collectCycles] begin\n")
  287. var j: GcEnv
  288. init j.traceStack
  289. when useJumpStack:
  290. init j.jumpStack
  291. collectCyclesBacon(j)
  292. while j.jumpStack.len > 0:
  293. let (t, desc) = j.jumpStack.pop
  294. # not in jump stack anymore!
  295. t.rc = t.rc and not jumpStackFlag
  296. deinit j.jumpStack
  297. else:
  298. collectCyclesBacon(j)
  299. deinit j.traceStack
  300. deinit roots
  301. when not defined(nimStressOrc):
  302. # compute the threshold based on the previous history
  303. # of the cycle collector's effectiveness:
  304. # we're effective when we collected 50% or more of the nodes
  305. # we touched. If we're effective, we can reset the threshold:
  306. if j.freed * 2 >= j.touched:
  307. when defined(nimAdaptiveOrc):
  308. rootsThreshold = max(rootsThreshold div 2, 16)
  309. else:
  310. rootsThreshold = defaultThreshold
  311. #cfprintf(cstderr, "[collectCycles] freed %ld, touched %ld new threshold %ld\n", j.freed, j.touched, rootsThreshold)
  312. elif rootsThreshold < high(int) div 4:
  313. rootsThreshold = rootsThreshold * 3 div 2
  314. when logOrc:
  315. cfprintf(cstderr, "[collectCycles] end; freed %ld new threshold %ld touched: %ld mem: %ld\n", j.freed, rootsThreshold, j.touched,
  316. getOccupiedMem())
  317. proc registerCycle(s: Cell; desc: PNimTypeV2) =
  318. s.rootIdx = roots.len+1
  319. if roots.d == nil: init(roots)
  320. add(roots, s, desc)
  321. if roots.len >= rootsThreshold:
  322. collectCycles()
  323. when logOrc:
  324. writeCell("[added root]", s, desc)
  325. proc GC_runOrc* =
  326. ## Forces a cycle collection pass.
  327. collectCycles()
  328. proc GC_enableOrc*() =
  329. ## Enables the cycle collector subsystem of ``--gc:orc``. This is a ``--gc:orc``
  330. ## specific API. Check with ``when defined(gcOrc)`` for its existence.
  331. when not defined(nimStressOrc):
  332. rootsThreshold = defaultThreshold
  333. proc GC_disableOrc*() =
  334. ## Disables the cycle collector subsystem of ``--gc:orc``. This is a ``--gc:orc``
  335. ## specific API. Check with ``when defined(gcOrc)`` for its existence.
  336. when not defined(nimStressOrc):
  337. rootsThreshold = high(int)
  338. proc GC_fullCollect* =
  339. ## Forces a full garbage collection pass. With ``--gc:orc`` triggers the cycle
  340. ## collector. This is an alias for ``GC_runOrc``.
  341. collectCycles()
  342. proc GC_enableMarkAndSweep*() =
  343. ## For ``--gc:orc`` an alias for ``GC_enableOrc``.
  344. GC_enableOrc()
  345. proc GC_disableMarkAndSweep*() =
  346. ## For ``--gc:orc`` an alias for ``GC_disableOrc``.
  347. GC_disableOrc()
  348. when optimizedOrc:
  349. template markedAsCyclic(s: Cell): bool = (s.rc and maybeCycle) != 0
  350. else:
  351. template markedAsCyclic(s: Cell): bool = true
  352. proc rememberCycle(isDestroyAction: bool; s: Cell; desc: PNimTypeV2) {.noinline.} =
  353. if isDestroyAction:
  354. if s.rootIdx > 0:
  355. unregisterCycle(s)
  356. else:
  357. # do not call 'rememberCycle' again unless this cell
  358. # got an 'incRef' event:
  359. if s.rootIdx == 0 and markedAsCyclic(s):
  360. s.setColor colBlack
  361. registerCycle(s, desc)
  362. proc nimDecRefIsLastCyclicDyn(p: pointer): bool {.compilerRtl, inl.} =
  363. if p != nil:
  364. var cell = head(p)
  365. if (cell.rc and not rcMask) == 0:
  366. result = true
  367. #cprintf("[DESTROY] %p\n", p)
  368. else:
  369. dec cell.rc, rcIncrement
  370. #if cell.color == colPurple:
  371. rememberCycle(result, cell, cast[ptr PNimTypeV2](p)[])
  372. proc nimDecRefIsLastCyclicStatic(p: pointer; desc: PNimTypeV2): bool {.compilerRtl, inl.} =
  373. if p != nil:
  374. var cell = head(p)
  375. if (cell.rc and not rcMask) == 0:
  376. result = true
  377. #cprintf("[DESTROY] %p %s\n", p, desc.name)
  378. else:
  379. dec cell.rc, rcIncrement
  380. #if cell.color == colPurple:
  381. rememberCycle(result, cell, desc)